@kirosnn/mosaic 0.0.9 → 0.71.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 (56) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +83 -19
  3. package/package.json +52 -47
  4. package/src/agent/prompts/systemPrompt.ts +198 -68
  5. package/src/agent/prompts/toolsPrompt.ts +217 -135
  6. package/src/agent/provider/anthropic.ts +19 -15
  7. package/src/agent/provider/google.ts +21 -17
  8. package/src/agent/provider/ollama.ts +80 -41
  9. package/src/agent/provider/openai.ts +107 -67
  10. package/src/agent/provider/reasoning.ts +29 -0
  11. package/src/agent/provider/xai.ts +19 -15
  12. package/src/agent/tools/definitions.ts +9 -5
  13. package/src/agent/tools/executor.ts +655 -46
  14. package/src/agent/tools/exploreExecutor.ts +12 -12
  15. package/src/agent/tools/fetch.ts +58 -0
  16. package/src/agent/tools/glob.ts +20 -4
  17. package/src/agent/tools/grep.ts +62 -8
  18. package/src/agent/tools/plan.ts +27 -0
  19. package/src/agent/tools/read.ts +2 -0
  20. package/src/agent/types.ts +6 -6
  21. package/src/components/App.tsx +67 -25
  22. package/src/components/CustomInput.tsx +274 -68
  23. package/src/components/Main.tsx +323 -168
  24. package/src/components/ShortcutsModal.tsx +11 -8
  25. package/src/components/main/ChatPage.tsx +217 -58
  26. package/src/components/main/HomePage.tsx +5 -1
  27. package/src/components/main/ThinkingIndicator.tsx +11 -1
  28. package/src/components/main/types.ts +11 -10
  29. package/src/index.tsx +3 -5
  30. package/src/utils/approvalBridge.ts +29 -8
  31. package/src/utils/approvalModeBridge.ts +17 -0
  32. package/src/utils/commands/approvals.ts +48 -0
  33. package/src/utils/commands/image.ts +109 -0
  34. package/src/utils/commands/index.ts +5 -1
  35. package/src/utils/diffRendering.tsx +13 -14
  36. package/src/utils/history.ts +82 -40
  37. package/src/utils/imageBridge.ts +28 -0
  38. package/src/utils/images.ts +31 -0
  39. package/src/utils/models.ts +0 -7
  40. package/src/utils/notificationBridge.ts +23 -0
  41. package/src/utils/toolFormatting.ts +162 -43
  42. package/src/web/app.tsx +94 -34
  43. package/src/web/assets/css/ChatPage.css +102 -30
  44. package/src/web/assets/css/MessageItem.css +26 -29
  45. package/src/web/assets/css/ThinkingIndicator.css +44 -6
  46. package/src/web/assets/css/ToolMessage.css +36 -14
  47. package/src/web/components/ChatPage.tsx +228 -105
  48. package/src/web/components/HomePage.tsx +6 -6
  49. package/src/web/components/MessageItem.tsx +88 -89
  50. package/src/web/components/Setup.tsx +1 -1
  51. package/src/web/components/Sidebar.tsx +1 -1
  52. package/src/web/components/ThinkingIndicator.tsx +40 -21
  53. package/src/web/router.ts +1 -1
  54. package/src/web/server.tsx +187 -39
  55. package/src/web/storage.ts +23 -1
  56. package/src/web/types.ts +7 -6
@@ -1,15 +1,90 @@
1
- import { streamText, CoreMessage } from 'ai';
2
- import { createOpenAI } from '@ai-sdk/openai';
3
- import { AgentEvent, Provider, ProviderConfig, ProviderSendOptions } from '../types';
1
+ import { streamText, CoreMessage, CoreTool } from 'ai';
2
+ import { createOpenAI } from '@ai-sdk/openai';
3
+ import { AgentEvent, Provider, ProviderConfig, ProviderSendOptions } from '../types';
4
+ import { z } from 'zod';
5
+ import { shouldEnableReasoning } from './reasoning';
6
+
7
+ function unwrapOptional(schema: z.ZodTypeAny): z.ZodTypeAny {
8
+ if (schema instanceof z.ZodOptional) {
9
+ return unwrapOptional(schema.unwrap());
10
+ }
11
+ if (schema instanceof z.ZodEffects) {
12
+ const inner = unwrapOptional(schema.innerType());
13
+ return inner === schema.innerType() ? schema : new z.ZodEffects({
14
+ ...schema._def,
15
+ schema: inner,
16
+ });
17
+ }
18
+ return schema;
19
+ }
20
+
21
+ function makeAllPropertiesRequired(schema: z.ZodTypeAny): z.ZodTypeAny {
22
+ if (schema instanceof z.ZodEffects) {
23
+ const innerTransformed = makeAllPropertiesRequired(schema.innerType());
24
+ return new z.ZodEffects({
25
+ ...schema._def,
26
+ schema: innerTransformed,
27
+ });
28
+ }
29
+
30
+ if (schema instanceof z.ZodObject) {
31
+ const shape = schema.shape;
32
+ const newShape: Record<string, z.ZodTypeAny> = {};
33
+ for (const key in shape) {
34
+ let fieldSchema = shape[key];
35
+ fieldSchema = unwrapOptional(fieldSchema);
36
+ if (fieldSchema instanceof z.ZodObject) {
37
+ fieldSchema = makeAllPropertiesRequired(fieldSchema);
38
+ } else if (fieldSchema instanceof z.ZodArray) {
39
+ const innerType = fieldSchema.element;
40
+ if (innerType instanceof z.ZodObject) {
41
+ fieldSchema = z.array(makeAllPropertiesRequired(innerType));
42
+ }
43
+ } else if (fieldSchema instanceof z.ZodEffects) {
44
+ const innerType = fieldSchema.innerType();
45
+ if (innerType instanceof z.ZodObject) {
46
+ fieldSchema = new z.ZodEffects({
47
+ ...fieldSchema._def,
48
+ schema: makeAllPropertiesRequired(innerType),
49
+ });
50
+ }
51
+ }
52
+ newShape[key] = fieldSchema;
53
+ }
54
+ return z.object(newShape);
55
+ }
56
+ return schema;
57
+ }
58
+
59
+ function transformToolsForResponsesApi(
60
+ tools: Record<string, CoreTool> | undefined
61
+ ): Record<string, CoreTool> | undefined {
62
+ if (!tools) return tools;
63
+
64
+ const transformed: Record<string, CoreTool> = {};
65
+ for (const [name, tool] of Object.entries(tools)) {
66
+ const t = tool as any;
67
+ if (t.parameters) {
68
+ transformed[name] = {
69
+ ...t,
70
+ parameters: makeAllPropertiesRequired(t.parameters),
71
+ };
72
+ } else {
73
+ transformed[name] = tool;
74
+ }
75
+ }
76
+ return transformed;
77
+ }
4
78
 
5
79
  export class OpenAIProvider implements Provider {
6
- async *sendMessage(
7
- messages: CoreMessage[],
8
- config: ProviderConfig,
9
- options?: ProviderSendOptions
10
- ): AsyncGenerator<AgentEvent> {
11
- const cleanApiKey = config.apiKey?.trim().replace(/[\r\n]+/g, '');
12
- const cleanModel = config.model.trim().replace(/[\r\n]+/g, '');
80
+ async *sendMessage(
81
+ messages: CoreMessage[],
82
+ config: ProviderConfig,
83
+ options?: ProviderSendOptions
84
+ ): AsyncGenerator<AgentEvent> {
85
+ const cleanApiKey = config.apiKey?.trim().replace(/[\r\n]+/g, '');
86
+ const cleanModel = config.model.trim().replace(/[\r\n]+/g, '');
87
+ const reasoningEnabled = await shouldEnableReasoning(config.provider, cleanModel);
13
88
 
14
89
  const openai = createOpenAI({
15
90
  apiKey: cleanApiKey,
@@ -32,20 +107,25 @@ export class OpenAIProvider implements Provider {
32
107
  endpoint: OpenAIEndpoint,
33
108
  strictJsonSchema: boolean
34
109
  ): AsyncGenerator<AgentEvent> {
35
- const result = streamText({
36
- model: pickModel(endpoint),
37
- messages: messages,
38
- system: config.systemPrompt,
39
- tools: config.tools,
40
- maxSteps: config.maxSteps ?? 10,
41
- abortSignal: options?.abortSignal,
42
- providerOptions: {
43
- openai: {
44
- strictJsonSchema,
45
- reasoningEffort: 'medium',
46
- },
47
- },
48
- });
110
+ const toolsToUse =
111
+ endpoint === 'responses'
112
+ ? transformToolsForResponsesApi(config.tools)
113
+ : config.tools;
114
+
115
+ const result = streamText({
116
+ model: pickModel(endpoint),
117
+ messages: messages,
118
+ system: config.systemPrompt,
119
+ tools: toolsToUse,
120
+ maxSteps: config.maxSteps ?? 10,
121
+ abortSignal: options?.abortSignal,
122
+ providerOptions: {
123
+ openai: {
124
+ strictJsonSchema,
125
+ ...(reasoningEnabled ? { reasoningEffort: 'high' } : {}),
126
+ },
127
+ },
128
+ });
49
129
 
50
130
  let stepCounter = 0;
51
131
 
@@ -150,59 +230,19 @@ export class OpenAIProvider implements Provider {
150
230
  };
151
231
 
152
232
  try {
153
- yield* run('responses', true);
233
+ yield* run('responses', false);
154
234
  } catch (error) {
155
235
  if (options?.abortSignal?.aborted) return;
156
236
  const msg = error instanceof Error ? error.message : String(error);
157
- const looksLikeStrictSchemaError =
158
- msg.includes('Invalid schema for function') &&
159
- msg.includes('required') &&
160
- msg.includes('properties');
161
-
162
- if (looksLikeStrictSchemaError) {
163
- try {
164
- yield* run('responses', false);
165
- return;
166
- } catch (retryError) {
167
- if (options?.abortSignal?.aborted) return;
168
- yield {
169
- type: 'error',
170
- error: retryError instanceof Error ? retryError.message : 'Unknown error occurred',
171
- };
172
- return;
173
- }
174
- }
175
237
 
176
238
  const fallbackEndpoint = classifyEndpointError(msg);
177
239
  if (fallbackEndpoint && fallbackEndpoint !== 'responses') {
178
240
  try {
179
- yield* run(fallbackEndpoint, true);
241
+ yield* run(fallbackEndpoint, false);
180
242
  return;
181
243
  } catch (endpointError) {
182
244
  if (options?.abortSignal?.aborted) return;
183
245
  const endpointMsg = endpointError instanceof Error ? endpointError.message : String(endpointError);
184
- const strictSchemaFromFallback =
185
- endpointMsg.includes('Invalid schema for function') &&
186
- endpointMsg.includes('required') &&
187
- endpointMsg.includes('properties');
188
-
189
- if (strictSchemaFromFallback) {
190
- try {
191
- yield* run(fallbackEndpoint, false);
192
- return;
193
- } catch (endpointRetryError) {
194
- if (options?.abortSignal?.aborted) return;
195
- yield {
196
- type: 'error',
197
- error:
198
- endpointRetryError instanceof Error
199
- ? endpointRetryError.message
200
- : 'Unknown error occurred',
201
- };
202
- return;
203
- }
204
- }
205
-
206
246
  yield {
207
247
  type: 'error',
208
248
  error: endpointMsg || 'Unknown error occurred',
@@ -217,4 +257,4 @@ export class OpenAIProvider implements Provider {
217
257
  };
218
258
  }
219
259
  }
220
- }
260
+ }
@@ -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,7 @@
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';
4
5
 
5
6
  export class XaiProvider implements Provider {
6
7
  async *sendMessage(
@@ -8,8 +9,9 @@ export class XaiProvider implements Provider {
8
9
  config: ProviderConfig,
9
10
  options?: ProviderSendOptions
10
11
  ): AsyncGenerator<AgentEvent> {
11
- const cleanApiKey = config.apiKey?.trim().replace(/[\r\n]+/g, '');
12
- const cleanModel = config.model.trim().replace(/[\r\n]+/g, '');
12
+ const cleanApiKey = config.apiKey?.trim().replace(/[\r\n]+/g, '');
13
+ const cleanModel = config.model.trim().replace(/[\r\n]+/g, '');
14
+ const reasoningEnabled = await shouldEnableReasoning(config.provider, cleanModel);
13
15
 
14
16
  const xai = createXai({
15
17
  apiKey: cleanApiKey,
@@ -19,15 +21,17 @@ export class XaiProvider implements Provider {
19
21
  model: xai(cleanModel),
20
22
  messages: messages,
21
23
  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
- });
24
+ tools: config.tools,
25
+ maxSteps: config.maxSteps || 10,
26
+ abortSignal: options?.abortSignal,
27
+ providerOptions: reasoningEnabled
28
+ ? {
29
+ xai: {
30
+ reasoningEffort: 'high',
31
+ },
32
+ }
33
+ : undefined,
34
+ });
31
35
 
32
36
  try {
33
37
  let stepCounter = 0;
@@ -119,4 +123,4 @@ export class XaiProvider implements Provider {
119
123
  };
120
124
  }
121
125
  }
122
- }
126
+ }
@@ -8,7 +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';
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,
@@ -18,10 +20,12 @@ export const tools: Record<string, CoreTool> = {
18
20
  glob,
19
21
  grep,
20
22
  edit,
21
- question,
22
- explore,
23
- };
23
+ question,
24
+ explore,
25
+ fetch,
26
+ plan,
27
+ };
24
28
 
25
29
  export function getTools(): Record<string, CoreTool> {
26
30
  return tools;
27
- }
31
+ }