@tuturuuu/ai 0.13.0 → 0.15.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/package.json +41 -41
- package/src/chat/google/assistant-message-parts.ts +105 -0
- package/src/chat/google/chat-tools.ts +7 -0
- package/src/chat/google/default-system-instruction.ts +5 -194
- package/src/chat/google/google-search-usage.ts +62 -0
- package/src/chat/google/message-file-processing.ts +8 -2
- package/src/chat/google/mira-step-preparation.ts +37 -16
- package/src/chat/google/route-chat-resolution.ts +11 -18
- package/src/chat/google/route-credits.ts +6 -7
- package/src/chat/google/route-message-preparation.ts +33 -0
- package/src/chat/google/route-mira-runtime.ts +56 -32
- package/src/chat/google/route.ts +32 -77
- package/src/chat/google/stream-finish-persistence.ts +39 -150
- package/src/chat/mira-artifact-intent.ts +38 -0
- package/src/chat/mira-prompt.ts +29 -0
- package/src/chat/mira-render-ui-policy.ts +65 -20
- package/src/chat/mira-system-instruction.ts +10 -172
- package/src/chat/mira-tool-loop-guard.ts +84 -0
- package/src/credits/cap-output-tokens.ts +48 -15
- package/src/credits/check-credits.ts +14 -3
- package/src/credits/constants.ts +3 -0
- package/src/credits/index.ts +1 -0
- package/src/generate/route.ts +6 -3
- package/src/meetings/audio-contract.ts +8 -0
- package/src/meetings/audio-transcripts.ts +25 -0
- package/src/meetings/chat-source-footer.ts +1 -0
- package/src/meetings/chat.ts +30 -7
- package/src/meetings/gemini.ts +84 -23
- package/src/meetings/public-chat-search.ts +4 -1
- package/src/meetings/transcript.ts +63 -0
- package/src/meetings/workspace-tools.ts +3 -1
- package/src/studio/metering.ts +5 -1
- package/src/tools/definitions/calendar-automation.ts +43 -0
- package/src/tools/definitions/calendar.ts +27 -1
- package/src/tools/definitions/meta.ts +17 -1
- package/src/tools/definitions/tasks.ts +82 -31
- package/src/tools/definitions/workspace-user-chat.ts +24 -0
- package/src/tools/executors/calendar-automation.ts +207 -0
- package/src/tools/executors/calendar.ts +30 -1
- package/src/tools/executors/helpers/encryption.ts +12 -9
- package/src/tools/executors/search.ts +40 -9
- package/src/tools/executors/task-create.ts +232 -0
- package/src/tools/executors/task-dashboard.ts +59 -0
- package/src/tools/executors/task-read.ts +76 -0
- package/src/tools/executors/tasks.ts +50 -205
- package/src/tools/executors/theme.ts +48 -0
- package/src/tools/google-search-events.ts +4 -0
- package/src/tools/mira-sdk-tool-search.ts +73 -0
- package/src/tools/mira-task-write-guard.ts +61 -0
- package/src/tools/mira-tool-discovery.ts +138 -0
- package/src/tools/mira-tool-dispatcher.ts +33 -1
- package/src/tools/mira-tool-metadata.ts +29 -1
- package/src/tools/mira-tool-names.ts +13 -0
- package/src/tools/mira-tool-types.ts +7 -0
- package/src/tools/mira-tools.ts +70 -2
- package/src/workspace-artifacts.ts +58 -0
|
@@ -3,17 +3,20 @@ import {
|
|
|
3
3
|
buildActiveToolsFromSelected,
|
|
4
4
|
countRenderUiAttemptsInSteps,
|
|
5
5
|
extractSelectedToolsFromSteps,
|
|
6
|
+
getWorkspaceArtifactProgress,
|
|
6
7
|
hasRenderableRenderUiInSteps,
|
|
7
8
|
hasSuccessfulWorkspaceContextResolutionInSteps,
|
|
8
9
|
hasToolCallInSteps,
|
|
9
10
|
removeWorkspaceDiscoveryTools,
|
|
10
11
|
wasToolEverSelectedInSteps,
|
|
11
12
|
} from '../mira-render-ui-policy';
|
|
13
|
+
import { getMiraToolLoopReason } from '../mira-tool-loop-guard';
|
|
12
14
|
|
|
13
15
|
export type PrepareMiraToolStepInput = {
|
|
14
16
|
steps: unknown[];
|
|
15
17
|
forceGoogleSearch: boolean;
|
|
16
18
|
forceRenderUi: boolean;
|
|
19
|
+
forceWorkspaceArtifact?: boolean;
|
|
17
20
|
needsParallelChecks: boolean;
|
|
18
21
|
needsWorkspaceContextResolution: boolean;
|
|
19
22
|
needsWorkspaceMembersTool: boolean;
|
|
@@ -24,26 +27,41 @@ export function prepareMiraToolStep({
|
|
|
24
27
|
steps,
|
|
25
28
|
forceGoogleSearch,
|
|
26
29
|
forceRenderUi,
|
|
30
|
+
forceWorkspaceArtifact,
|
|
27
31
|
needsParallelChecks,
|
|
28
32
|
needsWorkspaceContextResolution,
|
|
29
33
|
needsWorkspaceMembersTool,
|
|
30
34
|
preferMarkdownTables,
|
|
31
35
|
}: PrepareMiraToolStepInput): {
|
|
32
|
-
toolChoice?: 'required';
|
|
36
|
+
toolChoice?: 'required' | 'none';
|
|
33
37
|
activeTools: string[];
|
|
34
38
|
} {
|
|
39
|
+
if (forceWorkspaceArtifact) forceRenderUi = false;
|
|
40
|
+
const artifact = getWorkspaceArtifactProgress(steps);
|
|
41
|
+
if (forceWorkspaceArtifact && !artifact.completed && artifact.attempts >= 3)
|
|
42
|
+
return { toolChoice: 'none', activeTools: [] };
|
|
43
|
+
if (
|
|
44
|
+
forceWorkspaceArtifact &&
|
|
45
|
+
!artifact.completed &&
|
|
46
|
+
(!needsWorkspaceContextResolution ||
|
|
47
|
+
hasSuccessfulWorkspaceContextResolutionInSteps(steps))
|
|
48
|
+
) {
|
|
49
|
+
return { toolChoice: 'required', activeTools: ['show_workspace_artifact'] };
|
|
50
|
+
}
|
|
51
|
+
if (getMiraToolLoopReason(steps))
|
|
52
|
+
return { toolChoice: 'none', activeTools: [] };
|
|
35
53
|
if (steps.length === 0) {
|
|
36
54
|
if (forceGoogleSearch) {
|
|
37
55
|
return {
|
|
38
56
|
toolChoice: 'required',
|
|
39
|
-
activeTools: ['google_search'
|
|
57
|
+
activeTools: ['google_search'],
|
|
40
58
|
};
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
if (needsParallelChecks) {
|
|
44
62
|
return {
|
|
45
63
|
toolChoice: 'required',
|
|
46
|
-
activeTools: ['run_parallel_checks', 'select_tools'],
|
|
64
|
+
activeTools: ['run_parallel_checks', 'search_tools', 'select_tools'],
|
|
47
65
|
};
|
|
48
66
|
}
|
|
49
67
|
|
|
@@ -54,6 +72,7 @@ export function prepareMiraToolStep({
|
|
|
54
72
|
'list_accessible_workspaces',
|
|
55
73
|
'get_workspace_context',
|
|
56
74
|
'set_workspace_context',
|
|
75
|
+
'search_tools',
|
|
57
76
|
'select_tools',
|
|
58
77
|
],
|
|
59
78
|
};
|
|
@@ -65,13 +84,14 @@ export function prepareMiraToolStep({
|
|
|
65
84
|
activeTools: [
|
|
66
85
|
'get_workspace_context',
|
|
67
86
|
'list_workspace_members',
|
|
87
|
+
'search_tools',
|
|
68
88
|
'select_tools',
|
|
69
89
|
],
|
|
70
90
|
};
|
|
71
91
|
}
|
|
72
92
|
|
|
73
93
|
return {
|
|
74
|
-
activeTools: ['select_tools', 'no_action_needed'],
|
|
94
|
+
activeTools: ['search_tools', 'select_tools', 'no_action_needed'],
|
|
75
95
|
};
|
|
76
96
|
}
|
|
77
97
|
|
|
@@ -106,11 +126,17 @@ export function prepareMiraToolStep({
|
|
|
106
126
|
return {
|
|
107
127
|
toolChoice: 'required',
|
|
108
128
|
activeTools: hasListedAccessibleWorkspaces
|
|
109
|
-
? [
|
|
129
|
+
? [
|
|
130
|
+
'get_workspace_context',
|
|
131
|
+
'set_workspace_context',
|
|
132
|
+
'search_tools',
|
|
133
|
+
'select_tools',
|
|
134
|
+
]
|
|
110
135
|
: [
|
|
111
136
|
'list_accessible_workspaces',
|
|
112
137
|
'get_workspace_context',
|
|
113
138
|
'set_workspace_context',
|
|
139
|
+
'search_tools',
|
|
114
140
|
'select_tools',
|
|
115
141
|
],
|
|
116
142
|
};
|
|
@@ -131,6 +157,7 @@ export function prepareMiraToolStep({
|
|
|
131
157
|
'get_workspace_context',
|
|
132
158
|
'list_workspace_members',
|
|
133
159
|
...selected,
|
|
160
|
+
'search_tools',
|
|
134
161
|
'select_tools',
|
|
135
162
|
];
|
|
136
163
|
|
|
@@ -141,14 +168,7 @@ export function prepareMiraToolStep({
|
|
|
141
168
|
}
|
|
142
169
|
|
|
143
170
|
if (forceGoogleSearch && !hasToolCallInSteps(steps, 'google_search')) {
|
|
144
|
-
|
|
145
|
-
.filter((toolName) => toolName !== 'no_action_needed')
|
|
146
|
-
.concat('google_search', 'select_tools');
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
toolChoice: 'required',
|
|
150
|
-
activeTools: Array.from(new Set(active)),
|
|
151
|
-
};
|
|
171
|
+
return { toolChoice: 'required', activeTools: ['google_search'] };
|
|
152
172
|
}
|
|
153
173
|
|
|
154
174
|
if (
|
|
@@ -157,7 +177,7 @@ export function prepareMiraToolStep({
|
|
|
157
177
|
) {
|
|
158
178
|
const active = buildActiveToolsFromSelected(toolsForBuild)
|
|
159
179
|
.filter((toolName) => toolName !== 'no_action_needed')
|
|
160
|
-
.concat('run_parallel_checks', 'select_tools');
|
|
180
|
+
.concat('run_parallel_checks', 'search_tools', 'select_tools');
|
|
161
181
|
|
|
162
182
|
return {
|
|
163
183
|
toolChoice: 'required',
|
|
@@ -178,6 +198,7 @@ export function prepareMiraToolStep({
|
|
|
178
198
|
toolName !== 'select_tools' && toolName !== 'no_action_needed'
|
|
179
199
|
),
|
|
180
200
|
'render_ui',
|
|
201
|
+
'search_tools',
|
|
181
202
|
'select_tools',
|
|
182
203
|
];
|
|
183
204
|
|
|
@@ -199,7 +220,7 @@ export function prepareMiraToolStep({
|
|
|
199
220
|
) {
|
|
200
221
|
const active = buildActiveToolsFromSelected(toolsForBuild)
|
|
201
222
|
.filter((toolName) => toolName !== 'no_action_needed')
|
|
202
|
-
.concat('render_ui', 'select_tools');
|
|
223
|
+
.concat('render_ui', 'search_tools', 'select_tools');
|
|
203
224
|
return {
|
|
204
225
|
toolChoice: 'required',
|
|
205
226
|
activeTools: Array.from(new Set(active)),
|
|
@@ -209,7 +230,7 @@ export function prepareMiraToolStep({
|
|
|
209
230
|
if (hasRenderableRenderUiInSteps(steps)) {
|
|
210
231
|
const active = buildActiveToolsFromSelected(toolsForBuild)
|
|
211
232
|
.filter((toolName) => toolName !== 'render_ui')
|
|
212
|
-
.concat('select_tools');
|
|
233
|
+
.concat('search_tools', 'select_tools');
|
|
213
234
|
return {
|
|
214
235
|
activeTools: Array.from(new Set(active)),
|
|
215
236
|
};
|
|
@@ -59,10 +59,6 @@ export async function resolveChatIdForUser(
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
type MoveTempFilesToThreadParams = {
|
|
62
|
-
loadThread: () => PromiseLike<{
|
|
63
|
-
data: { role?: string }[] | null;
|
|
64
|
-
error: { message: string } | null;
|
|
65
|
-
}>;
|
|
66
62
|
listFiles: (tempStoragePath: string) => PromiseLike<{
|
|
67
63
|
data: { name: string }[] | null;
|
|
68
64
|
error: AdminClientLike | null;
|
|
@@ -77,7 +73,6 @@ type MoveTempFilesToThreadParams = {
|
|
|
77
73
|
};
|
|
78
74
|
|
|
79
75
|
export async function moveTempFilesToThread({
|
|
80
|
-
loadThread,
|
|
81
76
|
listFiles,
|
|
82
77
|
moveFile,
|
|
83
78
|
wsId,
|
|
@@ -88,30 +83,21 @@ export async function moveTempFilesToThread({
|
|
|
88
83
|
return null;
|
|
89
84
|
}
|
|
90
85
|
|
|
91
|
-
const { data: thread, error: threadError } = await loadThread();
|
|
92
|
-
|
|
93
|
-
if (threadError) {
|
|
94
|
-
console.error('Error getting thread:', threadError);
|
|
95
|
-
return new Response(threadError.message, { status: 500 });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!thread || thread.length === 0) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
86
|
const tempStoragePath = `${wsId}/chats/ai/resources/temp/${userId}`;
|
|
103
87
|
const { data: files, error: listError } = await listFiles(tempStoragePath);
|
|
104
88
|
|
|
105
89
|
if (listError) {
|
|
106
90
|
console.error('Error getting files:', listError);
|
|
107
|
-
return
|
|
91
|
+
return new Response('Could not load chat attachments. Please retry.', {
|
|
92
|
+
status: 502,
|
|
93
|
+
});
|
|
108
94
|
}
|
|
109
95
|
|
|
110
96
|
if (!files?.length) {
|
|
111
97
|
return null;
|
|
112
98
|
}
|
|
113
99
|
|
|
114
|
-
await Promise.all(
|
|
100
|
+
const moves = await Promise.all(
|
|
115
101
|
files.map(async (file) => {
|
|
116
102
|
const fileName = file.name;
|
|
117
103
|
const { error: copyError } = await moveFile(
|
|
@@ -122,8 +108,15 @@ export async function moveTempFilesToThread({
|
|
|
122
108
|
if (copyError) {
|
|
123
109
|
console.error('File copy error:', { fileName, copyError });
|
|
124
110
|
}
|
|
111
|
+
return copyError;
|
|
125
112
|
})
|
|
126
113
|
);
|
|
127
114
|
|
|
115
|
+
if (moves.some(Boolean)) {
|
|
116
|
+
return new Response('Could not attach every file. Please retry.', {
|
|
117
|
+
status: 502,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
128
121
|
return null;
|
|
129
122
|
}
|
|
@@ -44,15 +44,14 @@ export async function performCreditPreflight({
|
|
|
44
44
|
)
|
|
45
45
|
: null;
|
|
46
46
|
|
|
47
|
-
if (
|
|
48
|
-
cappedMaxOutput === null &&
|
|
49
|
-
creditCheck &&
|
|
50
|
-
creditCheck.remainingCredits <= 0
|
|
51
|
-
) {
|
|
47
|
+
if (cappedMaxOutput === null) {
|
|
52
48
|
return {
|
|
53
49
|
error: Response.json(
|
|
54
|
-
{
|
|
55
|
-
|
|
50
|
+
{
|
|
51
|
+
error: 'AI request budget could not be verified',
|
|
52
|
+
code: 'CREDIT_CHECK_FAILED',
|
|
53
|
+
},
|
|
54
|
+
{ status: 503 }
|
|
56
55
|
),
|
|
57
56
|
};
|
|
58
57
|
}
|
|
@@ -377,3 +377,36 @@ export async function persistLatestUserMessage({
|
|
|
377
377
|
console.log('User message saved to database');
|
|
378
378
|
return null;
|
|
379
379
|
}
|
|
380
|
+
|
|
381
|
+
export function splitSystemMessages(messages: ModelMessage[]) {
|
|
382
|
+
const systemMessages: string[] = [];
|
|
383
|
+
const nonSystemMessages: ModelMessage[] = [];
|
|
384
|
+
for (const message of messages) {
|
|
385
|
+
if (message.role === 'system') {
|
|
386
|
+
const systemMessage = getTextContent(message.content);
|
|
387
|
+
if (systemMessage) systemMessages.push(systemMessage);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
nonSystemMessages.push(message);
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
messages: nonSystemMessages,
|
|
394
|
+
system: systemMessages.join('\n\n').trim(),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function getTextContent(content: ModelMessage['content']) {
|
|
398
|
+
if (typeof content === 'string') return content.trim();
|
|
399
|
+
if (!Array.isArray(content)) return '';
|
|
400
|
+
return content
|
|
401
|
+
.map((part) => (part.type === 'text' ? part.text.trim() : ''))
|
|
402
|
+
.filter(Boolean)
|
|
403
|
+
.join('\n')
|
|
404
|
+
.trim();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function mergeSystemInstructions(...instructions: Array<string | null>) {
|
|
408
|
+
return instructions
|
|
409
|
+
.map((instruction) => instruction?.trim())
|
|
410
|
+
.filter(Boolean)
|
|
411
|
+
.join('\n\n');
|
|
412
|
+
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import type { TypedSupabaseClient } from '@tuturuuu/supabase/next/client';
|
|
2
|
+
import type { PermissionId } from '@tuturuuu/types';
|
|
2
3
|
import { normalizeWorkspaceContextId } from '@tuturuuu/utils/constants';
|
|
3
4
|
import { getPermissions } from '@tuturuuu/utils/workspace-helper';
|
|
4
5
|
import type { NextRequest } from 'next/server';
|
|
5
|
-
import {
|
|
6
|
+
import { createMiraSdkToolSearch } from '../../tools/mira-sdk-tool-search';
|
|
6
7
|
import {
|
|
7
8
|
createMiraStreamTools,
|
|
8
9
|
type MiraToolContext,
|
|
9
10
|
} from '../../tools/mira-tools';
|
|
10
11
|
import type { MiraWorkspaceContextState } from '../../tools/workspace-context';
|
|
11
12
|
import { resolveWorkspaceContextState } from '../../tools/workspace-context';
|
|
13
|
+
import { buildMiraPrompt } from '../mira-prompt';
|
|
12
14
|
import { buildMiraSystemInstruction } from '../mira-system-instruction';
|
|
13
15
|
import type { ChatRequestTaskBoardContext } from './chat-request-schema';
|
|
14
16
|
|
|
15
17
|
type PermissionResultLike = {
|
|
16
|
-
withoutPermission?: (permission:
|
|
18
|
+
withoutPermission?: (permission: PermissionId) => boolean;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
type SupabaseClientLike = TypedSupabaseClient;
|
|
@@ -171,6 +173,9 @@ export async function prepareMiraRuntime({
|
|
|
171
173
|
}: PrepareMiraRuntimeParams): Promise<{
|
|
172
174
|
miraSystemPrompt?: string;
|
|
173
175
|
miraTools?: ReturnType<typeof createMiraStreamTools>;
|
|
176
|
+
prepareMiraDiscoveryStep?: ReturnType<
|
|
177
|
+
typeof createMiraSdkToolSearch
|
|
178
|
+
>['prepareStep'];
|
|
174
179
|
}> {
|
|
175
180
|
if (!isMiraMode || !wsId) {
|
|
176
181
|
return {};
|
|
@@ -188,26 +193,29 @@ export async function prepareMiraRuntime({
|
|
|
188
193
|
});
|
|
189
194
|
} catch (workspaceContextErr) {
|
|
190
195
|
console.error(
|
|
191
|
-
'Failed to
|
|
196
|
+
'Failed to verify Mira workspace context:',
|
|
192
197
|
workspaceContextErr
|
|
193
198
|
);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
name: 'Current workspace',
|
|
198
|
-
personal: false,
|
|
199
|
-
memberCount: 0,
|
|
199
|
+
return {
|
|
200
|
+
miraSystemPrompt:
|
|
201
|
+
'You are Mira. Workspace access could not be verified for this request. Do not claim to read or change workspace data. Explain that the user can retry after access is restored; answer unrelated general questions normally.',
|
|
200
202
|
};
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
let withoutPermission: PermissionResultLike['withoutPermission'];
|
|
204
206
|
const denyPermissionByDefault = () => true;
|
|
205
207
|
withoutPermission = denyPermissionByDefault;
|
|
208
|
+
const permissionCache = new Map<
|
|
209
|
+
string,
|
|
210
|
+
Promise<PermissionResultLike | null>
|
|
211
|
+
>();
|
|
206
212
|
try {
|
|
207
|
-
const
|
|
213
|
+
const pending = getPermissions({
|
|
208
214
|
wsId: resolvedWorkspaceContext.wsId,
|
|
209
215
|
...(user ? { user } : { request }),
|
|
210
|
-
})
|
|
216
|
+
});
|
|
217
|
+
permissionCache.set(resolvedWorkspaceContext.wsId, pending);
|
|
218
|
+
const permissionsResult = await pending;
|
|
211
219
|
if (permissionsResult?.withoutPermission) {
|
|
212
220
|
withoutPermission = permissionsResult.withoutPermission;
|
|
213
221
|
}
|
|
@@ -223,36 +231,46 @@ export async function prepareMiraRuntime({
|
|
|
223
231
|
chatId,
|
|
224
232
|
supabase: miraSupabase,
|
|
225
233
|
timezone,
|
|
234
|
+
requestHeaders: request.headers,
|
|
235
|
+
authorizeWorkspaceTools: async (targetWsId, permissions) => {
|
|
236
|
+
let pending = permissionCache.get(targetWsId);
|
|
237
|
+
if (!pending) {
|
|
238
|
+
pending = getPermissions({
|
|
239
|
+
wsId: targetWsId,
|
|
240
|
+
...(user ? { user } : { request }),
|
|
241
|
+
});
|
|
242
|
+
permissionCache.set(targetWsId, pending);
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const resolved = await pending;
|
|
246
|
+
return (
|
|
247
|
+
!!resolved?.withoutPermission &&
|
|
248
|
+
permissions.every(
|
|
249
|
+
(permission) => !resolved.withoutPermission?.(permission)
|
|
250
|
+
)
|
|
251
|
+
);
|
|
252
|
+
} catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
},
|
|
226
256
|
};
|
|
227
257
|
|
|
228
258
|
let miraSystemPrompt: string;
|
|
229
259
|
try {
|
|
230
|
-
const
|
|
260
|
+
const taskBoardInstruction = await buildTaskBoardContextInstruction({
|
|
261
|
+
resolvedWorkspaceContext,
|
|
262
|
+
supabase: miraSupabase,
|
|
263
|
+
taskBoardContext,
|
|
264
|
+
});
|
|
265
|
+
miraSystemPrompt = await buildMiraPrompt({
|
|
231
266
|
userId,
|
|
232
267
|
wsId: resolvedWorkspaceContext.wsId,
|
|
233
268
|
supabase: miraSupabase,
|
|
234
269
|
timezone,
|
|
235
270
|
withoutPermission,
|
|
271
|
+
workspaceContext: resolvedWorkspaceContext,
|
|
272
|
+
taskBoardInstruction,
|
|
236
273
|
});
|
|
237
|
-
const dynamicInstruction = buildMiraSystemInstruction({
|
|
238
|
-
soul,
|
|
239
|
-
isFirstInteraction,
|
|
240
|
-
withoutPermission,
|
|
241
|
-
});
|
|
242
|
-
const workspaceContextInstruction = `## Workspace Context\n\nCurrent task/calendar/finance workspace context: ${resolvedWorkspaceContext.name} (${resolvedWorkspaceContext.personal ? 'personal' : 'shared'} workspace).\nUse this workspace for "my tasks", "my calendar", and "my finance" requests. Only switch to another workspace when the user explicitly names a different workspace.`;
|
|
243
|
-
const taskBoardContextInstruction = await buildTaskBoardContextInstruction({
|
|
244
|
-
resolvedWorkspaceContext,
|
|
245
|
-
supabase: miraSupabase,
|
|
246
|
-
taskBoardContext,
|
|
247
|
-
});
|
|
248
|
-
miraSystemPrompt = [
|
|
249
|
-
contextString,
|
|
250
|
-
workspaceContextInstruction,
|
|
251
|
-
taskBoardContextInstruction,
|
|
252
|
-
dynamicInstruction,
|
|
253
|
-
]
|
|
254
|
-
.filter(Boolean)
|
|
255
|
-
.join('\n\n');
|
|
256
274
|
} catch (ctxErr) {
|
|
257
275
|
console.error(
|
|
258
276
|
'Failed to build Mira context (continuing with default instruction):',
|
|
@@ -261,8 +279,14 @@ export async function prepareMiraRuntime({
|
|
|
261
279
|
miraSystemPrompt = buildMiraSystemInstruction({ withoutPermission });
|
|
262
280
|
}
|
|
263
281
|
|
|
282
|
+
const discovery = createMiraSdkToolSearch(
|
|
283
|
+
createMiraStreamTools(ctx, withoutPermission, getSteps),
|
|
284
|
+
ctx,
|
|
285
|
+
withoutPermission
|
|
286
|
+
);
|
|
264
287
|
return {
|
|
265
288
|
miraSystemPrompt,
|
|
266
|
-
miraTools:
|
|
289
|
+
miraTools: discovery.tools,
|
|
290
|
+
prepareMiraDiscoveryStep: discovery.prepareStep,
|
|
267
291
|
};
|
|
268
292
|
}
|
package/src/chat/google/route.ts
CHANGED
|
@@ -4,13 +4,7 @@ import {
|
|
|
4
4
|
normalizeWorkspaceId,
|
|
5
5
|
verifyWorkspaceMembershipType,
|
|
6
6
|
} from '@tuturuuu/utils/workspace-helper';
|
|
7
|
-
import {
|
|
8
|
-
consumeStream,
|
|
9
|
-
type ModelMessage,
|
|
10
|
-
smoothStream,
|
|
11
|
-
stepCountIs,
|
|
12
|
-
streamText,
|
|
13
|
-
} from 'ai';
|
|
7
|
+
import { consumeStream, smoothStream, stepCountIs, streamText } from 'ai';
|
|
14
8
|
import { type NextRequest, NextResponse } from 'next/server';
|
|
15
9
|
import { normalizeStableModelId } from '../../credits/model-mapping';
|
|
16
10
|
import {
|
|
@@ -18,8 +12,8 @@ import {
|
|
|
18
12
|
resolvePlanModel,
|
|
19
13
|
} from '../../credits/resolve-plan-model';
|
|
20
14
|
import { withAiMemory } from '../../memory';
|
|
21
|
-
import { createGoogleSearchToolSet } from '../../tools/google-search-tool';
|
|
22
15
|
import type { CreditSource as SharedCreditSource } from '../credit-source';
|
|
16
|
+
import { shouldPresentWorkspaceArtifact } from '../mira-artifact-intent';
|
|
23
17
|
import {
|
|
24
18
|
shouldForceGoogleSearchForLatestUserMessage,
|
|
25
19
|
shouldForceRenderUiForLatestUserMessage,
|
|
@@ -29,6 +23,7 @@ import {
|
|
|
29
23
|
shouldUseParallelChecksForLatestUserMessage,
|
|
30
24
|
} from '../mira-render-ui-policy';
|
|
31
25
|
import { ChatRequestBodySchema, mapToUIMessages } from './chat-request-schema';
|
|
26
|
+
import { resolveChatTools } from './chat-tools';
|
|
32
27
|
import { systemInstruction } from './default-system-instruction';
|
|
33
28
|
import { prepareMiraToolStep } from './mira-step-preparation';
|
|
34
29
|
import { resolveChatReasoningSettings } from './reasoning-settings';
|
|
@@ -50,9 +45,11 @@ import {
|
|
|
50
45
|
import { performCreditPreflight } from './route-credits';
|
|
51
46
|
import {
|
|
52
47
|
extractLatestUserMessageContent,
|
|
48
|
+
mergeSystemInstructions,
|
|
53
49
|
persistLatestUserMessage,
|
|
54
50
|
persistRequestScopedUserMessage,
|
|
55
51
|
prepareProcessedMessages,
|
|
52
|
+
splitSystemMessages,
|
|
56
53
|
} from './route-message-preparation';
|
|
57
54
|
import { prepareMiraRuntime } from './route-mira-runtime';
|
|
58
55
|
import {
|
|
@@ -60,44 +57,6 @@ import {
|
|
|
60
57
|
persistAssistantResponse,
|
|
61
58
|
} from './stream-finish-persistence';
|
|
62
59
|
|
|
63
|
-
function splitSystemMessages(messages: ModelMessage[]) {
|
|
64
|
-
const systemMessages: string[] = [];
|
|
65
|
-
const nonSystemMessages: ModelMessage[] = [];
|
|
66
|
-
|
|
67
|
-
for (const message of messages) {
|
|
68
|
-
if (message.role === 'system') {
|
|
69
|
-
const systemMessage = getTextContent(message.content);
|
|
70
|
-
if (systemMessage) systemMessages.push(systemMessage);
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
nonSystemMessages.push(message);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
messages: nonSystemMessages,
|
|
79
|
-
system: systemMessages.join('\n\n').trim(),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function getTextContent(content: ModelMessage['content']) {
|
|
84
|
-
if (typeof content === 'string') return content.trim();
|
|
85
|
-
if (!Array.isArray(content)) return '';
|
|
86
|
-
|
|
87
|
-
return content
|
|
88
|
-
.map((part) => (part.type === 'text' ? part.text.trim() : ''))
|
|
89
|
-
.filter(Boolean)
|
|
90
|
-
.join('\n')
|
|
91
|
-
.trim();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function mergeSystemInstructions(...instructions: Array<string | null>) {
|
|
95
|
-
return instructions
|
|
96
|
-
.map((instruction) => instruction?.trim())
|
|
97
|
-
.filter(Boolean)
|
|
98
|
-
.join('\n\n');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
60
|
export function createPOST(
|
|
102
61
|
_options: {
|
|
103
62
|
serverAPIKeyFallback?: boolean;
|
|
@@ -368,12 +327,6 @@ export function createPOST(
|
|
|
368
327
|
const chatId = resolvedChatId.chatId;
|
|
369
328
|
|
|
370
329
|
const moveFilesError = await moveTempFilesToThread({
|
|
371
|
-
loadThread: () =>
|
|
372
|
-
sbAdmin
|
|
373
|
-
.from('ai_chat_messages')
|
|
374
|
-
.select('role, ai_chats!chat_id!inner(creator_id)')
|
|
375
|
-
.eq('chat_id', chatId)
|
|
376
|
-
.eq('ai_chats.creator_id', user.id),
|
|
377
330
|
listFiles: (tempStoragePath) =>
|
|
378
331
|
sbAdmin.storage.from('workspaces').list(tempStoragePath),
|
|
379
332
|
moveFile: (fromPath, toPath) =>
|
|
@@ -480,21 +433,22 @@ export function createPOST(
|
|
|
480
433
|
|
|
481
434
|
const stepsRef: { current: unknown[] } = { current: [] };
|
|
482
435
|
|
|
483
|
-
const { miraSystemPrompt, miraTools } =
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
436
|
+
const { miraSystemPrompt, miraTools, prepareMiraDiscoveryStep } =
|
|
437
|
+
await prepareMiraRuntime({
|
|
438
|
+
isMiraMode,
|
|
439
|
+
wsId: normalizedWsId ?? undefined,
|
|
440
|
+
workspaceContextId,
|
|
441
|
+
creditWsId: billingWsId ?? normalizedWsId ?? undefined,
|
|
442
|
+
request: req,
|
|
443
|
+
user,
|
|
444
|
+
userId: user.id,
|
|
445
|
+
chatId,
|
|
446
|
+
supabase,
|
|
447
|
+
toolSupabase: sbAdmin as typeof supabase,
|
|
448
|
+
timezone,
|
|
449
|
+
taskBoardContext,
|
|
450
|
+
getSteps: () => stepsRef.current,
|
|
451
|
+
});
|
|
498
452
|
|
|
499
453
|
const effectiveSource = isMiraMode ? 'Mira' : 'Rewise';
|
|
500
454
|
|
|
@@ -509,6 +463,9 @@ export function createPOST(
|
|
|
509
463
|
});
|
|
510
464
|
|
|
511
465
|
const reasoningSettings = resolveChatReasoningSettings(thinkingMode);
|
|
466
|
+
const forceWorkspaceArtifact = shouldPresentWorkspaceArtifact(
|
|
467
|
+
promptMessages.messages
|
|
468
|
+
);
|
|
512
469
|
const forceRenderUi = shouldForceRenderUiForLatestUserMessage(
|
|
513
470
|
promptMessages.messages
|
|
514
471
|
);
|
|
@@ -529,14 +486,13 @@ export function createPOST(
|
|
|
529
486
|
promptMessages.messages
|
|
530
487
|
);
|
|
531
488
|
|
|
532
|
-
const googleSearchTool = createGoogleSearchToolSet();
|
|
533
|
-
|
|
534
489
|
type PrepareStep = NonNullable<
|
|
535
490
|
NonNullable<Parameters<typeof streamText>[0]>['prepareStep']
|
|
536
491
|
>;
|
|
537
|
-
const prepareStep: PrepareStep = ({ steps }) => {
|
|
492
|
+
const prepareStep: PrepareStep = async ({ steps }) => {
|
|
538
493
|
stepsRef.current = steps;
|
|
539
|
-
|
|
494
|
+
const policy = prepareMiraToolStep({
|
|
495
|
+
forceWorkspaceArtifact,
|
|
540
496
|
steps,
|
|
541
497
|
forceGoogleSearch,
|
|
542
498
|
forceRenderUi,
|
|
@@ -545,6 +501,9 @@ export function createPOST(
|
|
|
545
501
|
needsWorkspaceMembersTool,
|
|
546
502
|
preferMarkdownTables,
|
|
547
503
|
});
|
|
504
|
+
return prepareMiraDiscoveryStep
|
|
505
|
+
? prepareMiraDiscoveryStep(policy)
|
|
506
|
+
: policy;
|
|
548
507
|
};
|
|
549
508
|
|
|
550
509
|
const { lease: claimedLease, response: leaseResponse } =
|
|
@@ -597,20 +556,16 @@ export function createPOST(
|
|
|
597
556
|
promptMessages.system
|
|
598
557
|
),
|
|
599
558
|
...(cappedMaxOutput ? { maxOutputTokens: cappedMaxOutput } : {}),
|
|
559
|
+
tools: resolveChatTools(miraTools),
|
|
600
560
|
...(miraTools
|
|
601
561
|
? {
|
|
602
|
-
tools: { ...miraTools, ...googleSearchTool } as NonNullable<
|
|
603
|
-
Parameters<typeof streamText>[0]
|
|
604
|
-
>['tools'],
|
|
605
562
|
stopWhen: stepCountIs(25),
|
|
606
563
|
toolChoice: 'auto' as const,
|
|
607
564
|
prepareStep: prepareStep as NonNullable<
|
|
608
565
|
NonNullable<Parameters<typeof streamText>[0]>['prepareStep']
|
|
609
566
|
>,
|
|
610
567
|
}
|
|
611
|
-
: {
|
|
612
|
-
tools: googleSearchTool,
|
|
613
|
-
}),
|
|
568
|
+
: {}),
|
|
614
569
|
providerOptions: {
|
|
615
570
|
google: {
|
|
616
571
|
thinkingConfig: {
|