@kirosnn/mosaic 0.71.0 → 0.74.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 (79) hide show
  1. package/README.md +1 -5
  2. package/package.json +4 -2
  3. package/src/agent/Agent.ts +353 -131
  4. package/src/agent/context.ts +4 -4
  5. package/src/agent/prompts/systemPrompt.ts +15 -6
  6. package/src/agent/prompts/toolsPrompt.ts +136 -10
  7. package/src/agent/provider/anthropic.ts +100 -100
  8. package/src/agent/provider/google.ts +102 -102
  9. package/src/agent/provider/mistral.ts +95 -95
  10. package/src/agent/provider/ollama.ts +77 -60
  11. package/src/agent/provider/openai.ts +42 -38
  12. package/src/agent/provider/rateLimit.ts +178 -0
  13. package/src/agent/provider/xai.ts +99 -99
  14. package/src/agent/tools/definitions.ts +19 -9
  15. package/src/agent/tools/executor.ts +95 -85
  16. package/src/agent/tools/exploreExecutor.ts +8 -10
  17. package/src/agent/tools/grep.ts +30 -29
  18. package/src/agent/tools/question.ts +7 -1
  19. package/src/agent/types.ts +9 -8
  20. package/src/components/App.tsx +45 -45
  21. package/src/components/CustomInput.tsx +214 -36
  22. package/src/components/Main.tsx +552 -339
  23. package/src/components/Setup.tsx +1 -1
  24. package/src/components/Welcome.tsx +1 -1
  25. package/src/components/main/ApprovalPanel.tsx +4 -3
  26. package/src/components/main/ChatPage.tsx +858 -675
  27. package/src/components/main/HomePage.tsx +53 -38
  28. package/src/components/main/QuestionPanel.tsx +52 -7
  29. package/src/components/main/ThinkingIndicator.tsx +2 -1
  30. package/src/index.tsx +50 -20
  31. package/src/mcp/approvalPolicy.ts +156 -0
  32. package/src/mcp/cli/add.ts +185 -0
  33. package/src/mcp/cli/doctor.ts +74 -0
  34. package/src/mcp/cli/index.ts +85 -0
  35. package/src/mcp/cli/list.ts +50 -0
  36. package/src/mcp/cli/logs.ts +24 -0
  37. package/src/mcp/cli/manage.ts +99 -0
  38. package/src/mcp/cli/show.ts +53 -0
  39. package/src/mcp/cli/tools.ts +77 -0
  40. package/src/mcp/config.ts +234 -0
  41. package/src/mcp/index.ts +80 -0
  42. package/src/mcp/processManager.ts +304 -0
  43. package/src/mcp/rateLimiter.ts +50 -0
  44. package/src/mcp/registry.ts +151 -0
  45. package/src/mcp/schemaConverter.ts +100 -0
  46. package/src/mcp/servers/navigation/browser.ts +151 -0
  47. package/src/mcp/servers/navigation/index.ts +23 -0
  48. package/src/mcp/servers/navigation/tools.ts +263 -0
  49. package/src/mcp/servers/navigation/types.ts +17 -0
  50. package/src/mcp/servers/navigation/utils.ts +20 -0
  51. package/src/mcp/toolCatalog.ts +182 -0
  52. package/src/mcp/types.ts +116 -0
  53. package/src/utils/approvalBridge.ts +17 -5
  54. package/src/utils/commands/compact.ts +30 -0
  55. package/src/utils/commands/echo.ts +1 -1
  56. package/src/utils/commands/index.ts +4 -6
  57. package/src/utils/commands/new.ts +15 -0
  58. package/src/utils/commands/types.ts +3 -0
  59. package/src/utils/config.ts +3 -1
  60. package/src/utils/diffRendering.tsx +1 -3
  61. package/src/utils/exploreBridge.ts +10 -0
  62. package/src/utils/markdown.tsx +220 -122
  63. package/src/utils/models.ts +31 -9
  64. package/src/utils/questionBridge.ts +36 -1
  65. package/src/utils/tokenEstimator.ts +32 -0
  66. package/src/utils/toolFormatting.ts +317 -7
  67. package/src/web/app.tsx +72 -72
  68. package/src/web/components/HomePage.tsx +7 -7
  69. package/src/web/components/MessageItem.tsx +66 -35
  70. package/src/web/components/QuestionPanel.tsx +72 -12
  71. package/src/web/components/Sidebar.tsx +0 -2
  72. package/src/web/components/ThinkingIndicator.tsx +1 -0
  73. package/src/web/server.tsx +767 -683
  74. package/src/utils/commands/redo.ts +0 -74
  75. package/src/utils/commands/sessions.ts +0 -129
  76. package/src/utils/commands/undo.ts +0 -75
  77. package/src/utils/undoRedo.ts +0 -429
  78. package/src/utils/undoRedoBridge.ts +0 -45
  79. 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
+ }
@@ -2,6 +2,7 @@ import { streamText, CoreMessage } from 'ai';
2
2
  import { createXai } from '@ai-sdk/xai';
3
3
  import { AgentEvent, Provider, ProviderConfig, ProviderSendOptions } from '../types';
4
4
  import { shouldEnableReasoning } from './reasoning';
5
+ import { getRetryDecision, normalizeError, runWithRetry } from './rateLimit';
5
6
 
6
7
  export class XaiProvider implements Provider {
7
8
  async *sendMessage(
@@ -17,106 +18,105 @@ export class XaiProvider implements Provider {
17
18
  apiKey: cleanApiKey,
18
19
  });
19
20
 
20
- const result = streamText({
21
- model: xai(cleanModel),
22
- messages: messages,
23
- system: config.systemPrompt,
24
- tools: config.tools,
25
- maxSteps: config.maxSteps || 10,
26
- abortSignal: options?.abortSignal,
27
- providerOptions: reasoningEnabled
28
- ? {
29
- xai: {
30
- reasoningEffort: 'high',
31
- },
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
+ }
32
116
  }
33
- : undefined,
34
- });
35
-
36
- try {
37
- let stepCounter = 0;
38
-
39
- for await (const chunk of result.fullStream as any) {
40
- const c: any = chunk;
41
- switch (c.type) {
42
- case 'reasoning':
43
- if (c.textDelta) {
44
- yield {
45
- type: 'reasoning-delta',
46
- content: c.textDelta,
47
- };
48
- }
49
- break;
50
-
51
- case 'text-delta':
52
- yield {
53
- type: 'text-delta',
54
- content: c.textDelta,
55
- };
56
- break;
57
-
58
- case 'step-start':
59
- yield {
60
- type: 'step-start',
61
- stepNumber: typeof c.stepIndex === 'number' ? c.stepIndex : stepCounter,
62
- };
63
- stepCounter++;
64
- break;
65
-
66
- case 'step-finish':
67
- yield {
68
- type: 'step-finish',
69
- stepNumber:
70
- typeof c.stepIndex === 'number' ? c.stepIndex : Math.max(0, stepCounter - 1),
71
- finishReason: String(c.finishReason ?? 'stop'),
72
- };
73
- break;
74
-
75
- case 'tool-call':
76
- yield {
77
- type: 'tool-call-end',
78
- toolCallId: String(c.toolCallId ?? ''),
79
- toolName: String(c.toolName ?? ''),
80
- args: (c.args ?? {}) as Record<string, unknown>,
81
- };
82
- break;
83
-
84
- case 'tool-result':
85
- yield {
86
- type: 'tool-result',
87
- toolCallId: String(c.toolCallId ?? ''),
88
- toolName: String(c.toolName ?? ''),
89
- result: c.result,
90
- };
91
- break;
92
-
93
- case 'finish':
94
- yield {
95
- type: 'finish',
96
- finishReason: String(c.finishReason ?? 'stop'),
97
- usage: c.usage,
98
- };
99
- break;
100
-
101
- case 'error':
102
- {
103
- const err = c.error;
104
- const msg =
105
- err instanceof Error
106
- ? err.message
107
- : typeof err === 'string'
108
- ? err
109
- : 'Unknown error';
110
- yield {
111
- type: 'error',
112
- error: msg,
113
- };
114
- }
115
- break;
116
- }
117
- }
118
- } catch (error) {
119
- if (options?.abortSignal?.aborted) return;
117
+ }, { abortSignal: options?.abortSignal });
118
+ } catch (error) {
119
+ if (options?.abortSignal?.aborted) return;
120
120
  yield {
121
121
  type: 'error',
122
122
  error: error instanceof Error ? error.message : 'Unknown error occurred',
@@ -8,9 +8,9 @@ import { glob } from './glob.ts';
8
8
  import { grep } from './grep.ts';
9
9
  import { edit } from './edit.ts';
10
10
  import { question } from './question.ts';
11
- import { explore } from './explore.ts';
12
- import { fetch } from './fetch.ts';
13
- import { plan } from './plan.ts';
11
+ import { explore } from './explore.ts';
12
+ import { fetch } from './fetch.ts';
13
+ import { plan } from './plan.ts';
14
14
 
15
15
  export const tools: Record<string, CoreTool> = {
16
16
  read,
@@ -20,12 +20,22 @@ export const tools: Record<string, CoreTool> = {
20
20
  glob,
21
21
  grep,
22
22
  edit,
23
- question,
24
- explore,
25
- fetch,
26
- plan,
27
- };
23
+ question,
24
+ explore,
25
+ fetch,
26
+ plan,
27
+ };
28
28
 
29
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
+ }
30
40
  return tools;
31
- }
41
+ }