@tuturuuu/ai 0.12.0 → 0.14.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/google-search-usage.ts +62 -0
- package/src/chat/google/mira-step-preparation.ts +37 -16
- 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 -71
- 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 +8 -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 +4 -1
- package/src/meetings/gemini.ts +72 -20
- 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 +22 -3
- 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
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { DEV_MODE } from '@tuturuuu/utils/constants';
|
|
2
|
+
import { MIRA_TOOL_DIRECTORY } from './mira-tool-metadata';
|
|
3
|
+
import type { MiraToolName } from './mira-tool-names';
|
|
4
|
+
|
|
5
|
+
const normalize = (value: string) =>
|
|
6
|
+
value
|
|
7
|
+
.normalize('NFD')
|
|
8
|
+
.replace(/\p{Diacritic}/gu, '')
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/_/g, ' ');
|
|
11
|
+
const matchesTerm = (word: string, term: string) =>
|
|
12
|
+
word.startsWith(term) || term === `${word}s`;
|
|
13
|
+
const domains = {
|
|
14
|
+
tasks: {
|
|
15
|
+
keywords:
|
|
16
|
+
'task tasks board list label project assignee assign priority deadline cong viec nhiem vu nhan du an',
|
|
17
|
+
matches: /task|board|project/,
|
|
18
|
+
guidance:
|
|
19
|
+
'Discover board/list and member/label/project IDs before writes. Tasks require a list. Read a task before editing ambiguous fields. Use assignment tools for labels, projects, and people. Dates use the user timezone. Keep operations in the requested workspace; report the workspace and resulting task ID after creation.',
|
|
20
|
+
},
|
|
21
|
+
calendar: {
|
|
22
|
+
keywords:
|
|
23
|
+
'calendar event schedule scheduling google microsoft sync reclaim lock availability habit focus meeting lich dong bo su kien',
|
|
24
|
+
matches: /calendar|event|schedule|e2ee/,
|
|
25
|
+
guidance:
|
|
26
|
+
'Use existing connected accounts; an unconnected Google account needs user OAuth consent. Inspect sync health before retrying. Locked events must stay fixed. Preview scheduling before applying it; preserve hard commitments and explain conflicts. Use actual priorities, deadlines, availability, and durations, never invented free time.',
|
|
27
|
+
},
|
|
28
|
+
finance: {
|
|
29
|
+
keywords:
|
|
30
|
+
'finance wallet transaction money expense income currency category tag tai chinh vi giao dich',
|
|
31
|
+
matches: /wallet|transaction|spending|currency/,
|
|
32
|
+
guidance:
|
|
33
|
+
'Discover wallets and categories before writes. Positive amounts are income, negative amounts expenses. Keep currencies separate. Report actual tool outcomes and never infer a successful financial write from a visualization.',
|
|
34
|
+
},
|
|
35
|
+
time: {
|
|
36
|
+
keywords: 'time timer tracking goal hours duration thoi gian theo doi',
|
|
37
|
+
matches: /timer|time_track/,
|
|
38
|
+
guidance:
|
|
39
|
+
'Discover categories and sessions before edits. Respect the user timezone and running session state. Use history pagination and date filters to keep results small.',
|
|
40
|
+
},
|
|
41
|
+
memory: {
|
|
42
|
+
keywords:
|
|
43
|
+
'memory remember recall preference personality name settings nho so thich',
|
|
44
|
+
matches: /memor|recall|remember|settings|user_name/,
|
|
45
|
+
guidance:
|
|
46
|
+
'Recall relevant memories before personalization. Store durable preferences, not transient tool output. Behavior settings use update_my_settings; user naming uses update_user_name. Avoid duplicate memories.',
|
|
47
|
+
},
|
|
48
|
+
workspace: {
|
|
49
|
+
keywords:
|
|
50
|
+
'workspace member context sidebar artifact panel theme layout screen khong gian thanh ben giao dien',
|
|
51
|
+
matches: /workspace|sidebar|theme|immersive/,
|
|
52
|
+
guidance:
|
|
53
|
+
'Resolve named workspaces before operations. Opening artifacts fetches data in the UI, not in the model. Keep explanations in chat. Make each UI change once and finish after success. Replace a panel with close_artifact plus show_workspace_artifact; focus_artifact closes all other panels.',
|
|
54
|
+
},
|
|
55
|
+
creative: {
|
|
56
|
+
keywords:
|
|
57
|
+
'image qr file document convert markdown visualization render hinh anh tep tai lieu',
|
|
58
|
+
matches: /image|qr_code|convert_file|render_ui/,
|
|
59
|
+
guidance:
|
|
60
|
+
'Use Markdown for code, math, diagrams, and tables. Convert attached documents when their contents are needed. Google models receive YouTube URLs as video input; do not send YouTube links to file conversion. Use image generation only for requested images.',
|
|
61
|
+
},
|
|
62
|
+
research: {
|
|
63
|
+
keywords:
|
|
64
|
+
'search web current news weather research verify tim kiem tin tuc thoi tiet',
|
|
65
|
+
matches: /google_search|parallel_checks/,
|
|
66
|
+
guidance:
|
|
67
|
+
'Use web search for current external facts and cite sources. Use parallel checks for explicit deeper verification; summarize the results rather than dumping raw output.',
|
|
68
|
+
},
|
|
69
|
+
} as const;
|
|
70
|
+
|
|
71
|
+
export function searchMiraTools(
|
|
72
|
+
args: { query: string; limit?: number },
|
|
73
|
+
allowed?: ReadonlySet<string>
|
|
74
|
+
) {
|
|
75
|
+
const query = normalize(args.query);
|
|
76
|
+
const terms = query
|
|
77
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
78
|
+
.filter((term) => term.length > 2);
|
|
79
|
+
const matchingDomains = Object.entries(domains).filter(([, domain]) =>
|
|
80
|
+
terms.some((term) =>
|
|
81
|
+
normalize(domain.keywords)
|
|
82
|
+
.split(' ')
|
|
83
|
+
.some((word) => word === term || term === `${word}s`)
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
const matches = (
|
|
87
|
+
Object.entries(MIRA_TOOL_DIRECTORY) as [MiraToolName, string][]
|
|
88
|
+
)
|
|
89
|
+
.filter(
|
|
90
|
+
([name]) =>
|
|
91
|
+
!['search_tools', 'select_tools', 'no_action_needed'].includes(name) &&
|
|
92
|
+
(DEV_MODE || name !== 'render_ui') &&
|
|
93
|
+
(!allowed || allowed.has(name))
|
|
94
|
+
)
|
|
95
|
+
.map(([name, description]) => {
|
|
96
|
+
const words = normalize(`${name} ${description}`).split(
|
|
97
|
+
/[^\p{L}\p{N}]+/u
|
|
98
|
+
);
|
|
99
|
+
const nameWords = normalize(name).split(' ');
|
|
100
|
+
const score =
|
|
101
|
+
(normalize(name) === query ? 100 : 0) +
|
|
102
|
+
terms.reduce(
|
|
103
|
+
(total, term) =>
|
|
104
|
+
total +
|
|
105
|
+
(nameWords.some((word) => matchesTerm(word, term))
|
|
106
|
+
? 6
|
|
107
|
+
: words.some((word) => matchesTerm(word, term))
|
|
108
|
+
? 2
|
|
109
|
+
: 0),
|
|
110
|
+
0
|
|
111
|
+
) +
|
|
112
|
+
matchingDomains.reduce(
|
|
113
|
+
(total, [, domain]) => total + (domain.matches.test(name) ? 1 : 0),
|
|
114
|
+
0
|
|
115
|
+
);
|
|
116
|
+
return { name, description, score };
|
|
117
|
+
})
|
|
118
|
+
.filter((entry) => entry.score > 0)
|
|
119
|
+
.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
|
|
120
|
+
.slice(0, Math.min(8, Math.max(1, args.limit ?? 6)));
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
selectedTools: matches.map(({ name }) => name),
|
|
124
|
+
matches: matches.map(({ name, description }) => ({ name, description })),
|
|
125
|
+
guidance: matchingDomains.map(([domain, { guidance }]) => ({
|
|
126
|
+
domain,
|
|
127
|
+
guidance,
|
|
128
|
+
})),
|
|
129
|
+
...(matches.length
|
|
130
|
+
? {
|
|
131
|
+
next: 'Use the activated tools. Search again only for a different operation or missing capability.',
|
|
132
|
+
}
|
|
133
|
+
: {
|
|
134
|
+
next: 'No matching authorized tools. Try a specific app or operation.',
|
|
135
|
+
domains: Object.keys(domains),
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
executeGetUpcomingEvents,
|
|
7
7
|
executeUpdateEvent,
|
|
8
8
|
} from './executors/calendar';
|
|
9
|
+
import { executeCalendarAutomation } from './executors/calendar-automation';
|
|
9
10
|
import { executeSetImmersiveMode } from './executors/chat';
|
|
10
11
|
import {
|
|
11
12
|
executeCreateTransactionCategory,
|
|
@@ -41,6 +42,7 @@ import { executeParallelChecks } from './executors/parallel-checks';
|
|
|
41
42
|
import { executeCreateQrCode } from './executors/qr';
|
|
42
43
|
import { executeGoogleSearch } from './executors/search';
|
|
43
44
|
import { executeUpdateMySettings } from './executors/settings';
|
|
45
|
+
import { executeGetTask, executeSearchTasks } from './executors/task-read';
|
|
44
46
|
import {
|
|
45
47
|
executeAddTaskAssignee,
|
|
46
48
|
executeAddTaskLabels,
|
|
@@ -70,7 +72,12 @@ import {
|
|
|
70
72
|
executeUpdateTaskLabel,
|
|
71
73
|
executeUpdateTaskList,
|
|
72
74
|
} from './executors/tasks';
|
|
73
|
-
import {
|
|
75
|
+
import {
|
|
76
|
+
executeManageWorkspace,
|
|
77
|
+
executeSetSidebar,
|
|
78
|
+
executeSetTheme,
|
|
79
|
+
executeShowWorkspaceArtifact,
|
|
80
|
+
} from './executors/theme';
|
|
74
81
|
import {
|
|
75
82
|
executeCreateTimeTrackerGoal,
|
|
76
83
|
executeCreateTimeTrackingCategory,
|
|
@@ -98,6 +105,7 @@ import {
|
|
|
98
105
|
executeSetWorkspaceContext,
|
|
99
106
|
} from './executors/workspace';
|
|
100
107
|
import type { DefinedMiraToolName } from './mira-tool-definitions';
|
|
108
|
+
import { searchMiraTools } from './mira-tool-discovery';
|
|
101
109
|
import {
|
|
102
110
|
buildRenderUiRecoverySpec,
|
|
103
111
|
isRenderableRenderUiSpec,
|
|
@@ -111,12 +119,19 @@ type ToolHandler = (
|
|
|
111
119
|
) => Promise<unknown> | unknown;
|
|
112
120
|
|
|
113
121
|
const toolHandlers = {
|
|
122
|
+
search_tools: (args) =>
|
|
123
|
+
searchMiraTools({
|
|
124
|
+
query: String(args.query ?? ''),
|
|
125
|
+
limit: typeof args.limit === 'number' ? args.limit : undefined,
|
|
126
|
+
}),
|
|
114
127
|
select_tools: (args) => ({ ok: true, selectedTools: args.tools }),
|
|
115
128
|
no_action_needed: () => ({ ok: true }),
|
|
116
129
|
google_search: executeGoogleSearch,
|
|
117
130
|
run_parallel_checks: (args, ctx, options) =>
|
|
118
131
|
executeParallelChecks(args, ctx, options),
|
|
119
132
|
|
|
133
|
+
get_task: executeGetTask,
|
|
134
|
+
search_tasks: executeSearchTasks,
|
|
120
135
|
get_my_tasks: executeGetMyTasks,
|
|
121
136
|
create_task: executeCreateTask,
|
|
122
137
|
complete_task: executeCompleteTask,
|
|
@@ -145,6 +160,20 @@ const toolHandlers = {
|
|
|
145
160
|
add_task_assignee: executeAddTaskAssignee,
|
|
146
161
|
remove_task_assignee: executeRemoveTaskAssignee,
|
|
147
162
|
|
|
163
|
+
get_calendar_connections: (args, ctx) =>
|
|
164
|
+
executeCalendarAutomation('get_calendar_connections', args, ctx),
|
|
165
|
+
connect_google_calendar: (args, ctx) =>
|
|
166
|
+
executeCalendarAutomation('connect_google_calendar', args, ctx),
|
|
167
|
+
sync_calendar: (args, ctx) =>
|
|
168
|
+
executeCalendarAutomation('sync_calendar', args, ctx),
|
|
169
|
+
set_event_locked: (args, ctx) =>
|
|
170
|
+
executeCalendarAutomation('set_event_locked', args, ctx),
|
|
171
|
+
preview_calendar_schedule: (args, ctx) =>
|
|
172
|
+
executeCalendarAutomation('preview_calendar_schedule', args, ctx),
|
|
173
|
+
apply_calendar_schedule: (args, ctx) =>
|
|
174
|
+
executeCalendarAutomation('apply_calendar_schedule', args, ctx),
|
|
175
|
+
get_schedulable_tasks: (args, ctx) =>
|
|
176
|
+
executeCalendarAutomation('get_schedulable_tasks', args, ctx),
|
|
148
177
|
get_upcoming_events: executeGetUpcomingEvents,
|
|
149
178
|
create_event: executeCreateEvent,
|
|
150
179
|
update_event: executeUpdateEvent,
|
|
@@ -205,6 +234,9 @@ const toolHandlers = {
|
|
|
205
234
|
update_my_settings: executeUpdateMySettings,
|
|
206
235
|
set_default_currency: executeSetDefaultCurrency,
|
|
207
236
|
set_theme: executeSetTheme,
|
|
237
|
+
set_sidebar: executeSetSidebar,
|
|
238
|
+
manage_workspace: executeManageWorkspace,
|
|
239
|
+
show_workspace_artifact: executeShowWorkspaceArtifact,
|
|
208
240
|
list_workspace_members: executeListWorkspaceMembers,
|
|
209
241
|
update_user_name: executeUpdateUserName,
|
|
210
242
|
set_immersive_mode: executeSetImmersiveMode,
|
|
@@ -2,7 +2,10 @@ import type { PermissionId } from '@tuturuuu/types';
|
|
|
2
2
|
import type { MiraToolName } from './mira-tool-names';
|
|
3
3
|
|
|
4
4
|
export const MIRA_TOOL_DIRECTORY: Record<MiraToolName, string> = {
|
|
5
|
-
select_tools: '
|
|
5
|
+
select_tools: 'Activate a small working set of tools',
|
|
6
|
+
search_tools: 'Discover tools and domain guidance for an operation',
|
|
7
|
+
get_task: 'Read saved task details and location by ID',
|
|
8
|
+
search_tasks: 'Search tasks by title with pagination',
|
|
6
9
|
get_my_tasks: 'Get your tasks by status (overdue, today, upcoming)',
|
|
7
10
|
create_task: 'Create a new task',
|
|
8
11
|
complete_task: 'Mark a task as completed',
|
|
@@ -30,6 +33,20 @@ export const MIRA_TOOL_DIRECTORY: Record<MiraToolName, string> = {
|
|
|
30
33
|
remove_task_from_project: 'Unlink a task from a project',
|
|
31
34
|
add_task_assignee: 'Assign a user to a task',
|
|
32
35
|
remove_task_assignee: 'Remove a user from a task',
|
|
36
|
+
get_calendar_connections:
|
|
37
|
+
'List linked external calendar accounts and their sync status',
|
|
38
|
+
connect_google_calendar:
|
|
39
|
+
'Start the Google Calendar OAuth connection flow for this workspace',
|
|
40
|
+
sync_calendar:
|
|
41
|
+
'Synchronize events with a connected external calendar in a given direction',
|
|
42
|
+
set_event_locked:
|
|
43
|
+
'Lock or unlock a calendar event so automatic scheduling cannot move it',
|
|
44
|
+
preview_calendar_schedule:
|
|
45
|
+
'Preview a proposed schedule for tasks without writing events',
|
|
46
|
+
apply_calendar_schedule:
|
|
47
|
+
'Apply a previously previewed schedule and create calendar events',
|
|
48
|
+
get_schedulable_tasks:
|
|
49
|
+
'List tasks eligible for automatic calendar scheduling',
|
|
33
50
|
get_upcoming_events: 'Get calendar events for the next N days',
|
|
34
51
|
create_event: 'Create a calendar event',
|
|
35
52
|
update_event: 'Update a calendar event',
|
|
@@ -102,6 +119,10 @@ export const MIRA_TOOL_DIRECTORY: Record<MiraToolName, string> = {
|
|
|
102
119
|
set_workspace_context:
|
|
103
120
|
'Switch the current workspace context for task, calendar, and finance tools',
|
|
104
121
|
update_my_settings: "Update the assistant's personality settings",
|
|
122
|
+
manage_workspace: 'Close, focus, or arrange the conversation workspace',
|
|
123
|
+
set_sidebar: 'Control navigation sidebar visibility and hover expansion',
|
|
124
|
+
show_workspace_artifact:
|
|
125
|
+
'Open tasks, calendar, finance, or meetings beside the chat',
|
|
105
126
|
set_theme: 'Switch dark mode, light mode, or system theme',
|
|
106
127
|
render_ui:
|
|
107
128
|
'Generate an interactive, actionable UI component or widget instead of plain text when it significantly improves user experience (e.g. for forms, dashboards, blog-like insight layouts, or data visualization).',
|
|
@@ -136,6 +157,13 @@ export const MIRA_TOOL_PERMISSIONS: Partial<
|
|
|
136
157
|
remove_task_from_project: 'manage_projects',
|
|
137
158
|
add_task_assignee: 'manage_projects',
|
|
138
159
|
remove_task_assignee: 'manage_projects',
|
|
160
|
+
get_calendar_connections: 'manage_calendar',
|
|
161
|
+
connect_google_calendar: 'manage_calendar',
|
|
162
|
+
sync_calendar: 'manage_calendar',
|
|
163
|
+
set_event_locked: 'manage_calendar',
|
|
164
|
+
preview_calendar_schedule: 'manage_calendar',
|
|
165
|
+
apply_calendar_schedule: 'manage_calendar',
|
|
166
|
+
get_schedulable_tasks: 'manage_calendar',
|
|
139
167
|
get_upcoming_events: 'manage_calendar',
|
|
140
168
|
create_event: 'manage_calendar',
|
|
141
169
|
update_event: 'manage_calendar',
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export const MIRA_TOOL_NAMES = [
|
|
2
2
|
'select_tools',
|
|
3
|
+
'search_tools',
|
|
4
|
+
'get_task',
|
|
5
|
+
'search_tasks',
|
|
3
6
|
'get_my_tasks',
|
|
4
7
|
'create_task',
|
|
5
8
|
'complete_task',
|
|
@@ -27,6 +30,13 @@ export const MIRA_TOOL_NAMES = [
|
|
|
27
30
|
'remove_task_from_project',
|
|
28
31
|
'add_task_assignee',
|
|
29
32
|
'remove_task_assignee',
|
|
33
|
+
'get_calendar_connections',
|
|
34
|
+
'connect_google_calendar',
|
|
35
|
+
'sync_calendar',
|
|
36
|
+
'set_event_locked',
|
|
37
|
+
'preview_calendar_schedule',
|
|
38
|
+
'apply_calendar_schedule',
|
|
39
|
+
'get_schedulable_tasks',
|
|
30
40
|
'get_upcoming_events',
|
|
31
41
|
'create_event',
|
|
32
42
|
'update_event',
|
|
@@ -84,6 +94,9 @@ export const MIRA_TOOL_NAMES = [
|
|
|
84
94
|
'set_workspace_context',
|
|
85
95
|
'update_my_settings',
|
|
86
96
|
'set_theme',
|
|
97
|
+
'set_sidebar',
|
|
98
|
+
'show_workspace_artifact',
|
|
99
|
+
'manage_workspace',
|
|
87
100
|
'render_ui',
|
|
88
101
|
'list_workspace_members',
|
|
89
102
|
'update_user_name',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TypedSupabaseClient } from '@tuturuuu/supabase/next/client';
|
|
2
|
+
import type { PermissionId } from '@tuturuuu/types';
|
|
2
3
|
import type { MiraWorkspaceContextState } from './workspace-context';
|
|
3
4
|
|
|
4
5
|
export interface MiraToolContext {
|
|
@@ -9,6 +10,12 @@ export interface MiraToolContext {
|
|
|
9
10
|
chatId?: string;
|
|
10
11
|
supabase: TypedSupabaseClient;
|
|
11
12
|
timezone?: string;
|
|
13
|
+
requestHeaders?: Pick<Headers, 'get'>;
|
|
14
|
+
executionState?: object;
|
|
15
|
+
authorizeWorkspaceTools?: (
|
|
16
|
+
wsId: string,
|
|
17
|
+
permissions: PermissionId[]
|
|
18
|
+
) => Promise<boolean>;
|
|
12
19
|
canReadUserGroupStorage?: (input: {
|
|
13
20
|
groupId: string;
|
|
14
21
|
storagePath: string;
|
package/src/tools/mira-tools.ts
CHANGED
|
@@ -2,7 +2,9 @@ import type { PermissionId } from '@tuturuuu/types';
|
|
|
2
2
|
import { DEV_MODE } from '@tuturuuu/utils/constants';
|
|
3
3
|
import type { Tool, ToolSet } from 'ai';
|
|
4
4
|
import { createStreamRenderUiTool } from './definitions/render-ui';
|
|
5
|
+
import { createTaskWriteGuard } from './mira-task-write-guard';
|
|
5
6
|
import { miraToolDefinitions } from './mira-tool-definitions';
|
|
7
|
+
import { searchMiraTools } from './mira-tool-discovery';
|
|
6
8
|
import { executeMiraTool } from './mira-tool-dispatcher';
|
|
7
9
|
import {
|
|
8
10
|
MIRA_TOOL_DIRECTORY,
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
isRenderableRenderUiSpec,
|
|
15
17
|
} from './mira-tool-render-ui';
|
|
16
18
|
import type { MiraToolContext } from './mira-tool-types';
|
|
19
|
+
import { getWorkspaceContextWorkspaceId } from './workspace-context';
|
|
17
20
|
|
|
18
21
|
export type { MiraToolContext } from './mira-tool-types';
|
|
19
22
|
export type { MiraToolName };
|
|
@@ -31,6 +34,9 @@ export function createMiraStreamTools(
|
|
|
31
34
|
): ToolSet {
|
|
32
35
|
const tools: ToolSet = {};
|
|
33
36
|
let renderUiInvalidAttempts = 0;
|
|
37
|
+
const permittedNames = new Set<string>();
|
|
38
|
+
const guardTaskWrite = createTaskWriteGuard();
|
|
39
|
+
ctx.executionState ??= {};
|
|
34
40
|
|
|
35
41
|
// Create a per-stream render_ui tool with stateful preprocessor.
|
|
36
42
|
// The preprocessor auto-populates a context-aware fallback on the first
|
|
@@ -63,7 +69,38 @@ export function createMiraStreamTools(
|
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
if (isMissingPermission)
|
|
72
|
+
if (!isMissingPermission) permittedNames.add(name);
|
|
73
|
+
|
|
74
|
+
if (name === 'search_tools') {
|
|
75
|
+
tools[name] = {
|
|
76
|
+
...def,
|
|
77
|
+
execute: async (args: { query: string; limit?: number }) => {
|
|
78
|
+
const workspaceId = getWorkspaceContextWorkspaceId(ctx);
|
|
79
|
+
const allowed = new Set<string>();
|
|
80
|
+
for (const candidate of Object.keys(
|
|
81
|
+
miraToolDefinitions
|
|
82
|
+
) as MiraToolName[]) {
|
|
83
|
+
const permission = MIRA_TOOL_PERMISSIONS[candidate];
|
|
84
|
+
const required = permission
|
|
85
|
+
? Array.isArray(permission)
|
|
86
|
+
? permission
|
|
87
|
+
: [permission]
|
|
88
|
+
: [];
|
|
89
|
+
if (
|
|
90
|
+
ctx.authorizeWorkspaceTools
|
|
91
|
+
? !required.length ||
|
|
92
|
+
(await ctx.authorizeWorkspaceTools(workspaceId, required))
|
|
93
|
+
: permittedNames.has(candidate)
|
|
94
|
+
)
|
|
95
|
+
allowed.add(candidate);
|
|
96
|
+
}
|
|
97
|
+
return searchMiraTools(args, allowed);
|
|
98
|
+
},
|
|
99
|
+
} as Tool;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (isMissingPermission && !ctx.authorizeWorkspaceTools) {
|
|
67
104
|
tools[name] = {
|
|
68
105
|
...def,
|
|
69
106
|
execute: async () => ({
|
|
@@ -159,7 +196,38 @@ export function createMiraStreamTools(
|
|
|
159
196
|
execute: async (
|
|
160
197
|
args: Record<string, unknown>,
|
|
161
198
|
options?: { abortSignal?: AbortSignal }
|
|
162
|
-
) =>
|
|
199
|
+
) => {
|
|
200
|
+
const executionCtx =
|
|
201
|
+
name === 'set_workspace_context'
|
|
202
|
+
? ctx
|
|
203
|
+
: {
|
|
204
|
+
...ctx,
|
|
205
|
+
workspaceContext: ctx.workspaceContext
|
|
206
|
+
? { ...ctx.workspaceContext }
|
|
207
|
+
: undefined,
|
|
208
|
+
};
|
|
209
|
+
const targetWsId = getWorkspaceContextWorkspaceId(executionCtx);
|
|
210
|
+
const required = requiredPerm
|
|
211
|
+
? Array.isArray(requiredPerm)
|
|
212
|
+
? requiredPerm
|
|
213
|
+
: [requiredPerm]
|
|
214
|
+
: [];
|
|
215
|
+
if (
|
|
216
|
+
required.length &&
|
|
217
|
+
ctx.authorizeWorkspaceTools &&
|
|
218
|
+
!(await ctx.authorizeWorkspaceTools(targetWsId, required))
|
|
219
|
+
)
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
error:
|
|
223
|
+
'The current workspace does not grant permission for this operation.',
|
|
224
|
+
};
|
|
225
|
+
return name === 'create_task'
|
|
226
|
+
? guardTaskWrite(targetWsId, args, () =>
|
|
227
|
+
executeMiraTool(name, args, executionCtx, options)
|
|
228
|
+
)
|
|
229
|
+
: executeMiraTool(name, args, executionCtx, options);
|
|
230
|
+
},
|
|
163
231
|
} as Tool;
|
|
164
232
|
}
|
|
165
233
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const artifactKindSchema = z.enum([
|
|
4
|
+
'tasks',
|
|
5
|
+
'calendar',
|
|
6
|
+
'finance',
|
|
7
|
+
'meetings',
|
|
8
|
+
]);
|
|
9
|
+
export const artifactLayoutSchema = z.enum([
|
|
10
|
+
'auto',
|
|
11
|
+
'horizontal',
|
|
12
|
+
'vertical',
|
|
13
|
+
'grid',
|
|
14
|
+
]);
|
|
15
|
+
export const artifactPresentationSchema = z.object({
|
|
16
|
+
title: z.string().trim().min(1).max(80).optional(),
|
|
17
|
+
description: z.string().trim().max(240).optional(),
|
|
18
|
+
highlights: z.array(z.string().trim().min(1).max(160)).max(3).optional(),
|
|
19
|
+
search: z.string().trim().max(120).optional(),
|
|
20
|
+
taskStatus: z.enum(['all', 'overdue', 'today', 'upcoming']).optional(),
|
|
21
|
+
currency: z
|
|
22
|
+
.string()
|
|
23
|
+
.trim()
|
|
24
|
+
.max(8)
|
|
25
|
+
.toUpperCase()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('Finance only: ISO currency code'),
|
|
28
|
+
date: z.iso
|
|
29
|
+
.date()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('Calendar only: start the seven-day view on this local date'),
|
|
32
|
+
itemIds: z.array(z.string().min(1).max(80)).max(50).optional(),
|
|
33
|
+
});
|
|
34
|
+
export type ArtifactPresentation = z.infer<typeof artifactPresentationSchema>;
|
|
35
|
+
export const showWorkspaceArtifactSchema = z.object({
|
|
36
|
+
kind: artifactKindSchema,
|
|
37
|
+
layout: artifactLayoutSchema.optional(),
|
|
38
|
+
presentation: artifactPresentationSchema.optional(),
|
|
39
|
+
});
|
|
40
|
+
export const manageWorkspaceSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
operation: z.enum([
|
|
43
|
+
'close_artifact',
|
|
44
|
+
'close_all',
|
|
45
|
+
'focus_artifact',
|
|
46
|
+
'set_layout',
|
|
47
|
+
]),
|
|
48
|
+
kind: artifactKindSchema.optional(),
|
|
49
|
+
layout: artifactLayoutSchema.optional(),
|
|
50
|
+
})
|
|
51
|
+
.refine(
|
|
52
|
+
(value) =>
|
|
53
|
+
value.operation === 'close_all' ||
|
|
54
|
+
(value.operation === 'set_layout' ? !!value.layout : !!value.kind),
|
|
55
|
+
{
|
|
56
|
+
message: 'Provide kind for artifact operations or layout for set_layout.',
|
|
57
|
+
}
|
|
58
|
+
);
|