@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.
Files changed (99) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -6
  3. package/package.json +55 -48
  4. package/src/agent/Agent.ts +353 -131
  5. package/src/agent/context.ts +4 -4
  6. package/src/agent/prompts/systemPrompt.ts +209 -70
  7. package/src/agent/prompts/toolsPrompt.ts +285 -138
  8. package/src/agent/provider/anthropic.ts +109 -105
  9. package/src/agent/provider/google.ts +111 -107
  10. package/src/agent/provider/mistral.ts +95 -95
  11. package/src/agent/provider/ollama.ts +73 -17
  12. package/src/agent/provider/openai.ts +146 -102
  13. package/src/agent/provider/rateLimit.ts +178 -0
  14. package/src/agent/provider/reasoning.ts +29 -0
  15. package/src/agent/provider/xai.ts +108 -104
  16. package/src/agent/tools/definitions.ts +15 -1
  17. package/src/agent/tools/executor.ts +717 -98
  18. package/src/agent/tools/exploreExecutor.ts +20 -22
  19. package/src/agent/tools/fetch.ts +58 -0
  20. package/src/agent/tools/glob.ts +20 -4
  21. package/src/agent/tools/grep.ts +64 -9
  22. package/src/agent/tools/plan.ts +27 -0
  23. package/src/agent/tools/question.ts +7 -1
  24. package/src/agent/tools/read.ts +2 -0
  25. package/src/agent/types.ts +15 -14
  26. package/src/components/App.tsx +50 -8
  27. package/src/components/CustomInput.tsx +461 -77
  28. package/src/components/Main.tsx +1459 -1112
  29. package/src/components/Setup.tsx +1 -1
  30. package/src/components/ShortcutsModal.tsx +11 -8
  31. package/src/components/Welcome.tsx +1 -1
  32. package/src/components/main/ApprovalPanel.tsx +4 -3
  33. package/src/components/main/ChatPage.tsx +858 -516
  34. package/src/components/main/HomePage.tsx +58 -39
  35. package/src/components/main/QuestionPanel.tsx +52 -7
  36. package/src/components/main/ThinkingIndicator.tsx +13 -2
  37. package/src/components/main/types.ts +11 -10
  38. package/src/index.tsx +53 -25
  39. package/src/mcp/approvalPolicy.ts +148 -0
  40. package/src/mcp/cli/add.ts +185 -0
  41. package/src/mcp/cli/doctor.ts +77 -0
  42. package/src/mcp/cli/index.ts +85 -0
  43. package/src/mcp/cli/list.ts +50 -0
  44. package/src/mcp/cli/logs.ts +24 -0
  45. package/src/mcp/cli/manage.ts +99 -0
  46. package/src/mcp/cli/show.ts +53 -0
  47. package/src/mcp/cli/tools.ts +77 -0
  48. package/src/mcp/config.ts +223 -0
  49. package/src/mcp/index.ts +80 -0
  50. package/src/mcp/processManager.ts +299 -0
  51. package/src/mcp/rateLimiter.ts +50 -0
  52. package/src/mcp/registry.ts +151 -0
  53. package/src/mcp/schemaConverter.ts +100 -0
  54. package/src/mcp/servers/navigation.ts +854 -0
  55. package/src/mcp/toolCatalog.ts +169 -0
  56. package/src/mcp/types.ts +95 -0
  57. package/src/utils/approvalBridge.ts +45 -12
  58. package/src/utils/approvalModeBridge.ts +17 -0
  59. package/src/utils/commands/approvals.ts +48 -0
  60. package/src/utils/commands/compact.ts +30 -0
  61. package/src/utils/commands/echo.ts +1 -1
  62. package/src/utils/commands/image.ts +109 -0
  63. package/src/utils/commands/index.ts +9 -7
  64. package/src/utils/commands/new.ts +15 -0
  65. package/src/utils/commands/types.ts +3 -0
  66. package/src/utils/config.ts +3 -1
  67. package/src/utils/diffRendering.tsx +13 -16
  68. package/src/utils/exploreBridge.ts +10 -0
  69. package/src/utils/history.ts +82 -40
  70. package/src/utils/imageBridge.ts +28 -0
  71. package/src/utils/images.ts +31 -0
  72. package/src/utils/markdown.tsx +163 -99
  73. package/src/utils/models.ts +31 -16
  74. package/src/utils/notificationBridge.ts +23 -0
  75. package/src/utils/questionBridge.ts +36 -1
  76. package/src/utils/tokenEstimator.ts +32 -0
  77. package/src/utils/toolFormatting.ts +428 -48
  78. package/src/web/app.tsx +65 -5
  79. package/src/web/assets/css/ChatPage.css +102 -30
  80. package/src/web/assets/css/MessageItem.css +26 -29
  81. package/src/web/assets/css/ThinkingIndicator.css +44 -6
  82. package/src/web/assets/css/ToolMessage.css +36 -14
  83. package/src/web/components/ChatPage.tsx +228 -105
  84. package/src/web/components/HomePage.tsx +3 -3
  85. package/src/web/components/MessageItem.tsx +80 -81
  86. package/src/web/components/QuestionPanel.tsx +72 -12
  87. package/src/web/components/Setup.tsx +1 -1
  88. package/src/web/components/Sidebar.tsx +1 -3
  89. package/src/web/components/ThinkingIndicator.tsx +41 -21
  90. package/src/web/router.ts +1 -1
  91. package/src/web/server.tsx +894 -662
  92. package/src/web/storage.ts +23 -1
  93. package/src/web/types.ts +7 -6
  94. package/src/utils/commands/redo.ts +0 -74
  95. package/src/utils/commands/sessions.ts +0 -129
  96. package/src/utils/commands/undo.ts +0 -75
  97. package/src/utils/undoRedo.ts +0 -429
  98. package/src/utils/undoRedoBridge.ts +0 -45
  99. package/src/utils/undoRedoDb.ts +0 -338
@@ -0,0 +1,178 @@
1
+ export type RetryDecision = {
2
+ shouldRetry: boolean;
3
+ retryAfterMs?: number;
4
+ };
5
+
6
+ export type RetryOptions = {
7
+ maxRetries: number;
8
+ baseDelayMs: number;
9
+ maxDelayMs: number;
10
+ jitterMs: number;
11
+ abortSignal?: AbortSignal;
12
+ };
13
+
14
+ const DEFAULT_OPTIONS: RetryOptions = {
15
+ maxRetries: 4,
16
+ baseDelayMs: 800,
17
+ maxDelayMs: 12000,
18
+ jitterMs: 250,
19
+ };
20
+
21
+ function toErrorMessage(error: unknown): string {
22
+ if (error instanceof Error) return error.message;
23
+ if (typeof error === 'string') return error;
24
+ try {
25
+ return JSON.stringify(error);
26
+ } catch {
27
+ return 'Unknown error';
28
+ }
29
+ }
30
+
31
+ export function normalizeError(error: unknown): Error {
32
+ if (error instanceof Error) return error;
33
+ return new Error(toErrorMessage(error));
34
+ }
35
+
36
+ function getStatus(error: unknown): number | undefined {
37
+ const e: any = error as any;
38
+ const direct = e?.status ?? e?.statusCode ?? e?.code;
39
+ if (typeof direct === 'number') return direct;
40
+ const nested = e?.response?.status ?? e?.response?.statusCode;
41
+ if (typeof nested === 'number') return nested;
42
+ const inner = e?.error?.status ?? e?.error?.statusCode;
43
+ if (typeof inner === 'number') return inner;
44
+ return undefined;
45
+ }
46
+
47
+ export function getErrorSignature(error: unknown): string {
48
+ const status = getStatus(error);
49
+ const code = (error as any)?.code;
50
+ const message = toErrorMessage(error);
51
+ return `${status ?? ''}|${code ?? ''}|${message}`;
52
+ }
53
+
54
+ function getHeader(error: unknown, name: string): string | undefined {
55
+ const e: any = error as any;
56
+ const headers = e?.response?.headers ?? e?.headers ?? e?.responseHeaders;
57
+ if (!headers) return undefined;
58
+ const lower = name.toLowerCase();
59
+ if (typeof headers.get === 'function') {
60
+ const value = headers.get(name) ?? headers.get(lower);
61
+ return value ? String(value) : undefined;
62
+ }
63
+ if (typeof headers === 'object') {
64
+ const value = headers[name] ?? headers[lower];
65
+ return value ? String(value) : undefined;
66
+ }
67
+ return undefined;
68
+ }
69
+
70
+ function parseRetryAfter(value: string | undefined): number | undefined {
71
+ if (!value) return undefined;
72
+ const trimmed = value.trim();
73
+ if (!trimmed) return undefined;
74
+ const seconds = Number(trimmed);
75
+ if (!Number.isNaN(seconds)) {
76
+ return Math.max(0, Math.round(seconds * 1000));
77
+ }
78
+ const dateMs = Date.parse(trimmed);
79
+ if (!Number.isNaN(dateMs)) {
80
+ const delta = dateMs - Date.now();
81
+ return delta > 0 ? delta : 0;
82
+ }
83
+ return undefined;
84
+ }
85
+
86
+ function isRetryableMessage(message: string): boolean {
87
+ const m = message.toLowerCase();
88
+ if (m.includes('rate limit')) return true;
89
+ if (m.includes('too many requests')) return true;
90
+ if (m.includes('quota')) return true;
91
+ if (m.includes('throttle')) return true;
92
+ if (m.includes('429')) return true;
93
+ if (m.includes('timeout')) return true;
94
+ if (m.includes('timed out')) return true;
95
+ if (m.includes('econnreset')) return true;
96
+ if (m.includes('econnrefused')) return true;
97
+ if (m.includes('etimedout')) return true;
98
+ if (m.includes('enotfound')) return true;
99
+ if (m.includes('fetch failed')) return true;
100
+ if (m.includes('socket hang up')) return true;
101
+ return false;
102
+ }
103
+
104
+ export function getRetryDecision(error: unknown): RetryDecision {
105
+ const status = getStatus(error);
106
+ if (status === 429) {
107
+ return { shouldRetry: true, retryAfterMs: parseRetryAfter(getHeader(error, 'retry-after')) };
108
+ }
109
+ if (status === 408 || (status !== undefined && status >= 500 && status < 600)) {
110
+ return { shouldRetry: true };
111
+ }
112
+ const message = toErrorMessage(error);
113
+ if (isRetryableMessage(message)) {
114
+ return { shouldRetry: true, retryAfterMs: parseRetryAfter(getHeader(error, 'retry-after')) };
115
+ }
116
+ return { shouldRetry: false };
117
+ }
118
+
119
+ function computeDelayMs(attempt: number, options: RetryOptions, retryAfterMs?: number): number {
120
+ if (retryAfterMs !== undefined && retryAfterMs >= 0) {
121
+ return Math.min(options.maxDelayMs, retryAfterMs);
122
+ }
123
+ const jitter = Math.floor(Math.random() * options.jitterMs);
124
+ const backoff = options.baseDelayMs * Math.pow(2, attempt);
125
+ return Math.min(options.maxDelayMs, backoff + jitter);
126
+ }
127
+
128
+ async function sleep(ms: number, abortSignal?: AbortSignal): Promise<void> {
129
+ if (ms <= 0) return;
130
+ if (abortSignal?.aborted) return;
131
+ await new Promise<void>((resolve) => {
132
+ const timer = setTimeout(() => resolve(), ms);
133
+ const onAbort = () => {
134
+ clearTimeout(timer);
135
+ resolve();
136
+ };
137
+ if (abortSignal) {
138
+ abortSignal.addEventListener('abort', onAbort, { once: true });
139
+ }
140
+ });
141
+ }
142
+
143
+ export async function* runWithRetry<T>(
144
+ run: () => AsyncGenerator<T>,
145
+ options: Partial<RetryOptions> = {}
146
+ ): AsyncGenerator<T> {
147
+ const config: RetryOptions = { ...DEFAULT_OPTIONS, ...options };
148
+ let attempt = 0;
149
+ let lastSignature: string | null = null;
150
+ let sameSignatureCount = 0;
151
+
152
+ while (true) {
153
+ if (config.abortSignal?.aborted) return;
154
+ try {
155
+ yield* run();
156
+ return;
157
+ } catch (error) {
158
+ if (config.abortSignal?.aborted) return;
159
+ const decision = getRetryDecision(error);
160
+ const signature = getErrorSignature(error);
161
+ if (signature === lastSignature) {
162
+ sameSignatureCount += 1;
163
+ } else {
164
+ lastSignature = signature;
165
+ sameSignatureCount = 0;
166
+ }
167
+ if (!decision.shouldRetry || attempt >= config.maxRetries) {
168
+ throw error;
169
+ }
170
+ if (sameSignatureCount >= 1) {
171
+ throw error;
172
+ }
173
+ const delay = computeDelayMs(attempt, config, decision.retryAfterMs);
174
+ await sleep(delay, config.abortSignal);
175
+ attempt += 1;
176
+ }
177
+ }
178
+ }
@@ -0,0 +1,29 @@
1
+ import { findModelsDevModelById, getModelsDevModel } from '../../utils/models';
2
+
3
+ function normalizeId(value: string): string {
4
+ return value.trim();
5
+ }
6
+
7
+ function matchesReasoningHeuristic(modelId: string): boolean {
8
+ const id = modelId.toLowerCase();
9
+ return id.includes('reasoning') || id.includes('o1') || id.includes('o3') || id.includes('r1');
10
+ }
11
+
12
+ export async function shouldEnableReasoning(providerId: string, modelId: string): Promise<boolean> {
13
+ const provider = normalizeId(providerId);
14
+ const model = normalizeId(modelId);
15
+
16
+ try {
17
+ const direct = await getModelsDevModel(provider, model);
18
+ if (direct && typeof direct.reasoning === 'boolean') return direct.reasoning;
19
+ } catch {
20
+ }
21
+
22
+ try {
23
+ const byId = await findModelsDevModelById(model);
24
+ if (byId?.model && typeof byId.model.reasoning === 'boolean') return byId.model.reasoning;
25
+ } catch {
26
+ }
27
+
28
+ return matchesReasoningHeuristic(model);
29
+ }
@@ -1,6 +1,8 @@
1
- import { streamText, CoreMessage } from 'ai';
2
- import { createXai } from '@ai-sdk/xai';
3
- import { AgentEvent, Provider, ProviderConfig, ProviderSendOptions } from '../types';
1
+ import { streamText, CoreMessage } from 'ai';
2
+ import { createXai } from '@ai-sdk/xai';
3
+ import { AgentEvent, Provider, ProviderConfig, ProviderSendOptions } from '../types';
4
+ import { shouldEnableReasoning } from './reasoning';
5
+ import { getRetryDecision, normalizeError, runWithRetry } from './rateLimit';
4
6
 
5
7
  export class XaiProvider implements Provider {
6
8
  async *sendMessage(
@@ -8,115 +10,117 @@ export class XaiProvider implements Provider {
8
10
  config: ProviderConfig,
9
11
  options?: ProviderSendOptions
10
12
  ): AsyncGenerator<AgentEvent> {
11
- const cleanApiKey = config.apiKey?.trim().replace(/[\r\n]+/g, '');
12
- const cleanModel = config.model.trim().replace(/[\r\n]+/g, '');
13
+ const cleanApiKey = config.apiKey?.trim().replace(/[\r\n]+/g, '');
14
+ const cleanModel = config.model.trim().replace(/[\r\n]+/g, '');
15
+ const reasoningEnabled = await shouldEnableReasoning(config.provider, cleanModel);
13
16
 
14
17
  const xai = createXai({
15
18
  apiKey: cleanApiKey,
16
19
  });
17
20
 
18
- const result = streamText({
19
- model: xai(cleanModel),
20
- messages: messages,
21
- system: config.systemPrompt,
22
- tools: config.tools,
23
- maxSteps: config.maxSteps || 10,
24
- abortSignal: options?.abortSignal,
25
- providerOptions: {
26
- xai: {
27
- reasoningEffort: 'high',
28
- },
29
- },
30
- });
31
-
32
- try {
33
- let stepCounter = 0;
34
-
35
- for await (const chunk of result.fullStream as any) {
36
- const c: any = chunk;
37
- switch (c.type) {
38
- case 'reasoning':
39
- if (c.textDelta) {
40
- yield {
41
- type: 'reasoning-delta',
42
- content: c.textDelta,
43
- };
44
- }
45
- break;
46
-
47
- case 'text-delta':
48
- yield {
49
- type: 'text-delta',
50
- content: c.textDelta,
51
- };
52
- break;
53
-
54
- case 'step-start':
55
- yield {
56
- type: 'step-start',
57
- stepNumber: typeof c.stepIndex === 'number' ? c.stepIndex : stepCounter,
58
- };
59
- stepCounter++;
60
- break;
61
-
62
- case 'step-finish':
63
- yield {
64
- type: 'step-finish',
65
- stepNumber:
66
- typeof c.stepIndex === 'number' ? c.stepIndex : Math.max(0, stepCounter - 1),
67
- finishReason: String(c.finishReason ?? 'stop'),
68
- };
69
- break;
70
-
71
- case 'tool-call':
72
- yield {
73
- type: 'tool-call-end',
74
- toolCallId: String(c.toolCallId ?? ''),
75
- toolName: String(c.toolName ?? ''),
76
- args: (c.args ?? {}) as Record<string, unknown>,
77
- };
78
- break;
79
-
80
- case 'tool-result':
81
- yield {
82
- type: 'tool-result',
83
- toolCallId: String(c.toolCallId ?? ''),
84
- toolName: String(c.toolName ?? ''),
85
- result: c.result,
86
- };
87
- break;
88
-
89
- case 'finish':
90
- yield {
91
- type: 'finish',
92
- finishReason: String(c.finishReason ?? 'stop'),
93
- usage: c.usage,
94
- };
95
- break;
96
-
97
- case 'error':
98
- {
99
- const err = c.error;
100
- const msg =
101
- err instanceof Error
102
- ? err.message
103
- : typeof err === 'string'
104
- ? err
105
- : 'Unknown error';
106
- yield {
107
- type: 'error',
108
- error: msg,
109
- };
110
- }
111
- break;
112
- }
113
- }
114
- } catch (error) {
115
- if (options?.abortSignal?.aborted) return;
21
+ try {
22
+ let stepCounter = 0;
23
+
24
+ yield* runWithRetry(async function* () {
25
+ const result = streamText({
26
+ model: xai(cleanModel),
27
+ messages: messages,
28
+ system: config.systemPrompt,
29
+ tools: config.tools,
30
+ maxSteps: config.maxSteps || 100,
31
+ abortSignal: options?.abortSignal,
32
+ providerOptions: reasoningEnabled
33
+ ? {
34
+ xai: {
35
+ reasoningEffort: 'high',
36
+ },
37
+ }
38
+ : undefined,
39
+ });
40
+
41
+ for await (const chunk of result.fullStream as any) {
42
+ const c: any = chunk;
43
+ switch (c.type) {
44
+ case 'reasoning':
45
+ if (c.textDelta) {
46
+ yield {
47
+ type: 'reasoning-delta',
48
+ content: c.textDelta,
49
+ };
50
+ }
51
+ break;
52
+
53
+ case 'text-delta':
54
+ yield {
55
+ type: 'text-delta',
56
+ content: c.textDelta,
57
+ };
58
+ break;
59
+
60
+ case 'step-start':
61
+ yield {
62
+ type: 'step-start',
63
+ stepNumber: typeof c.stepIndex === 'number' ? c.stepIndex : stepCounter,
64
+ };
65
+ stepCounter++;
66
+ break;
67
+
68
+ case 'step-finish':
69
+ yield {
70
+ type: 'step-finish',
71
+ stepNumber:
72
+ typeof c.stepIndex === 'number' ? c.stepIndex : Math.max(0, stepCounter - 1),
73
+ finishReason: String(c.finishReason ?? 'stop'),
74
+ };
75
+ break;
76
+
77
+ case 'tool-call':
78
+ yield {
79
+ type: 'tool-call-end',
80
+ toolCallId: String(c.toolCallId ?? ''),
81
+ toolName: String(c.toolName ?? ''),
82
+ args: (c.args ?? {}) as Record<string, unknown>,
83
+ };
84
+ break;
85
+
86
+ case 'tool-result':
87
+ yield {
88
+ type: 'tool-result',
89
+ toolCallId: String(c.toolCallId ?? ''),
90
+ toolName: String(c.toolName ?? ''),
91
+ result: c.result,
92
+ };
93
+ break;
94
+
95
+ case 'finish':
96
+ yield {
97
+ type: 'finish',
98
+ finishReason: String(c.finishReason ?? 'stop'),
99
+ usage: c.usage,
100
+ };
101
+ break;
102
+
103
+ case 'error': {
104
+ const err = normalizeError(c.error);
105
+ const decision = getRetryDecision(err);
106
+ if (decision.shouldRetry) {
107
+ throw err;
108
+ }
109
+ yield {
110
+ type: 'error',
111
+ error: err.message,
112
+ };
113
+ break;
114
+ }
115
+ }
116
+ }
117
+ }, { abortSignal: options?.abortSignal });
118
+ } catch (error) {
119
+ if (options?.abortSignal?.aborted) return;
116
120
  yield {
117
121
  type: 'error',
118
122
  error: error instanceof Error ? error.message : 'Unknown error occurred',
119
123
  };
120
124
  }
121
125
  }
122
- }
126
+ }
@@ -9,6 +9,8 @@ import { grep } from './grep.ts';
9
9
  import { edit } from './edit.ts';
10
10
  import { question } from './question.ts';
11
11
  import { explore } from './explore.ts';
12
+ import { fetch } from './fetch.ts';
13
+ import { plan } from './plan.ts';
12
14
 
13
15
  export const tools: Record<string, CoreTool> = {
14
16
  read,
@@ -20,8 +22,20 @@ export const tools: Record<string, CoreTool> = {
20
22
  edit,
21
23
  question,
22
24
  explore,
25
+ fetch,
26
+ plan,
23
27
  };
24
28
 
25
29
  export function getTools(): Record<string, CoreTool> {
30
+ try {
31
+ const { getMcpCatalog, isMcpInitialized } = require('../../mcp/index');
32
+ if (isMcpInitialized()) {
33
+ const catalog = getMcpCatalog();
34
+ const mcpTools = catalog.getExposedTools();
35
+ return { ...tools, ...mcpTools };
36
+ }
37
+ } catch {
38
+ // MCP not available, return internal tools only
39
+ }
26
40
  return tools;
27
- }
41
+ }