@geminilight/mindos 0.5.19 → 0.5.21
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/app/app/api/ask/route.ts +308 -172
- package/app/app/api/file/route.ts +35 -11
- package/app/app/api/skills/route.ts +22 -3
- package/app/components/SettingsModal.tsx +52 -58
- package/app/components/Sidebar.tsx +21 -1
- package/app/components/settings/AiTab.tsx +4 -25
- package/app/components/settings/AppearanceTab.tsx +31 -13
- package/app/components/settings/KnowledgeTab.tsx +13 -28
- package/app/components/settings/McpAgentInstall.tsx +227 -0
- package/app/components/settings/McpServerStatus.tsx +172 -0
- package/app/components/settings/McpSkillsSection.tsx +583 -0
- package/app/components/settings/McpTab.tsx +16 -728
- package/app/components/settings/PluginsTab.tsx +4 -27
- package/app/components/settings/Primitives.tsx +69 -0
- package/app/components/settings/ShortcutsTab.tsx +2 -4
- package/app/components/settings/SyncTab.tsx +8 -24
- package/app/components/settings/types.ts +116 -2
- package/app/lib/agent/context.ts +151 -87
- package/app/lib/agent/index.ts +4 -3
- package/app/lib/agent/model.ts +76 -10
- package/app/lib/agent/stream-consumer.ts +73 -77
- package/app/lib/agent/to-agent-messages.ts +106 -0
- package/app/lib/agent/tools.ts +260 -266
- package/app/lib/i18n-en.ts +480 -0
- package/app/lib/i18n-zh.ts +505 -0
- package/app/lib/i18n.ts +4 -947
- package/app/next-env.d.ts +1 -1
- package/app/next.config.ts +7 -0
- package/app/package-lock.json +3258 -3093
- package/app/package.json +6 -3
- package/bin/cli.js +140 -5
- package/package.json +4 -1
- package/scripts/setup.js +13 -0
- package/skills/mindos/SKILL.md +10 -168
- package/skills/mindos-zh/SKILL.md +14 -172
- package/templates/skill-rules/en/skill-rules.md +222 -0
- package/templates/skill-rules/en/user-rules.md +20 -0
- package/templates/skill-rules/zh/skill-rules.md +222 -0
- package/templates/skill-rules/zh/user-rules.md +20 -0
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import type { Message, MessagePart, ToolCallPart, TextPart, ReasoningPart } from '@/lib/types';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Parse
|
|
5
|
-
*
|
|
2
|
+
* Parse MindOS SSE stream (6 event types) into structured Message parts.
|
|
3
|
+
*
|
|
4
|
+
* MindOS SSE format (backend: route.ts):
|
|
5
|
+
* - text_delta: { type, delta }
|
|
6
|
+
* - thinking_delta: { type, delta } (Anthropic extended thinking)
|
|
7
|
+
* - tool_start: { type, toolCallId, toolName, args }
|
|
8
|
+
* - tool_end: { type, toolCallId, output, isError }
|
|
9
|
+
* - done: { type, usage? }
|
|
10
|
+
* - error: { type, message }
|
|
11
|
+
*
|
|
12
|
+
* Frontend Message structure:
|
|
13
|
+
* - role: 'assistant'
|
|
14
|
+
* - content: concatenated text deltas (for display)
|
|
15
|
+
* - parts: structured [TextPart | ReasoningPart | ToolCallPart] (for detailed view)
|
|
6
16
|
*/
|
|
17
|
+
import type { Message, MessagePart, ToolCallPart, TextPart, ReasoningPart } from '@/lib/types';
|
|
18
|
+
|
|
7
19
|
export async function consumeUIMessageStream(
|
|
8
20
|
body: ReadableStream<Uint8Array>,
|
|
9
21
|
onUpdate: (message: Message) => void,
|
|
@@ -13,18 +25,19 @@ export async function consumeUIMessageStream(
|
|
|
13
25
|
const decoder = new TextDecoder();
|
|
14
26
|
let buffer = '';
|
|
15
27
|
|
|
16
|
-
// Mutable working copies
|
|
28
|
+
// Mutable working copies
|
|
17
29
|
const parts: MessagePart[] = [];
|
|
18
30
|
const toolCalls = new Map<string, ToolCallPart>();
|
|
19
31
|
let currentTextId: string | null = null;
|
|
20
32
|
let currentReasoningPart: ReasoningPart | null = null;
|
|
21
33
|
|
|
22
|
-
/**
|
|
34
|
+
/** Build an immutable Message snapshot from current parts */
|
|
23
35
|
function buildMessage(): Message {
|
|
24
36
|
const clonedParts: MessagePart[] = parts.map(p => {
|
|
25
37
|
if (p.type === 'text') return { type: 'text' as const, text: p.text };
|
|
26
38
|
if (p.type === 'reasoning') return { type: 'reasoning' as const, text: p.text };
|
|
27
|
-
|
|
39
|
+
// ToolCallPart — shallow copy safe (primitive fields, input is replaced not mutated)
|
|
40
|
+
return { ...p };
|
|
28
41
|
});
|
|
29
42
|
const textContent = clonedParts
|
|
30
43
|
.filter((p): p is TextPart => p.type === 'text')
|
|
@@ -37,6 +50,7 @@ export async function consumeUIMessageStream(
|
|
|
37
50
|
};
|
|
38
51
|
}
|
|
39
52
|
|
|
53
|
+
/** Get or create the last text part with given ID */
|
|
40
54
|
function findOrCreateTextPart(id: string): TextPart {
|
|
41
55
|
if (currentTextId === id) {
|
|
42
56
|
const last = parts[parts.length - 1];
|
|
@@ -48,6 +62,7 @@ export async function consumeUIMessageStream(
|
|
|
48
62
|
return part;
|
|
49
63
|
}
|
|
50
64
|
|
|
65
|
+
/** Get or create a tool call part */
|
|
51
66
|
function findOrCreateToolCall(toolCallId: string, toolName?: string): ToolCallPart {
|
|
52
67
|
let tc = toolCalls.get(toolCallId);
|
|
53
68
|
if (!tc) {
|
|
@@ -60,7 +75,7 @@ export async function consumeUIMessageStream(
|
|
|
60
75
|
};
|
|
61
76
|
toolCalls.set(toolCallId, tc);
|
|
62
77
|
parts.push(tc);
|
|
63
|
-
currentTextId = null;
|
|
78
|
+
currentTextId = null;
|
|
64
79
|
}
|
|
65
80
|
return tc;
|
|
66
81
|
}
|
|
@@ -82,112 +97,93 @@ export async function consumeUIMessageStream(
|
|
|
82
97
|
for (const line of lines) {
|
|
83
98
|
const trimmed = line.trim();
|
|
84
99
|
|
|
85
|
-
// SSE format:
|
|
86
|
-
// Also handle standard "data:{json}" for robustness
|
|
100
|
+
// Standard SSE format: "data:{json}"
|
|
87
101
|
let jsonStr: string | null = null;
|
|
88
|
-
if (trimmed.startsWith('
|
|
89
|
-
jsonStr = trimmed.slice(2);
|
|
90
|
-
} else if (trimmed.startsWith('data:')) {
|
|
102
|
+
if (trimmed.startsWith('data:')) {
|
|
91
103
|
jsonStr = trimmed.slice(5).trim();
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
if (!jsonStr) continue;
|
|
95
107
|
|
|
96
|
-
let
|
|
108
|
+
let event: Record<string, unknown>;
|
|
97
109
|
try {
|
|
98
|
-
|
|
110
|
+
event = JSON.parse(jsonStr);
|
|
99
111
|
} catch {
|
|
100
|
-
continue; // skip malformed
|
|
112
|
+
continue; // skip malformed
|
|
101
113
|
}
|
|
102
114
|
|
|
103
|
-
const type =
|
|
115
|
+
const type = event.type as string;
|
|
104
116
|
|
|
105
117
|
switch (type) {
|
|
106
|
-
case '
|
|
107
|
-
|
|
118
|
+
case 'text_delta': {
|
|
119
|
+
// Regular text from assistant
|
|
120
|
+
const part = findOrCreateTextPart('text');
|
|
121
|
+
part.text += (event.delta as string) ?? '';
|
|
108
122
|
changed = true;
|
|
109
123
|
break;
|
|
110
124
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
case 'tool-input-start': {
|
|
122
|
-
const tc = findOrCreateToolCall(chunk.toolCallId as string, chunk.toolName as string);
|
|
123
|
-
tc.state = 'running';
|
|
125
|
+
|
|
126
|
+
case 'thinking_delta': {
|
|
127
|
+
// Extended thinking (Anthropic)
|
|
128
|
+
if (!currentReasoningPart) {
|
|
129
|
+
currentReasoningPart = { type: 'reasoning', text: '' };
|
|
130
|
+
parts.push(currentReasoningPart);
|
|
131
|
+
currentTextId = null;
|
|
132
|
+
}
|
|
133
|
+
currentReasoningPart.text += (event.delta as string) ?? '';
|
|
124
134
|
changed = true;
|
|
125
135
|
break;
|
|
126
136
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const tc = findOrCreateToolCall(
|
|
133
|
-
tc.input =
|
|
137
|
+
|
|
138
|
+
case 'tool_start': {
|
|
139
|
+
// Beginning of tool execution
|
|
140
|
+
const toolCallId = event.toolCallId as string;
|
|
141
|
+
const toolName = event.toolName as string;
|
|
142
|
+
const tc = findOrCreateToolCall(toolCallId, toolName);
|
|
143
|
+
tc.input = event.args;
|
|
134
144
|
tc.state = 'running';
|
|
135
145
|
changed = true;
|
|
136
146
|
break;
|
|
137
147
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
: '';
|
|
144
|
-
tc.state = 'done';
|
|
145
|
-
changed = true;
|
|
146
|
-
}
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
case 'tool-output-error':
|
|
150
|
-
case 'tool-input-error': {
|
|
151
|
-
const tc = toolCalls.get(chunk.toolCallId as string);
|
|
148
|
+
|
|
149
|
+
case 'tool_end': {
|
|
150
|
+
// Tool execution finished
|
|
151
|
+
const toolCallId = event.toolCallId as string;
|
|
152
|
+
const tc = toolCalls.get(toolCallId);
|
|
152
153
|
if (tc) {
|
|
153
|
-
|
|
154
|
-
tc.
|
|
154
|
+
const output = event.output as string;
|
|
155
|
+
tc.output = output ?? '';
|
|
156
|
+
tc.state = (event.isError ? 'error' : 'done');
|
|
155
157
|
changed = true;
|
|
156
158
|
}
|
|
157
159
|
break;
|
|
158
160
|
}
|
|
161
|
+
|
|
159
162
|
case 'error': {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
// step-start, metadata, finish — ignored for now
|
|
167
|
-
case 'reasoning-start': {
|
|
168
|
-
currentReasoningPart = { type: 'reasoning', text: '' };
|
|
169
|
-
parts.push(currentReasoningPart);
|
|
163
|
+
// Stream error
|
|
164
|
+
const message = event.message as string;
|
|
165
|
+
parts.push({
|
|
166
|
+
type: 'text',
|
|
167
|
+
text: `\n\n**Stream Error:** ${message}`,
|
|
168
|
+
});
|
|
170
169
|
currentTextId = null;
|
|
171
170
|
changed = true;
|
|
172
171
|
break;
|
|
173
172
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
case 'reasoning-end': {
|
|
182
|
-
currentReasoningPart = null;
|
|
173
|
+
|
|
174
|
+
case 'done': {
|
|
175
|
+
// Stream completed cleanly — usage data is optional
|
|
176
|
+
// No state change needed; just marks end of SSE stream
|
|
183
177
|
break;
|
|
184
178
|
}
|
|
179
|
+
|
|
185
180
|
default:
|
|
181
|
+
// Ignore unknown event types
|
|
186
182
|
break;
|
|
187
183
|
}
|
|
188
184
|
}
|
|
189
185
|
|
|
190
|
-
// Emit once per reader
|
|
186
|
+
// Emit once per reader batch, not per SSE line
|
|
191
187
|
if (changed) {
|
|
192
188
|
onUpdate(buildMessage());
|
|
193
189
|
}
|
|
@@ -196,8 +192,8 @@ export async function consumeUIMessageStream(
|
|
|
196
192
|
reader.releaseLock();
|
|
197
193
|
}
|
|
198
194
|
|
|
199
|
-
// Finalize any tool calls still
|
|
200
|
-
// (stream ended
|
|
195
|
+
// Finalize any tool calls still in running/pending state
|
|
196
|
+
// (stream ended unexpectedly — abort, network error, step limit)
|
|
201
197
|
let finalized = false;
|
|
202
198
|
for (const tc of toolCalls.values()) {
|
|
203
199
|
if (tc.state === 'running' || tc.state === 'pending') {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert frontend Message[] (with parts containing tool calls + results)
|
|
3
|
+
* into pi-agent-core AgentMessage[] that Agent expects.
|
|
4
|
+
*
|
|
5
|
+
* This is "Layer 1" of the two-layer conversion:
|
|
6
|
+
* Frontend Message[] → AgentMessage[] (this file)
|
|
7
|
+
* AgentMessage[] → pi-ai Message[] (handled by Agent's convertToLlm internally)
|
|
8
|
+
*
|
|
9
|
+
* Key responsibilities:
|
|
10
|
+
* - User messages: wrap as { role: 'user', content, timestamp }
|
|
11
|
+
* - Assistant messages: convert parts into { role: 'assistant', content: [...] }
|
|
12
|
+
* - Tool results: emit separate { role: 'toolResult', ... } per tool call
|
|
13
|
+
* - Orphaned tool calls (running/pending from interrupted streams): supply empty result
|
|
14
|
+
* - Reasoning parts: filtered out (display-only, not sent back to LLM)
|
|
15
|
+
*/
|
|
16
|
+
import type { Message as FrontendMessage, ToolCallPart as FrontendToolCallPart } from '@/lib/types';
|
|
17
|
+
import type { AgentMessage } from '@mariozechner/pi-agent-core';
|
|
18
|
+
import type { UserMessage, AssistantMessage, ToolResultMessage } from '@mariozechner/pi-ai';
|
|
19
|
+
|
|
20
|
+
// Re-export for convenience
|
|
21
|
+
export type { AgentMessage } from '@mariozechner/pi-agent-core';
|
|
22
|
+
|
|
23
|
+
export function toAgentMessages(messages: FrontendMessage[]): AgentMessage[] {
|
|
24
|
+
const result: AgentMessage[] = [];
|
|
25
|
+
|
|
26
|
+
for (const msg of messages) {
|
|
27
|
+
if (msg.role === 'user') {
|
|
28
|
+
result.push({
|
|
29
|
+
role: 'user',
|
|
30
|
+
content: msg.content,
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
} satisfies UserMessage as AgentMessage);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Skip error placeholder messages from frontend
|
|
37
|
+
if (msg.content.startsWith('__error__')) continue;
|
|
38
|
+
|
|
39
|
+
// Assistant message
|
|
40
|
+
if (!msg.parts || msg.parts.length === 0) {
|
|
41
|
+
// Plain text assistant message — no tool calls
|
|
42
|
+
if (msg.content) {
|
|
43
|
+
result.push({
|
|
44
|
+
role: 'assistant',
|
|
45
|
+
content: [{ type: 'text', text: msg.content }],
|
|
46
|
+
// Minimal required fields for historical messages
|
|
47
|
+
api: 'anthropic-messages' as any,
|
|
48
|
+
provider: 'anthropic' as any,
|
|
49
|
+
model: '',
|
|
50
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
|
|
51
|
+
stopReason: 'stop',
|
|
52
|
+
timestamp: Date.now(),
|
|
53
|
+
} satisfies AssistantMessage as AgentMessage);
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Build assistant content array (text + tool calls)
|
|
59
|
+
const assistantContent: AssistantMessage['content'] = [];
|
|
60
|
+
const toolCalls: FrontendToolCallPart[] = [];
|
|
61
|
+
|
|
62
|
+
for (const part of msg.parts) {
|
|
63
|
+
if (part.type === 'text') {
|
|
64
|
+
if (part.text) {
|
|
65
|
+
assistantContent.push({ type: 'text', text: part.text });
|
|
66
|
+
}
|
|
67
|
+
} else if (part.type === 'tool-call') {
|
|
68
|
+
assistantContent.push({
|
|
69
|
+
type: 'toolCall' as any,
|
|
70
|
+
id: part.toolCallId,
|
|
71
|
+
name: part.toolName,
|
|
72
|
+
arguments: part.input ?? {},
|
|
73
|
+
});
|
|
74
|
+
toolCalls.push(part);
|
|
75
|
+
}
|
|
76
|
+
// 'reasoning' parts are display-only; not sent back to model
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (assistantContent.length > 0) {
|
|
80
|
+
result.push({
|
|
81
|
+
role: 'assistant',
|
|
82
|
+
content: assistantContent,
|
|
83
|
+
api: 'anthropic-messages' as any,
|
|
84
|
+
provider: 'anthropic' as any,
|
|
85
|
+
model: '',
|
|
86
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
|
|
87
|
+
stopReason: toolCalls.length > 0 ? 'toolUse' : 'stop',
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
} satisfies AssistantMessage as AgentMessage);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Emit tool result messages for each tool call
|
|
93
|
+
for (const tc of toolCalls) {
|
|
94
|
+
result.push({
|
|
95
|
+
role: 'toolResult',
|
|
96
|
+
toolCallId: tc.toolCallId,
|
|
97
|
+
toolName: tc.toolName,
|
|
98
|
+
content: [{ type: 'text', text: tc.output ?? '' }],
|
|
99
|
+
isError: tc.state === 'error',
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
} satisfies ToolResultMessage as AgentMessage);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|