@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
@@ -1,6 +1,7 @@
1
1
  import { streamText, CoreMessage } from 'ai';
2
2
  import { createMistral } from '@ai-sdk/mistral';
3
- import { AgentEvent, Provider, ProviderConfig, ProviderSendOptions } from '../types';
3
+ import { AgentEvent, Provider, ProviderConfig, ProviderSendOptions } from '../types';
4
+ import { getRetryDecision, normalizeError, runWithRetry } from './rateLimit';
4
5
 
5
6
  export class MistralProvider implements Provider {
6
7
  async *sendMessage(
@@ -15,103 +16,102 @@ export class MistralProvider implements Provider {
15
16
  apiKey: cleanApiKey,
16
17
  });
17
18
 
18
- const result = streamText({
19
- model: mistral(cleanModel),
20
- messages: messages,
21
- system: config.systemPrompt,
22
- tools: config.tools,
23
- maxSteps: config.maxSteps || 10,
24
- abortSignal: options?.abortSignal
25
- });
26
-
27
- try {
28
- let stepCounter = 0;
29
-
30
- for await (const chunk of result.fullStream as any) {
31
- const c: any = chunk;
32
- switch (c.type) {
33
- case 'reasoning':
34
- if (c.textDelta) {
35
- yield {
36
- type: 'reasoning-delta',
37
- content: c.textDelta,
38
- };
39
- }
40
- break;
41
-
42
- case 'text-delta':
43
- yield {
44
- type: 'text-delta',
45
- content: c.textDelta,
46
- };
47
- break;
48
-
49
- case 'step-start':
50
- yield {
51
- type: 'step-start',
52
- stepNumber: typeof c.stepIndex === 'number' ? c.stepIndex : stepCounter,
53
- };
54
- stepCounter++;
55
- break;
56
-
57
- case 'step-finish':
58
- yield {
59
- type: 'step-finish',
60
- stepNumber:
61
- typeof c.stepIndex === 'number' ? c.stepIndex : Math.max(0, stepCounter - 1),
62
- finishReason: String(c.finishReason ?? 'stop'),
63
- };
64
- break;
65
-
66
- case 'tool-call':
67
- yield {
68
- type: 'tool-call-end',
69
- toolCallId: String(c.toolCallId ?? ''),
70
- toolName: String(c.toolName ?? ''),
71
- args: (c.args ?? {}) as Record<string, unknown>,
72
- };
73
- break;
74
-
75
- case 'tool-result':
76
- yield {
77
- type: 'tool-result',
78
- toolCallId: String(c.toolCallId ?? ''),
79
- toolName: String(c.toolName ?? ''),
80
- result: c.result,
81
- };
82
- break;
83
-
84
- case 'finish':
85
- yield {
86
- type: 'finish',
87
- finishReason: String(c.finishReason ?? 'stop'),
88
- usage: c.usage,
89
- };
90
- break;
91
-
92
- case 'error':
93
- {
94
- const err = c.error;
95
- const msg =
96
- err instanceof Error
97
- ? err.message
98
- : typeof err === 'string'
99
- ? err
100
- : 'Unknown error';
101
- yield {
102
- type: 'error',
103
- error: msg,
104
- };
105
- }
106
- break;
107
- }
108
- }
109
- } catch (error) {
110
- if (options?.abortSignal?.aborted) return;
19
+ try {
20
+ let stepCounter = 0;
21
+
22
+ yield* runWithRetry(async function* () {
23
+ const result = streamText({
24
+ model: mistral(cleanModel),
25
+ messages: messages,
26
+ system: config.systemPrompt,
27
+ tools: config.tools,
28
+ maxSteps: config.maxSteps || 100,
29
+ abortSignal: options?.abortSignal
30
+ });
31
+
32
+ for await (const chunk of result.fullStream as any) {
33
+ const c: any = chunk;
34
+ switch (c.type) {
35
+ case 'reasoning':
36
+ if (c.textDelta) {
37
+ yield {
38
+ type: 'reasoning-delta',
39
+ content: c.textDelta,
40
+ };
41
+ }
42
+ break;
43
+
44
+ case 'text-delta':
45
+ yield {
46
+ type: 'text-delta',
47
+ content: c.textDelta,
48
+ };
49
+ break;
50
+
51
+ case 'step-start':
52
+ yield {
53
+ type: 'step-start',
54
+ stepNumber: typeof c.stepIndex === 'number' ? c.stepIndex : stepCounter,
55
+ };
56
+ stepCounter++;
57
+ break;
58
+
59
+ case 'step-finish':
60
+ yield {
61
+ type: 'step-finish',
62
+ stepNumber:
63
+ typeof c.stepIndex === 'number' ? c.stepIndex : Math.max(0, stepCounter - 1),
64
+ finishReason: String(c.finishReason ?? 'stop'),
65
+ };
66
+ break;
67
+
68
+ case 'tool-call':
69
+ yield {
70
+ type: 'tool-call-end',
71
+ toolCallId: String(c.toolCallId ?? ''),
72
+ toolName: String(c.toolName ?? ''),
73
+ args: (c.args ?? {}) as Record<string, unknown>,
74
+ };
75
+ break;
76
+
77
+ case 'tool-result':
78
+ yield {
79
+ type: 'tool-result',
80
+ toolCallId: String(c.toolCallId ?? ''),
81
+ toolName: String(c.toolName ?? ''),
82
+ result: c.result,
83
+ };
84
+ break;
85
+
86
+ case 'finish':
87
+ yield {
88
+ type: 'finish',
89
+ finishReason: String(c.finishReason ?? 'stop'),
90
+ usage: c.usage,
91
+ };
92
+ break;
93
+
94
+ case 'error': {
95
+ const err = normalizeError(c.error);
96
+ const decision = getRetryDecision(err);
97
+ if (decision.shouldRetry) {
98
+ throw err;
99
+ }
100
+ yield {
101
+ type: 'error',
102
+ error: err.message,
103
+ };
104
+ break;
105
+ }
106
+ }
107
+ }
108
+ }, { abortSignal: options?.abortSignal });
109
+ } catch (error) {
110
+ if (options?.abortSignal?.aborted) return;
111
111
  yield {
112
112
  type: 'error',
113
113
  error: error instanceof Error ? error.message : 'Unknown error occurred',
114
114
  };
115
115
  }
116
116
  }
117
- }
117
+ }
@@ -2,6 +2,10 @@ import { Ollama } from 'ollama';
2
2
  import { spawn } from 'child_process';
3
3
  import { CoreMessage, CoreTool } from 'ai';
4
4
  import { AgentEvent, Provider, ProviderConfig, ProviderSendOptions } from '../types';
5
+ import { zodToJsonSchema } from 'zod-to-json-schema';
6
+ import { z } from 'zod';
7
+ import { shouldEnableReasoning } from './reasoning';
8
+ import { getErrorSignature, getRetryDecision } from './rateLimit';
5
9
 
6
10
  let serveStartPromise: Promise<void> | null = null;
7
11
  const pullPromises = new Map<string, Promise<void>>();
@@ -9,6 +13,8 @@ const pullPromises = new Map<string, Promise<void>>();
9
13
  const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
10
14
 
11
15
  function isTransientError(error: unknown): boolean {
16
+ const decision = getRetryDecision(error);
17
+ if (decision.shouldRetry) return true;
12
18
  const msg = error instanceof Error ? error.message : String(error);
13
19
  return (
14
20
  msg.includes('ECONNREFUSED') ||
@@ -21,13 +27,27 @@ function isTransientError(error: unknown): boolean {
21
27
 
22
28
  async function retry<T>(fn: () => Promise<T>, retries: number, baseDelayMs: number): Promise<T> {
23
29
  let lastError: unknown;
30
+ let lastSignature: string | null = null;
31
+ let sameSignatureCount = 0;
24
32
  for (let attempt = 0; attempt <= retries; attempt++) {
25
33
  try {
26
34
  return await fn();
27
35
  } catch (e) {
28
36
  lastError = e;
29
37
  if (attempt >= retries || !isTransientError(e)) throw e;
30
- await sleep(baseDelayMs * Math.max(1, attempt + 1));
38
+ const signature = getErrorSignature(e);
39
+ if (signature === lastSignature) {
40
+ sameSignatureCount += 1;
41
+ } else {
42
+ lastSignature = signature;
43
+ sameSignatureCount = 0;
44
+ }
45
+ if (sameSignatureCount >= 1) {
46
+ throw e;
47
+ }
48
+ const decision = getRetryDecision(e);
49
+ const delay = decision.retryAfterMs ?? (baseDelayMs * Math.max(1, attempt + 1));
50
+ await sleep(delay);
31
51
  }
32
52
  }
33
53
  throw lastError instanceof Error ? lastError : new Error('Request failed');
@@ -57,7 +77,7 @@ function createCloudOllamaClient(apiKey: string): Ollama {
57
77
  } as any);
58
78
  }
59
79
 
60
- async function ensureOllamaServe(apiKey?: string): Promise<void> {
80
+ async function ensureOllamaServe(_apiKey?: string): Promise<void> {
61
81
  if (serveStartPromise) return serveStartPromise;
62
82
 
63
83
  serveStartPromise = (async () => {
@@ -218,21 +238,41 @@ function contentToString(content: CoreMessage['content']): string {
218
238
  }
219
239
  }
220
240
 
241
+ function imagePartToBase64(image: any): string | undefined {
242
+ if (!image) return undefined;
243
+ if (typeof image === 'string') return image;
244
+ if (Buffer.isBuffer(image)) return image.toString('base64');
245
+ if (image instanceof Uint8Array) return Buffer.from(image).toString('base64');
246
+ if (image instanceof ArrayBuffer) return Buffer.from(new Uint8Array(image)).toString('base64');
247
+ return undefined;
248
+ }
249
+
221
250
  function toOllamaTools(tools?: Record<string, CoreTool>): any[] | undefined {
222
251
  if (!tools) return undefined;
223
252
 
224
- return Object.entries(tools).map(([name, tool]) => ({
225
- type: 'function',
226
- function: {
227
- name,
228
- description: String((tool as any)?.description ?? name),
229
- parameters: (() => {
230
- const params = (tool as any)?.parameters;
231
- if (params && typeof params === 'object' && 'type' in params) return params;
232
- return { type: 'object', properties: {} };
233
- })(),
234
- },
235
- }));
253
+ return Object.entries(tools).map(([name, tool]) => {
254
+ const params = (tool as any)?.parameters;
255
+ let jsonSchema: any = { type: 'object', properties: {} };
256
+
257
+ if (params) {
258
+ if (params instanceof z.ZodType) {
259
+ const converted = zodToJsonSchema(params, { target: 'openApi3' });
260
+ jsonSchema = converted;
261
+ if ('$schema' in jsonSchema) delete jsonSchema.$schema;
262
+ } else if (typeof params === 'object' && 'type' in params) {
263
+ jsonSchema = params;
264
+ }
265
+ }
266
+
267
+ return {
268
+ type: 'function',
269
+ function: {
270
+ name,
271
+ description: String((tool as any)?.description ?? name),
272
+ parameters: jsonSchema,
273
+ },
274
+ };
275
+ });
236
276
  }
237
277
 
238
278
  function coreMessagesToOllamaMessages(messages: CoreMessage[]): any[] {
@@ -269,6 +309,21 @@ function coreMessagesToOllamaMessages(messages: CoreMessage[]): any[] {
269
309
  };
270
310
  }
271
311
 
312
+ if (message.role === 'user' && Array.isArray(message.content)) {
313
+ const textParts = message.content
314
+ .map((part: any) => (part && typeof part.text === 'string' ? part.text : ''))
315
+ .filter(Boolean)
316
+ .join('');
317
+ const images = message.content
318
+ .map((part: any) => (part && part.type === 'image' ? imagePartToBase64(part.image) : undefined))
319
+ .filter(Boolean);
320
+ const msg: any = { role: 'user', content: textParts };
321
+ if (images.length > 0) {
322
+ msg.images = images;
323
+ }
324
+ return msg;
325
+ }
326
+
272
327
  return {
273
328
  role: message.role,
274
329
  content: contentToString(message.content),
@@ -308,6 +363,7 @@ export class OllamaProvider implements Provider {
308
363
  ): AsyncGenerator<AgentEvent> {
309
364
  const apiKey = config.apiKey?.trim().replace(/[\r\n]+/g, '');
310
365
  const cleanModel = config.model.trim().replace(/[\r\n]+/g, '');
366
+ const reasoningEnabled = await shouldEnableReasoning(config.provider, cleanModel);
311
367
 
312
368
  if (options?.abortSignal?.aborted) {
313
369
  return;
@@ -374,7 +430,7 @@ export class OllamaProvider implements Provider {
374
430
  }
375
431
 
376
432
  const toolsSchema = toOllamaTools(config.tools);
377
- const maxSteps = config.maxSteps || 10;
433
+ const maxSteps = config.maxSteps || 100;
378
434
 
379
435
  const baseMessages = config.systemPrompt
380
436
  ? [{ role: 'system' as const, content: config.systemPrompt }, ...messages]
@@ -401,7 +457,7 @@ export class OllamaProvider implements Provider {
401
457
  messages: ollamaMessages,
402
458
  tools: toolsSchema,
403
459
  stream: true,
404
- think: true,
460
+ think: reasoningEnabled,
405
461
  signal: options?.abortSignal,
406
462
  } as any) as any,
407
463
  2,
@@ -528,4 +584,4 @@ export class OllamaProvider implements Provider {
528
584
  };
529
585
  }
530
586
  }
531
- }
587
+ }
@@ -1,15 +1,91 @@
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
+ import { getRetryDecision, normalizeError, runWithRetry } from './rateLimit';
7
+
8
+ function unwrapOptional(schema: z.ZodTypeAny): z.ZodTypeAny {
9
+ if (schema instanceof z.ZodOptional) {
10
+ return unwrapOptional(schema.unwrap());
11
+ }
12
+ if (schema instanceof z.ZodEffects) {
13
+ const inner = unwrapOptional(schema.innerType());
14
+ return inner === schema.innerType() ? schema : new z.ZodEffects({
15
+ ...schema._def,
16
+ schema: inner,
17
+ });
18
+ }
19
+ return schema;
20
+ }
21
+
22
+ function makeAllPropertiesRequired(schema: z.ZodTypeAny): z.ZodTypeAny {
23
+ if (schema instanceof z.ZodEffects) {
24
+ const innerTransformed = makeAllPropertiesRequired(schema.innerType());
25
+ return new z.ZodEffects({
26
+ ...schema._def,
27
+ schema: innerTransformed,
28
+ });
29
+ }
30
+
31
+ if (schema instanceof z.ZodObject) {
32
+ const shape = schema.shape;
33
+ const newShape: Record<string, z.ZodTypeAny> = {};
34
+ for (const key in shape) {
35
+ let fieldSchema = shape[key];
36
+ fieldSchema = unwrapOptional(fieldSchema);
37
+ if (fieldSchema instanceof z.ZodObject) {
38
+ fieldSchema = makeAllPropertiesRequired(fieldSchema);
39
+ } else if (fieldSchema instanceof z.ZodArray) {
40
+ const innerType = fieldSchema.element;
41
+ if (innerType instanceof z.ZodObject) {
42
+ fieldSchema = z.array(makeAllPropertiesRequired(innerType));
43
+ }
44
+ } else if (fieldSchema instanceof z.ZodEffects) {
45
+ const innerType = fieldSchema.innerType();
46
+ if (innerType instanceof z.ZodObject) {
47
+ fieldSchema = new z.ZodEffects({
48
+ ...fieldSchema._def,
49
+ schema: makeAllPropertiesRequired(innerType),
50
+ });
51
+ }
52
+ }
53
+ newShape[key] = fieldSchema;
54
+ }
55
+ return z.object(newShape);
56
+ }
57
+ return schema;
58
+ }
59
+
60
+ function transformToolsForResponsesApi(
61
+ tools: Record<string, CoreTool> | undefined
62
+ ): Record<string, CoreTool> | undefined {
63
+ if (!tools) return tools;
64
+
65
+ const transformed: Record<string, CoreTool> = {};
66
+ for (const [name, tool] of Object.entries(tools)) {
67
+ const t = tool as any;
68
+ if (t.parameters) {
69
+ transformed[name] = {
70
+ ...t,
71
+ parameters: makeAllPropertiesRequired(t.parameters),
72
+ };
73
+ } else {
74
+ transformed[name] = tool;
75
+ }
76
+ }
77
+ return transformed;
78
+ }
4
79
 
5
80
  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, '');
81
+ async *sendMessage(
82
+ messages: CoreMessage[],
83
+ config: ProviderConfig,
84
+ options?: ProviderSendOptions
85
+ ): AsyncGenerator<AgentEvent> {
86
+ const cleanApiKey = config.apiKey?.trim().replace(/[\r\n]+/g, '');
87
+ const cleanModel = config.model.trim().replace(/[\r\n]+/g, '');
88
+ const reasoningEnabled = await shouldEnableReasoning(config.provider, cleanModel);
13
89
 
14
90
  const openai = createOpenAI({
15
91
  apiKey: cleanApiKey,
@@ -28,24 +104,29 @@ export class OpenAIProvider implements Provider {
28
104
  }
29
105
  };
30
106
 
31
- const run = async function* (
32
- endpoint: OpenAIEndpoint,
33
- strictJsonSchema: boolean
34
- ): 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
- });
107
+ const runOnce = async function* (
108
+ endpoint: OpenAIEndpoint,
109
+ strictJsonSchema: boolean
110
+ ): AsyncGenerator<AgentEvent> {
111
+ const toolsToUse =
112
+ endpoint === 'responses'
113
+ ? transformToolsForResponsesApi(config.tools)
114
+ : config.tools;
115
+
116
+ const result = streamText({
117
+ model: pickModel(endpoint),
118
+ messages: messages,
119
+ system: config.systemPrompt,
120
+ tools: toolsToUse,
121
+ maxSteps: config.maxSteps ?? 100,
122
+ abortSignal: options?.abortSignal,
123
+ providerOptions: {
124
+ openai: {
125
+ strictJsonSchema,
126
+ ...(reasoningEnabled ? { reasoningEffort: 'high' } : {}),
127
+ },
128
+ },
129
+ });
49
130
 
50
131
  let stepCounter = 0;
51
132
 
@@ -111,26 +192,23 @@ export class OpenAIProvider implements Provider {
111
192
  };
112
193
  break;
113
194
 
114
- case 'error':
115
- {
116
- const err = c.error;
117
- const msg =
118
- err instanceof Error
119
- ? err.message
120
- : typeof err === 'string'
121
- ? err
122
- : 'Unknown error';
123
- yield {
124
- type: 'error',
125
- error: msg,
126
- };
127
- }
128
- break;
129
- }
130
- }
131
-
132
- return;
133
- };
195
+ case 'error': {
196
+ const err = normalizeError(c.error);
197
+ const decision = getRetryDecision(err);
198
+ if (decision.shouldRetry) {
199
+ throw err;
200
+ }
201
+ yield {
202
+ type: 'error',
203
+ error: err.message,
204
+ };
205
+ break;
206
+ }
207
+ }
208
+ }
209
+
210
+ return;
211
+ };
134
212
 
135
213
  const classifyEndpointError = (msg: string): OpenAIEndpoint | null => {
136
214
  const m = msg || '';
@@ -150,59 +228,25 @@ export class OpenAIProvider implements Provider {
150
228
  };
151
229
 
152
230
  try {
153
- yield* run('responses', true);
154
- } catch (error) {
155
- if (options?.abortSignal?.aborted) return;
156
- 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
-
176
- const fallbackEndpoint = classifyEndpointError(msg);
177
- if (fallbackEndpoint && fallbackEndpoint !== 'responses') {
178
- try {
179
- yield* run(fallbackEndpoint, true);
180
- return;
181
- } catch (endpointError) {
182
- if (options?.abortSignal?.aborted) return;
183
- 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
-
231
+ yield* runWithRetry(
232
+ () => runOnce('responses', false),
233
+ { abortSignal: options?.abortSignal }
234
+ );
235
+ } catch (error) {
236
+ if (options?.abortSignal?.aborted) return;
237
+ const msg = error instanceof Error ? error.message : String(error);
238
+
239
+ const fallbackEndpoint = classifyEndpointError(msg);
240
+ if (fallbackEndpoint && fallbackEndpoint !== 'responses') {
241
+ try {
242
+ yield* runWithRetry(
243
+ () => runOnce(fallbackEndpoint, false),
244
+ { abortSignal: options?.abortSignal }
245
+ );
246
+ return;
247
+ } catch (endpointError) {
248
+ if (options?.abortSignal?.aborted) return;
249
+ const endpointMsg = endpointError instanceof Error ? endpointError.message : String(endpointError);
206
250
  yield {
207
251
  type: 'error',
208
252
  error: endpointMsg || 'Unknown error occurred',
@@ -217,4 +261,4 @@ export class OpenAIProvider implements Provider {
217
261
  };
218
262
  }
219
263
  }
220
- }
264
+ }