@kirosnn/mosaic 0.0.91 → 0.73.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 +1 -1
- package/README.md +2 -6
- package/package.json +55 -48
- package/src/agent/Agent.ts +353 -131
- package/src/agent/context.ts +4 -4
- package/src/agent/prompts/systemPrompt.ts +209 -70
- package/src/agent/prompts/toolsPrompt.ts +285 -138
- package/src/agent/provider/anthropic.ts +109 -105
- package/src/agent/provider/google.ts +111 -107
- package/src/agent/provider/mistral.ts +95 -95
- package/src/agent/provider/ollama.ts +73 -17
- package/src/agent/provider/openai.ts +146 -102
- package/src/agent/provider/rateLimit.ts +178 -0
- package/src/agent/provider/reasoning.ts +29 -0
- package/src/agent/provider/xai.ts +108 -104
- package/src/agent/tools/definitions.ts +15 -1
- package/src/agent/tools/executor.ts +717 -98
- package/src/agent/tools/exploreExecutor.ts +20 -22
- package/src/agent/tools/fetch.ts +58 -0
- package/src/agent/tools/glob.ts +20 -4
- package/src/agent/tools/grep.ts +64 -9
- package/src/agent/tools/plan.ts +27 -0
- package/src/agent/tools/question.ts +7 -1
- package/src/agent/tools/read.ts +2 -0
- package/src/agent/types.ts +15 -14
- package/src/components/App.tsx +50 -8
- package/src/components/CustomInput.tsx +461 -77
- package/src/components/Main.tsx +1459 -1112
- package/src/components/Setup.tsx +1 -1
- package/src/components/ShortcutsModal.tsx +11 -8
- package/src/components/Welcome.tsx +1 -1
- package/src/components/main/ApprovalPanel.tsx +4 -3
- package/src/components/main/ChatPage.tsx +858 -516
- package/src/components/main/HomePage.tsx +58 -39
- package/src/components/main/QuestionPanel.tsx +52 -7
- package/src/components/main/ThinkingIndicator.tsx +13 -2
- package/src/components/main/types.ts +11 -10
- package/src/index.tsx +53 -25
- package/src/mcp/approvalPolicy.ts +148 -0
- package/src/mcp/cli/add.ts +185 -0
- package/src/mcp/cli/doctor.ts +77 -0
- package/src/mcp/cli/index.ts +85 -0
- package/src/mcp/cli/list.ts +50 -0
- package/src/mcp/cli/logs.ts +24 -0
- package/src/mcp/cli/manage.ts +99 -0
- package/src/mcp/cli/show.ts +53 -0
- package/src/mcp/cli/tools.ts +77 -0
- package/src/mcp/config.ts +223 -0
- package/src/mcp/index.ts +80 -0
- package/src/mcp/processManager.ts +299 -0
- package/src/mcp/rateLimiter.ts +50 -0
- package/src/mcp/registry.ts +151 -0
- package/src/mcp/schemaConverter.ts +100 -0
- package/src/mcp/servers/navigation.ts +854 -0
- package/src/mcp/toolCatalog.ts +169 -0
- package/src/mcp/types.ts +95 -0
- package/src/utils/approvalBridge.ts +45 -12
- package/src/utils/approvalModeBridge.ts +17 -0
- package/src/utils/commands/approvals.ts +48 -0
- package/src/utils/commands/compact.ts +30 -0
- package/src/utils/commands/echo.ts +1 -1
- package/src/utils/commands/image.ts +109 -0
- package/src/utils/commands/index.ts +9 -7
- package/src/utils/commands/new.ts +15 -0
- package/src/utils/commands/types.ts +3 -0
- package/src/utils/config.ts +3 -1
- package/src/utils/diffRendering.tsx +13 -16
- package/src/utils/exploreBridge.ts +10 -0
- package/src/utils/history.ts +82 -40
- package/src/utils/imageBridge.ts +28 -0
- package/src/utils/images.ts +31 -0
- package/src/utils/markdown.tsx +163 -99
- package/src/utils/models.ts +31 -16
- package/src/utils/notificationBridge.ts +23 -0
- package/src/utils/questionBridge.ts +36 -1
- package/src/utils/tokenEstimator.ts +32 -0
- package/src/utils/toolFormatting.ts +428 -48
- package/src/web/app.tsx +65 -5
- package/src/web/assets/css/ChatPage.css +102 -30
- package/src/web/assets/css/MessageItem.css +26 -29
- package/src/web/assets/css/ThinkingIndicator.css +44 -6
- package/src/web/assets/css/ToolMessage.css +36 -14
- package/src/web/components/ChatPage.tsx +228 -105
- package/src/web/components/HomePage.tsx +3 -3
- package/src/web/components/MessageItem.tsx +80 -81
- package/src/web/components/QuestionPanel.tsx +72 -12
- package/src/web/components/Setup.tsx +1 -1
- package/src/web/components/Sidebar.tsx +1 -3
- package/src/web/components/ThinkingIndicator.tsx +41 -21
- package/src/web/router.ts +1 -1
- package/src/web/server.tsx +894 -662
- package/src/web/storage.ts +23 -1
- package/src/web/types.ts +7 -6
- package/src/utils/commands/redo.ts +0 -74
- package/src/utils/commands/sessions.ts +0 -129
- package/src/utils/commands/undo.ts +0 -75
- package/src/utils/undoRedo.ts +0 -429
- package/src/utils/undoRedoBridge.ts +0 -45
- package/src/utils/undoRedoDb.ts +0 -338
package/LICENSE
CHANGED
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h1 align="center">Mosaic CLI</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>Version 0.0
|
|
8
|
+
<strong>Version 0.73.0</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
@@ -113,9 +113,6 @@ Open http://127.0.0.1:8192 in your browser.
|
|
|
113
113
|
|-------------|--------------------------------------|
|
|
114
114
|
| `/init` | Initialize project context (MOSAIC.md) |
|
|
115
115
|
| `/help` | Show available commands |
|
|
116
|
-
| `/undo` | Undo last file change |
|
|
117
|
-
| `/redo` | Redo undone change |
|
|
118
|
-
| `/sessions` | Manage conversation sessions |
|
|
119
116
|
| `/web` | Open web interface |
|
|
120
117
|
| `/echo` | Echo a message (debug) |
|
|
121
118
|
|
|
@@ -151,7 +148,6 @@ Mosaic relies on a tool registry that exposes safe, focused capabilities to the
|
|
|
151
148
|
|
|
152
149
|
**Safety Features:**
|
|
153
150
|
- Write and edit operations require user approval before execution
|
|
154
|
-
- Built-in undo/redo system tracks all file changes (SQLite-backed)
|
|
155
151
|
- Project context via `MOSAIC.md` helps the agent understand your codebase
|
|
156
152
|
|
|
157
153
|
## AI Providers
|
|
@@ -187,5 +183,5 @@ MIT - see [LICENSE](LICENSE) for details.
|
|
|
187
183
|
---
|
|
188
184
|
|
|
189
185
|
<p align="center">
|
|
190
|
-
Made with Bun
|
|
186
|
+
Made with <a href="https://bun.sh">Bun</a>, <a href="https://react.dev">React</a>, and <a href="https://opentui.com">OpenTUI</a>
|
|
191
187
|
</p>
|
package/package.json
CHANGED
|
@@ -1,48 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@kirosnn/mosaic",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "The open source coding agent.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"mosaic": "bin/mosaic.cjs"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"bin",
|
|
12
|
-
"src",
|
|
13
|
-
"README.md",
|
|
14
|
-
"LICENSE"
|
|
15
|
-
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"dev": "bun run --watch src/index.tsx",
|
|
18
|
-
"mosaic": "bun run src/index.tsx",
|
|
19
|
-
"start": "bun run src/index.tsx"
|
|
20
|
-
},
|
|
21
|
-
"engines": {
|
|
22
|
-
"node": ">=18.0.0"
|
|
23
|
-
},
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"@types/bun": "latest",
|
|
26
|
-
"@types/react": "^19.0.0",
|
|
27
|
-
"@types/react-dom": "^19.2.3"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"@ai-sdk/
|
|
32
|
-
"@ai-sdk/
|
|
33
|
-
"@ai-sdk/
|
|
34
|
-
"@ai-sdk/
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@kirosnn/mosaic",
|
|
3
|
+
"version": "0.73.0",
|
|
4
|
+
"description": "The open source coding agent.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mosaic": "bin/mosaic.cjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "bun run --watch src/index.tsx",
|
|
18
|
+
"mosaic": "bun run src/index.tsx",
|
|
19
|
+
"start": "bun run src/index.tsx"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/bun": "latest",
|
|
26
|
+
"@types/react": "^19.0.0",
|
|
27
|
+
"@types/react-dom": "^19.2.3",
|
|
28
|
+
"@types/turndown": "^5.0.6"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@ai-sdk/anthropic": "^1.0.0",
|
|
32
|
+
"@ai-sdk/google": "^1.0.0",
|
|
33
|
+
"@ai-sdk/mistral": "^1.0.0",
|
|
34
|
+
"@ai-sdk/openai": "^1.0.0",
|
|
35
|
+
"@ai-sdk/xai": "^1.0.0",
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
37
|
+
"@mozilla/readability": "^0.6.0",
|
|
38
|
+
"@opentui/core": "^0.1.69",
|
|
39
|
+
"@opentui/react": "^0.1.69",
|
|
40
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
|
41
|
+
"ai": "^4.0.0",
|
|
42
|
+
"better-sqlite3": "^12.6.0",
|
|
43
|
+
"linkedom": "^0.18.12",
|
|
44
|
+
"ollama": "^0.5.0",
|
|
45
|
+
"playwright": "^1.45.0",
|
|
46
|
+
"react": "^19.2.3",
|
|
47
|
+
"react-dom": "^19.2.3",
|
|
48
|
+
"react-markdown": "^10.1.0",
|
|
49
|
+
"react-syntax-highlighter": "^16.1.0",
|
|
50
|
+
"remark-gfm": "^4.0.1",
|
|
51
|
+
"turndown": "^7.2.2",
|
|
52
|
+
"zod": "^3.23.8",
|
|
53
|
+
"zod-to-json-schema": "^3.25.1"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/agent/Agent.ts
CHANGED
|
@@ -1,131 +1,353 @@
|
|
|
1
|
-
import { CoreMessage
|
|
2
|
-
import {
|
|
3
|
-
AgentEvent,
|
|
4
|
-
AgentMessage,
|
|
5
|
-
ProviderConfig,
|
|
6
|
-
Provider,
|
|
7
|
-
ProviderSendOptions,
|
|
8
|
-
} from './types';
|
|
9
|
-
import { readConfig } from '../utils/config';
|
|
10
|
-
import { DEFAULT_SYSTEM_PROMPT, processSystemPrompt } from './prompts/systemPrompt';
|
|
11
|
-
import { getTools } from './tools/definitions';
|
|
12
|
-
import { AnthropicProvider } from './provider/anthropic';
|
|
13
|
-
import { OpenAIProvider } from './provider/openai';
|
|
14
|
-
import { GoogleProvider } from './provider/google';
|
|
15
|
-
import { MistralProvider } from './provider/mistral';
|
|
16
|
-
import { XaiProvider } from './provider/xai';
|
|
17
|
-
import { OllamaProvider, checkAndStartOllama } from './provider/ollama';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
1
|
+
import { CoreMessage } from 'ai';
|
|
2
|
+
import {
|
|
3
|
+
AgentEvent,
|
|
4
|
+
AgentMessage,
|
|
5
|
+
ProviderConfig,
|
|
6
|
+
Provider,
|
|
7
|
+
ProviderSendOptions,
|
|
8
|
+
} from './types';
|
|
9
|
+
import { readConfig } from '../utils/config';
|
|
10
|
+
import { DEFAULT_SYSTEM_PROMPT, processSystemPrompt } from './prompts/systemPrompt';
|
|
11
|
+
import { getTools } from './tools/definitions';
|
|
12
|
+
import { AnthropicProvider } from './provider/anthropic';
|
|
13
|
+
import { OpenAIProvider } from './provider/openai';
|
|
14
|
+
import { GoogleProvider } from './provider/google';
|
|
15
|
+
import { MistralProvider } from './provider/mistral';
|
|
16
|
+
import { XaiProvider } from './provider/xai';
|
|
17
|
+
import { OllamaProvider, checkAndStartOllama } from './provider/ollama';
|
|
18
|
+
import { getModelsDevContextLimit } from '../utils/models';
|
|
19
|
+
import { estimateTokensFromText, estimateTokensForContent, getDefaultContextBudget } from '../utils/tokenEstimator';
|
|
20
|
+
import { setExploreContext } from '../utils/exploreBridge';
|
|
21
|
+
|
|
22
|
+
function contentToText(content: CoreMessage['content']): string {
|
|
23
|
+
if (typeof content === 'string') return content;
|
|
24
|
+
if (!content) return '';
|
|
25
|
+
|
|
26
|
+
if (Array.isArray(content)) {
|
|
27
|
+
const text = content
|
|
28
|
+
.map((part: any) => {
|
|
29
|
+
if (part && typeof part.text === 'string') return part.text;
|
|
30
|
+
if (typeof part === 'string') return part;
|
|
31
|
+
return '';
|
|
32
|
+
})
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.join('');
|
|
35
|
+
|
|
36
|
+
if (text) return text;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
return JSON.stringify(content);
|
|
41
|
+
} catch {
|
|
42
|
+
return String(content);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function estimateTokensForMessages(messages: CoreMessage[]): number {
|
|
47
|
+
let total = 0;
|
|
48
|
+
for (const message of messages) {
|
|
49
|
+
total += estimateTokensForContent(contentToText(message.content));
|
|
50
|
+
}
|
|
51
|
+
return total;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeWhitespace(text: string): string {
|
|
55
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function truncateText(text: string, maxChars: number): string {
|
|
59
|
+
if (text.length <= maxChars) return text;
|
|
60
|
+
return text.slice(0, Math.max(0, maxChars - 3)) + '...';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function summarizeMessage(message: CoreMessage, isLastUser: boolean): string {
|
|
64
|
+
if (message.role === 'tool') {
|
|
65
|
+
const content: any = message.content;
|
|
66
|
+
const part = Array.isArray(content) ? content[0] : undefined;
|
|
67
|
+
const toolName = part?.toolName ?? part?.tool_name ?? 'tool';
|
|
68
|
+
let resultText = '';
|
|
69
|
+
if (part?.result !== undefined) {
|
|
70
|
+
if (typeof part.result === 'string') resultText = part.result;
|
|
71
|
+
else {
|
|
72
|
+
try {
|
|
73
|
+
resultText = JSON.stringify(part.result);
|
|
74
|
+
} catch {
|
|
75
|
+
resultText = String(part.result);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
resultText = contentToText(message.content);
|
|
80
|
+
}
|
|
81
|
+
const isError = resultText.toLowerCase().includes('error') || resultText.toLowerCase().includes('failed');
|
|
82
|
+
const status = isError ? 'FAILED' : 'OK';
|
|
83
|
+
const cleaned = normalizeWhitespace(resultText);
|
|
84
|
+
return `[tool:${toolName} ${status}] ${truncateText(cleaned, 120)}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (message.role === 'assistant') {
|
|
88
|
+
const text = contentToText(message.content);
|
|
89
|
+
const cleaned = normalizeWhitespace(text);
|
|
90
|
+
const sentenceMatch = cleaned.match(/^[^.!?\n]{10,}[.!?]/);
|
|
91
|
+
const summary = sentenceMatch ? sentenceMatch[0] : cleaned;
|
|
92
|
+
return `assistant: ${truncateText(summary, 200)}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const cleaned = normalizeWhitespace(contentToText(message.content));
|
|
96
|
+
const limit = isLastUser ? cleaned.length : 400;
|
|
97
|
+
return `user: ${truncateText(cleaned, limit)}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildSummary(messages: CoreMessage[], maxTokens: number): string {
|
|
101
|
+
const maxChars = Math.max(0, maxTokens * 3);
|
|
102
|
+
const header = 'CONVERSATION SUMMARY (auto):';
|
|
103
|
+
let charCount = header.length + 1;
|
|
104
|
+
const lines: string[] = [];
|
|
105
|
+
|
|
106
|
+
let lastUserIndex = -1;
|
|
107
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
108
|
+
if (messages[i]!.role === 'user') { lastUserIndex = i; break; }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < messages.length; i++) {
|
|
112
|
+
if (charCount >= maxChars) break;
|
|
113
|
+
const line = `- ${summarizeMessage(messages[i]!, i === lastUserIndex)}`;
|
|
114
|
+
charCount += line.length + 1;
|
|
115
|
+
lines.push(line);
|
|
116
|
+
}
|
|
117
|
+
const body = lines.join('\n');
|
|
118
|
+
const full = `${header}\n${body}`.trim();
|
|
119
|
+
return truncateText(full, maxChars);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function compactMessages(
|
|
123
|
+
messages: CoreMessage[],
|
|
124
|
+
systemPrompt: string,
|
|
125
|
+
maxContextTokens?: number,
|
|
126
|
+
provider?: string
|
|
127
|
+
): CoreMessage[] {
|
|
128
|
+
const budget = maxContextTokens ?? getDefaultContextBudget(provider);
|
|
129
|
+
const systemTokens = estimateTokensFromText(systemPrompt) + 8;
|
|
130
|
+
const messagesTokens = estimateTokensForMessages(messages);
|
|
131
|
+
const total = systemTokens + messagesTokens;
|
|
132
|
+
|
|
133
|
+
if (total <= budget) return messages;
|
|
134
|
+
|
|
135
|
+
const summaryTokens = Math.min(2000, Math.max(400, Math.floor(budget * 0.2)));
|
|
136
|
+
const recentBudget = Math.max(500, budget - summaryTokens);
|
|
137
|
+
|
|
138
|
+
let recentTokens = 0;
|
|
139
|
+
const recent: CoreMessage[] = [];
|
|
140
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
141
|
+
const message = messages[i]!;
|
|
142
|
+
const msgTokens = estimateTokensForContent(contentToText(message.content));
|
|
143
|
+
if (recentTokens + msgTokens > recentBudget && recent.length > 0) break;
|
|
144
|
+
recent.unshift(message);
|
|
145
|
+
recentTokens += msgTokens;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const cutoff = messages.length - recent.length;
|
|
149
|
+
const older = cutoff > 0 ? messages.slice(0, cutoff) : [];
|
|
150
|
+
|
|
151
|
+
if (older.length === 0) return recent;
|
|
152
|
+
|
|
153
|
+
const summary = buildSummary(older, summaryTokens);
|
|
154
|
+
const summaryMessage: CoreMessage = { role: 'assistant', content: summary };
|
|
155
|
+
|
|
156
|
+
return [summaryMessage, ...recent];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildExploreContext(messages: CoreMessage[]): string {
|
|
160
|
+
const parts: string[] = [];
|
|
161
|
+
|
|
162
|
+
const userMessages: string[] = [];
|
|
163
|
+
const recentFiles = new Set<string>();
|
|
164
|
+
|
|
165
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
166
|
+
const msg = messages[i]!;
|
|
167
|
+
|
|
168
|
+
if (msg.role === 'user' && userMessages.length < 2) {
|
|
169
|
+
const text = normalizeWhitespace(contentToText(msg.content));
|
|
170
|
+
if (text) userMessages.unshift(truncateText(text, 300));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (msg.role === 'tool' && recentFiles.size < 15) {
|
|
174
|
+
const content: any = msg.content;
|
|
175
|
+
const part = Array.isArray(content) ? content[0] : undefined;
|
|
176
|
+
const toolName = part?.toolName ?? part?.tool_name;
|
|
177
|
+
if (toolName === 'read' || toolName === 'write' || toolName === 'edit') {
|
|
178
|
+
const args = part?.args ?? part?.input;
|
|
179
|
+
const path = args?.path;
|
|
180
|
+
if (typeof path === 'string') recentFiles.add(path);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (userMessages.length > 0) {
|
|
186
|
+
parts.push(`User intent:\n${userMessages.map(m => `- ${m}`).join('\n')}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (recentFiles.size > 0) {
|
|
190
|
+
parts.push(`Files recently accessed:\n${[...recentFiles].map(f => `- ${f}`).join('\n')}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return parts.join('\n\n');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export class Agent {
|
|
197
|
+
private messageHistory: CoreMessage[] = [];
|
|
198
|
+
private provider: Provider;
|
|
199
|
+
private config: ProviderConfig;
|
|
200
|
+
private static ollamaChecked = false;
|
|
201
|
+
private resolvedMaxContextTokens?: number;
|
|
202
|
+
|
|
203
|
+
static async ensureProviderReady(): Promise<{ ready: boolean; started?: boolean; error?: string }> {
|
|
204
|
+
const userConfig = readConfig();
|
|
205
|
+
|
|
206
|
+
if (userConfig.provider === 'ollama') {
|
|
207
|
+
if (Agent.ollamaChecked) {
|
|
208
|
+
return { ready: true };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const result = await checkAndStartOllama();
|
|
212
|
+
Agent.ollamaChecked = true;
|
|
213
|
+
|
|
214
|
+
if (!result.running) {
|
|
215
|
+
return { ready: false, error: result.error };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { ready: true, started: result.started };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return { ready: true };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
constructor() {
|
|
225
|
+
const userConfig = readConfig();
|
|
226
|
+
|
|
227
|
+
if (!userConfig.provider || !userConfig.model) {
|
|
228
|
+
throw new Error('No provider or model configured. Please run setup first.');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const rawSystemPrompt = userConfig.systemPrompt || DEFAULT_SYSTEM_PROMPT;
|
|
232
|
+
|
|
233
|
+
let mcpToolInfos: Array<{ serverId: string; name: string; description: string; inputSchema: Record<string, unknown>; canonicalId: string; safeId: string }> | undefined;
|
|
234
|
+
try {
|
|
235
|
+
const { getMcpCatalog, isMcpInitialized } = require('../mcp/index');
|
|
236
|
+
if (isMcpInitialized()) {
|
|
237
|
+
mcpToolInfos = getMcpCatalog().getMcpToolInfos();
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
// MCP not available
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const systemPrompt = processSystemPrompt(rawSystemPrompt, true, mcpToolInfos);
|
|
244
|
+
const tools = getTools();
|
|
245
|
+
|
|
246
|
+
this.config = {
|
|
247
|
+
provider: userConfig.provider,
|
|
248
|
+
model: userConfig.model,
|
|
249
|
+
apiKey: userConfig.apiKey,
|
|
250
|
+
systemPrompt,
|
|
251
|
+
tools,
|
|
252
|
+
maxSteps: userConfig.maxSteps ?? 100,
|
|
253
|
+
maxContextTokens: userConfig.maxContextTokens,
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
this.provider = this.createProvider(userConfig.provider);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private createProvider(providerName: string): Provider {
|
|
260
|
+
switch (providerName) {
|
|
261
|
+
case 'openai':
|
|
262
|
+
return new OpenAIProvider();
|
|
263
|
+
case 'anthropic':
|
|
264
|
+
return new AnthropicProvider();
|
|
265
|
+
case 'google':
|
|
266
|
+
return new GoogleProvider();
|
|
267
|
+
case 'mistral':
|
|
268
|
+
return new MistralProvider();
|
|
269
|
+
case 'xai':
|
|
270
|
+
return new XaiProvider();
|
|
271
|
+
case 'ollama':
|
|
272
|
+
return new OllamaProvider();
|
|
273
|
+
default:
|
|
274
|
+
throw new Error(`Unknown provider: ${providerName}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async *sendMessage(userMessage: string, options?: ProviderSendOptions): AsyncGenerator<AgentEvent> {
|
|
279
|
+
this.messageHistory.push({
|
|
280
|
+
role: 'user',
|
|
281
|
+
content: userMessage,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
if (this.resolvedMaxContextTokens === undefined) {
|
|
286
|
+
const resolved = await getModelsDevContextLimit(this.config.provider, this.config.model);
|
|
287
|
+
if (typeof resolved === 'number') {
|
|
288
|
+
this.resolvedMaxContextTokens = resolved;
|
|
289
|
+
if (!this.config.maxContextTokens) {
|
|
290
|
+
this.config = { ...this.config, maxContextTokens: resolved };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const compacted = compactMessages(
|
|
295
|
+
this.messageHistory,
|
|
296
|
+
this.config.systemPrompt,
|
|
297
|
+
this.config.maxContextTokens ?? this.resolvedMaxContextTokens,
|
|
298
|
+
this.config.provider
|
|
299
|
+
);
|
|
300
|
+
setExploreContext(buildExploreContext(this.messageHistory));
|
|
301
|
+
yield* this.provider.sendMessage(compacted, this.config, options);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
yield {
|
|
304
|
+
type: 'error',
|
|
305
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async *streamMessages(messages: AgentMessage[], options?: ProviderSendOptions): AsyncGenerator<AgentEvent> {
|
|
311
|
+
this.messageHistory = messages.map(msg => ({
|
|
312
|
+
role: msg.role,
|
|
313
|
+
content: msg.content,
|
|
314
|
+
})) as CoreMessage[];
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
if (this.resolvedMaxContextTokens === undefined) {
|
|
318
|
+
const resolved = await getModelsDevContextLimit(this.config.provider, this.config.model);
|
|
319
|
+
if (typeof resolved === 'number') {
|
|
320
|
+
this.resolvedMaxContextTokens = resolved;
|
|
321
|
+
if (!this.config.maxContextTokens) {
|
|
322
|
+
this.config = { ...this.config, maxContextTokens: resolved };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const compacted = compactMessages(
|
|
327
|
+
this.messageHistory,
|
|
328
|
+
this.config.systemPrompt,
|
|
329
|
+
this.config.maxContextTokens ?? this.resolvedMaxContextTokens,
|
|
330
|
+
this.config.provider
|
|
331
|
+
);
|
|
332
|
+
setExploreContext(buildExploreContext(this.messageHistory));
|
|
333
|
+
yield* this.provider.sendMessage(compacted, this.config, options);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
yield {
|
|
336
|
+
type: 'error',
|
|
337
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
getHistory(): CoreMessage[] {
|
|
343
|
+
return [...this.messageHistory];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
clearHistory(): void {
|
|
347
|
+
this.messageHistory = [];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
updateConfig(updates: Partial<ProviderConfig>): void {
|
|
351
|
+
this.config = { ...this.config, ...updates };
|
|
352
|
+
}
|
|
353
|
+
}
|
package/src/agent/context.ts
CHANGED
|
@@ -73,9 +73,9 @@ export class AgentContextManager {
|
|
|
73
73
|
return this.currentStep;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
getMaxSteps(): number {
|
|
77
|
-
return this.config.maxSteps ||
|
|
78
|
-
}
|
|
76
|
+
getMaxSteps(): number {
|
|
77
|
+
return this.config.maxSteps || 100;
|
|
78
|
+
}
|
|
79
79
|
|
|
80
80
|
canContinue(): boolean {
|
|
81
81
|
return this.currentStep < this.getMaxSteps();
|
|
@@ -93,4 +93,4 @@ export class AgentContextManager {
|
|
|
93
93
|
getMessageCount(): number {
|
|
94
94
|
return this.messages.length;
|
|
95
95
|
}
|
|
96
|
-
}
|
|
96
|
+
}
|