@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.
- package/LICENSE +1 -1
- package/README.md +2 -6
- package/package.json +55 -48
- package/src/agent/Agent.ts +353 -131
- package/src/agent/context.ts +4 -4
- package/src/agent/prompts/systemPrompt.ts +209 -70
- package/src/agent/prompts/toolsPrompt.ts +285 -138
- package/src/agent/provider/anthropic.ts +109 -105
- package/src/agent/provider/google.ts +111 -107
- package/src/agent/provider/mistral.ts +95 -95
- package/src/agent/provider/ollama.ts +73 -17
- package/src/agent/provider/openai.ts +146 -102
- package/src/agent/provider/rateLimit.ts +178 -0
- package/src/agent/provider/reasoning.ts +29 -0
- package/src/agent/provider/xai.ts +108 -104
- package/src/agent/tools/definitions.ts +15 -1
- package/src/agent/tools/executor.ts +717 -98
- package/src/agent/tools/exploreExecutor.ts +20 -22
- package/src/agent/tools/fetch.ts +58 -0
- package/src/agent/tools/glob.ts +20 -4
- package/src/agent/tools/grep.ts +64 -9
- package/src/agent/tools/plan.ts +27 -0
- package/src/agent/tools/question.ts +7 -1
- package/src/agent/tools/read.ts +2 -0
- package/src/agent/types.ts +15 -14
- package/src/components/App.tsx +50 -8
- package/src/components/CustomInput.tsx +461 -77
- package/src/components/Main.tsx +1459 -1112
- package/src/components/Setup.tsx +1 -1
- package/src/components/ShortcutsModal.tsx +11 -8
- package/src/components/Welcome.tsx +1 -1
- package/src/components/main/ApprovalPanel.tsx +4 -3
- package/src/components/main/ChatPage.tsx +858 -516
- package/src/components/main/HomePage.tsx +58 -39
- package/src/components/main/QuestionPanel.tsx +52 -7
- package/src/components/main/ThinkingIndicator.tsx +13 -2
- package/src/components/main/types.ts +11 -10
- package/src/index.tsx +53 -25
- package/src/mcp/approvalPolicy.ts +148 -0
- package/src/mcp/cli/add.ts +185 -0
- package/src/mcp/cli/doctor.ts +77 -0
- package/src/mcp/cli/index.ts +85 -0
- package/src/mcp/cli/list.ts +50 -0
- package/src/mcp/cli/logs.ts +24 -0
- package/src/mcp/cli/manage.ts +99 -0
- package/src/mcp/cli/show.ts +53 -0
- package/src/mcp/cli/tools.ts +77 -0
- package/src/mcp/config.ts +223 -0
- package/src/mcp/index.ts +80 -0
- package/src/mcp/processManager.ts +299 -0
- package/src/mcp/rateLimiter.ts +50 -0
- package/src/mcp/registry.ts +151 -0
- package/src/mcp/schemaConverter.ts +100 -0
- package/src/mcp/servers/navigation.ts +854 -0
- package/src/mcp/toolCatalog.ts +169 -0
- package/src/mcp/types.ts +95 -0
- package/src/utils/approvalBridge.ts +45 -12
- package/src/utils/approvalModeBridge.ts +17 -0
- package/src/utils/commands/approvals.ts +48 -0
- package/src/utils/commands/compact.ts +30 -0
- package/src/utils/commands/echo.ts +1 -1
- package/src/utils/commands/image.ts +109 -0
- package/src/utils/commands/index.ts +9 -7
- package/src/utils/commands/new.ts +15 -0
- package/src/utils/commands/types.ts +3 -0
- package/src/utils/config.ts +3 -1
- package/src/utils/diffRendering.tsx +13 -16
- package/src/utils/exploreBridge.ts +10 -0
- package/src/utils/history.ts +82 -40
- package/src/utils/imageBridge.ts +28 -0
- package/src/utils/images.ts +31 -0
- package/src/utils/markdown.tsx +163 -99
- package/src/utils/models.ts +31 -16
- package/src/utils/notificationBridge.ts +23 -0
- package/src/utils/questionBridge.ts +36 -1
- package/src/utils/tokenEstimator.ts +32 -0
- package/src/utils/toolFormatting.ts +428 -48
- package/src/web/app.tsx +65 -5
- package/src/web/assets/css/ChatPage.css +102 -30
- package/src/web/assets/css/MessageItem.css +26 -29
- package/src/web/assets/css/ThinkingIndicator.css +44 -6
- package/src/web/assets/css/ToolMessage.css +36 -14
- package/src/web/components/ChatPage.tsx +228 -105
- package/src/web/components/HomePage.tsx +3 -3
- package/src/web/components/MessageItem.tsx +80 -81
- package/src/web/components/QuestionPanel.tsx +72 -12
- package/src/web/components/Setup.tsx +1 -1
- package/src/web/components/Sidebar.tsx +1 -3
- package/src/web/components/ThinkingIndicator.tsx +41 -21
- package/src/web/router.ts +1 -1
- package/src/web/server.tsx +894 -662
- package/src/web/storage.ts +23 -1
- package/src/web/types.ts +7 -6
- package/src/utils/commands/redo.ts +0 -74
- package/src/utils/commands/sessions.ts +0 -129
- package/src/utils/commands/undo.ts +0 -75
- package/src/utils/undoRedo.ts +0 -429
- package/src/utils/undoRedoBridge.ts +0 -45
- 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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{
|
|
94
|
-
const err = c.error;
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
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 ||
|
|
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:
|
|
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
|
|
32
|
-
endpoint: OpenAIEndpoint,
|
|
33
|
-
strictJsonSchema: boolean
|
|
34
|
-
): AsyncGenerator<AgentEvent> {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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*
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
try {
|
|
164
|
-
yield*
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
}
|