@tuturuuu/ai 0.0.10

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 (130) hide show
  1. package/README.md +76 -0
  2. package/package.json +106 -0
  3. package/src/api-key-hash.ts +28 -0
  4. package/src/calendar/events.ts +34 -0
  5. package/src/calendar/route.ts +114 -0
  6. package/src/chat/credit-source.ts +1 -0
  7. package/src/chat/google/chat-request-schema.ts +150 -0
  8. package/src/chat/google/default-system-instruction.ts +198 -0
  9. package/src/chat/google/message-file-processing.ts +212 -0
  10. package/src/chat/google/mira-step-preparation.ts +221 -0
  11. package/src/chat/google/new/route.ts +368 -0
  12. package/src/chat/google/route-auth.ts +81 -0
  13. package/src/chat/google/route-chat-resolution.ts +98 -0
  14. package/src/chat/google/route-credits.ts +61 -0
  15. package/src/chat/google/route-message-preparation.ts +331 -0
  16. package/src/chat/google/route-mira-runtime.ts +206 -0
  17. package/src/chat/google/route.ts +632 -0
  18. package/src/chat/google/stream-finish-persistence.ts +722 -0
  19. package/src/chat/google/summary/route.ts +153 -0
  20. package/src/chat/mira-render-ui-policy.ts +540 -0
  21. package/src/chat/mira-system-instruction.ts +484 -0
  22. package/src/chat-sdk/adapters.ts +389 -0
  23. package/src/chat-sdk/registry.ts +197 -0
  24. package/src/chat-sdk.ts +33 -0
  25. package/src/core.ts +3 -0
  26. package/src/credits/cap-output-tokens.ts +90 -0
  27. package/src/credits/check-credits.ts +232 -0
  28. package/src/credits/constants.ts +30 -0
  29. package/src/credits/index.ts +46 -0
  30. package/src/credits/model-mapping.ts +92 -0
  31. package/src/credits/reservations.ts +514 -0
  32. package/src/credits/resolve-plan-model.ts +219 -0
  33. package/src/credits/sync-gateway-models.ts +351 -0
  34. package/src/credits/types.ts +109 -0
  35. package/src/credits/use-ai-credits.ts +3 -0
  36. package/src/embeddings/metered.ts +283 -0
  37. package/src/executions/route.ts +137 -0
  38. package/src/generate/route.ts +411 -0
  39. package/src/hooks.ts +7 -0
  40. package/src/meetings/summary/route.ts +7 -0
  41. package/src/meetings/transcription/route.ts +134 -0
  42. package/src/memory/client.ts +158 -0
  43. package/src/memory/config.ts +38 -0
  44. package/src/memory/index.ts +32 -0
  45. package/src/memory/ingest.ts +51 -0
  46. package/src/memory/middleware.ts +35 -0
  47. package/src/memory/operations.ts +480 -0
  48. package/src/memory/scope.ts +102 -0
  49. package/src/memory/settings.ts +121 -0
  50. package/src/memory/types.ts +101 -0
  51. package/src/memory/workspace.ts +36 -0
  52. package/src/memory.ts +1 -0
  53. package/src/mind/patch.ts +146 -0
  54. package/src/mind/route.ts +687 -0
  55. package/src/mind/tools.ts +1500 -0
  56. package/src/mind/types.ts +20 -0
  57. package/src/object/core.ts +3 -0
  58. package/src/object/flashcards/route.ts +140 -0
  59. package/src/object/quizzes/explanation/route.ts +145 -0
  60. package/src/object/quizzes/route.ts +142 -0
  61. package/src/object/types.ts +187 -0
  62. package/src/object/year-plan/route.ts +196 -0
  63. package/src/react.ts +1 -0
  64. package/src/scheduling/algorithm.ts +791 -0
  65. package/src/scheduling/default.ts +36 -0
  66. package/src/scheduling/duration-optimizer.ts +689 -0
  67. package/src/scheduling/index.ts +79 -0
  68. package/src/scheduling/priority-calculator.ts +187 -0
  69. package/src/scheduling/recurrence-calculator.ts +621 -0
  70. package/src/scheduling/templates.ts +892 -0
  71. package/src/scheduling/types.ts +136 -0
  72. package/src/scheduling/web-adapter.ts +308 -0
  73. package/src/scheduling.ts +6 -0
  74. package/src/supported-actions.ts +1 -0
  75. package/src/supported-providers.ts +6 -0
  76. package/src/tools/context-builder.ts +372 -0
  77. package/src/tools/core.ts +1 -0
  78. package/src/tools/definitions/calendar.ts +106 -0
  79. package/src/tools/definitions/finance.ts +197 -0
  80. package/src/tools/definitions/image.ts +74 -0
  81. package/src/tools/definitions/memory.ts +83 -0
  82. package/src/tools/definitions/meta.ts +154 -0
  83. package/src/tools/definitions/render-ui.ts +81 -0
  84. package/src/tools/definitions/tasks.ts +343 -0
  85. package/src/tools/definitions/time-tracking.ts +381 -0
  86. package/src/tools/definitions/workspace-context.ts +45 -0
  87. package/src/tools/definitions/workspace-user-chat.ts +111 -0
  88. package/src/tools/executors/calendar.ts +371 -0
  89. package/src/tools/executors/chat.ts +15 -0
  90. package/src/tools/executors/finance.ts +638 -0
  91. package/src/tools/executors/helpers/encryption.ts +107 -0
  92. package/src/tools/executors/image.ts +247 -0
  93. package/src/tools/executors/markitdown.ts +684 -0
  94. package/src/tools/executors/memory.ts +277 -0
  95. package/src/tools/executors/parallel-checks.ts +176 -0
  96. package/src/tools/executors/qr.ts +170 -0
  97. package/src/tools/executors/scope-helpers.ts +192 -0
  98. package/src/tools/executors/search.ts +149 -0
  99. package/src/tools/executors/settings.ts +40 -0
  100. package/src/tools/executors/tasks.ts +1087 -0
  101. package/src/tools/executors/theme.ts +23 -0
  102. package/src/tools/executors/timer/timer-categories-executor.ts +110 -0
  103. package/src/tools/executors/timer/timer-category-mutations.ts +240 -0
  104. package/src/tools/executors/timer/timer-goal-mutations.ts +323 -0
  105. package/src/tools/executors/timer/timer-goals-executor.ts +272 -0
  106. package/src/tools/executors/timer/timer-helpers.ts +372 -0
  107. package/src/tools/executors/timer/timer-mutation-schemas.ts +160 -0
  108. package/src/tools/executors/timer/timer-mutation-types.ts +212 -0
  109. package/src/tools/executors/timer/timer-mutations.ts +19 -0
  110. package/src/tools/executors/timer/timer-queries.ts +18 -0
  111. package/src/tools/executors/timer/timer-session-lifecycle.ts +299 -0
  112. package/src/tools/executors/timer/timer-session-mutations.ts +10 -0
  113. package/src/tools/executors/timer/timer-session-queries.ts +153 -0
  114. package/src/tools/executors/timer/timer-session-updates.ts +200 -0
  115. package/src/tools/executors/timer/timer-sessions-executor.ts +91 -0
  116. package/src/tools/executors/timer/timer-stats-executor.ts +157 -0
  117. package/src/tools/executors/timer.ts +22 -0
  118. package/src/tools/executors/user.ts +60 -0
  119. package/src/tools/executors/workspace.ts +135 -0
  120. package/src/tools/json-render-catalog.ts +875 -0
  121. package/src/tools/mira-tool-definitions.ts +55 -0
  122. package/src/tools/mira-tool-dispatcher.ts +265 -0
  123. package/src/tools/mira-tool-metadata.ts +164 -0
  124. package/src/tools/mira-tool-names.ts +95 -0
  125. package/src/tools/mira-tool-render-ui.ts +54 -0
  126. package/src/tools/mira-tool-types.ts +17 -0
  127. package/src/tools/mira-tools.ts +167 -0
  128. package/src/tools/normalize-render-ui-input.ts +321 -0
  129. package/src/tools/workspace-context.ts +233 -0
  130. package/src/types.ts +38 -0
@@ -0,0 +1,372 @@
1
+ import type { SupabaseClient } from '@tuturuuu/supabase';
2
+ import type { PermissionId } from '@tuturuuu/types';
3
+ import type { MiraSoulConfig } from '../chat/mira-system-instruction';
4
+ import { buildAiMemoryContext, resolveAiMemoryScope } from '../memory';
5
+
6
+ /**
7
+ * Token budget for context injection (~4K tokens ≈ 16K chars).
8
+ * Each section is capped individually so one large section can't starve others.
9
+ */
10
+ const MAX_CONTEXT_CHARS = 16_000;
11
+ const SECTION_CHAR_LIMITS = {
12
+ soul: 2000,
13
+ tasks: 4000,
14
+ calendar: 3000,
15
+ finance: 2000,
16
+ memories: 3000,
17
+ meta: 500,
18
+ } as const;
19
+
20
+ type ContextOptions = {
21
+ userId: string;
22
+ wsId: string;
23
+ supabase: SupabaseClient;
24
+ timezone?: string;
25
+ withoutPermission?: (permission: PermissionId) => boolean;
26
+ };
27
+
28
+ export type MiraContextResult = {
29
+ /** Combined context string for injection into the prompt. */
30
+ contextString: string;
31
+ /** Raw soul data for use in buildMiraSystemInstruction. */
32
+ soul: MiraSoulConfig | null;
33
+ /** True when no soul config row exists (brand-new user). */
34
+ isFirstInteraction: boolean;
35
+ };
36
+
37
+ /**
38
+ * Builds a dynamic context string injected into Mira's system prompt.
39
+ * All data sources are fetched in parallel for performance.
40
+ *
41
+ * Returns the context string alongside the raw soul config so the
42
+ * caller can pass it to `buildMiraSystemInstruction()`.
43
+ */
44
+ export async function buildMiraContext(
45
+ opts: ContextOptions
46
+ ): Promise<MiraContextResult> {
47
+ const { userId, wsId, supabase, timezone = 'UTC', withoutPermission } = opts;
48
+ const canReadCalendar = !withoutPermission?.('manage_calendar');
49
+ const canReadFinance = !withoutPermission?.('manage_finance');
50
+
51
+ let now = new Date();
52
+ try {
53
+ // If a valid timezone is provided, try to shift our "now" perspective
54
+ // to match the local calendar day for better "today" logic
55
+ const tzDateStr = new Intl.DateTimeFormat('en-US', {
56
+ timeZone: timezone,
57
+ year: 'numeric',
58
+ month: '2-digit',
59
+ day: '2-digit',
60
+ hour: '2-digit',
61
+ minute: '2-digit',
62
+ second: '2-digit',
63
+ hour12: false,
64
+ }).format(now);
65
+
66
+ // Parse the output back (MM/DD/YYYY, HH:mm:ss) to create a local-time equivalent Date
67
+ const [datePart, timePart] = tzDateStr.split(', ');
68
+ const [month, day, year] = datePart!.split('/');
69
+ const [hour, min, sec] = timePart!.split(':');
70
+ if (year && month && day && hour && min && sec) {
71
+ now = new Date(
72
+ parseInt(year, 10),
73
+ parseInt(month, 10) - 1,
74
+ parseInt(day, 10),
75
+ parseInt(hour, 10),
76
+ parseInt(min, 10),
77
+ parseInt(sec, 10)
78
+ );
79
+ }
80
+ } catch (err) {
81
+ console.warn(
82
+ 'Failed to parse timezone for context builder, falling back to UTC',
83
+ err
84
+ );
85
+ }
86
+
87
+ const todayStart = new Date(now);
88
+ todayStart.setHours(0, 0, 0, 0);
89
+ const todayEnd = new Date(now);
90
+ todayEnd.setHours(23, 59, 59, 999);
91
+ const weekAhead = new Date(now);
92
+ weekAhead.setDate(weekAhead.getDate() + 7);
93
+
94
+ // Fetch all context sources in parallel
95
+ const [
96
+ soulResult,
97
+ tasksResult,
98
+ calendarResult,
99
+ walletsResult,
100
+ memoriesResult,
101
+ ] = await Promise.all([
102
+ // Soul config
103
+ supabase
104
+ .from('mira_soul')
105
+ .select('name, tone, personality, boundaries, vibe, chat_tone')
106
+ .eq('user_id', userId)
107
+ .maybeSingle(),
108
+
109
+ // Tasks: overdue + due today (via RPC)
110
+ supabase.rpc('get_user_accessible_tasks', {
111
+ p_user_id: userId,
112
+ p_ws_id: wsId,
113
+ p_include_deleted: false,
114
+ p_list_statuses: ['not_started', 'active'],
115
+ }),
116
+
117
+ // Calendar: next 7 days
118
+ canReadCalendar
119
+ ? supabase
120
+ .from('workspace_calendar_events')
121
+ .select('title, start_at, end_at, location')
122
+ .eq('ws_id', wsId)
123
+ .gte('start_at', now.toISOString())
124
+ .lte('start_at', weekAhead.toISOString())
125
+ .order('start_at', { ascending: true })
126
+ .limit(15)
127
+ : Promise.resolve({ data: null, error: null }),
128
+
129
+ // Wallets with balances
130
+ canReadFinance
131
+ ? supabase
132
+ .schema('private')
133
+ .from('workspace_wallets')
134
+ .select('name, currency, balance')
135
+ .eq('ws_id', wsId)
136
+ .limit(10)
137
+ : Promise.resolve({ data: null, error: null }),
138
+
139
+ // Mira AI memories, scoped to the current user + workspace.
140
+ buildAiMemoryContext({
141
+ limit: 15,
142
+ scope: resolveAiMemoryScope({
143
+ customId: `mira-context-${wsId}`,
144
+ product: 'mira',
145
+ source: 'mira_context',
146
+ surface: 'mira_context',
147
+ userId,
148
+ wsId,
149
+ }),
150
+ }).then((context) => ({ data: context, error: null })),
151
+ ]);
152
+
153
+ const sections: string[] = [];
154
+
155
+ // ── Soul / Personality ──
156
+ const soul = soulResult.data;
157
+ if (soul) {
158
+ const soulLines = [`Your name is ${soul.name || 'Mira'}.`];
159
+ if (soul.tone) soulLines.push(`Tone: ${soul.tone}.`);
160
+ if (soul.personality) soulLines.push(`Personality: ${soul.personality}`);
161
+ if (soul.boundaries) soulLines.push(`Boundaries: ${soul.boundaries}`);
162
+ if (soul.vibe) soulLines.push(`Vibe: ${soul.vibe}`);
163
+ if (soul.chat_tone) soulLines.push(`Chat tone: ${soul.chat_tone}.`);
164
+
165
+ sections.push(
166
+ truncateSection(
167
+ `## Your Identity\n${soulLines.join('\n')}`,
168
+ SECTION_CHAR_LIMITS.soul
169
+ )
170
+ );
171
+ }
172
+
173
+ // ── Meta ──
174
+ const formatterOptions: Intl.DateTimeFormatOptions = {
175
+ weekday: 'long',
176
+ year: 'numeric',
177
+ month: 'long',
178
+ day: 'numeric',
179
+ hour: '2-digit',
180
+ minute: '2-digit',
181
+ timeZone: timezone,
182
+ timeZoneName: 'short',
183
+ };
184
+
185
+ let timeStr = now.toISOString();
186
+ let dateStr = now.toLocaleDateString('en-US');
187
+
188
+ try {
189
+ // Re-use real current time for the literal string output so it shows actual timezone
190
+ const realNow = new Date();
191
+ timeStr = realNow.toLocaleString('en-US', formatterOptions);
192
+ dateStr = realNow.toLocaleDateString('en-US', {
193
+ weekday: 'long',
194
+ year: 'numeric',
195
+ month: 'long',
196
+ day: 'numeric',
197
+ timeZone: timezone,
198
+ });
199
+ } catch (_) {
200
+ // Ignore formatting errors
201
+ }
202
+
203
+ const metaLines = [
204
+ `Current time: ${timeStr} (${timezone})`,
205
+ `Today: ${dateStr}`,
206
+ ];
207
+ sections.push(
208
+ truncateSection(
209
+ `## Context\n${metaLines.join('\n')}`,
210
+ SECTION_CHAR_LIMITS.meta
211
+ )
212
+ );
213
+
214
+ // ── Tasks ──
215
+ type RpcTask = {
216
+ task_id: string;
217
+ task_name: string;
218
+ task_priority: string | null;
219
+ task_end_date: string | null;
220
+ task_completed_at: string | null;
221
+ task_closed_at: string | null;
222
+ };
223
+
224
+ const allTasks = (tasksResult.data || []) as RpcTask[];
225
+ const activeTasks = allTasks.filter(
226
+ (t) => !t.task_completed_at && !t.task_closed_at
227
+ );
228
+
229
+ const overdueTasks = activeTasks
230
+ .filter((t) => t.task_end_date && t.task_end_date < now.toISOString())
231
+ .slice(0, 10);
232
+
233
+ const todayTasks = activeTasks
234
+ .filter(
235
+ (t) =>
236
+ t.task_end_date &&
237
+ t.task_end_date >= todayStart.toISOString() &&
238
+ t.task_end_date <= todayEnd.toISOString()
239
+ )
240
+ .slice(0, 10);
241
+
242
+ const upcomingTasks = activeTasks
243
+ .filter(
244
+ (t) =>
245
+ t.task_end_date &&
246
+ t.task_end_date > todayEnd.toISOString() &&
247
+ t.task_end_date <= weekAhead.toISOString()
248
+ )
249
+ .slice(0, 5);
250
+
251
+ if (overdueTasks.length || todayTasks.length || upcomingTasks.length) {
252
+ const taskLines: string[] = [`Total active tasks: ${activeTasks.length}`];
253
+
254
+ if (overdueTasks.length) {
255
+ taskLines.push(`\nOverdue (${overdueTasks.length}):`);
256
+ for (const t of overdueTasks) {
257
+ taskLines.push(
258
+ `- ${t.task_name}${t.task_priority ? ` [${t.task_priority}]` : ''} (due ${t.task_end_date})`
259
+ );
260
+ }
261
+ }
262
+
263
+ if (todayTasks.length) {
264
+ taskLines.push(`\nDue today (${todayTasks.length}):`);
265
+ for (const t of todayTasks) {
266
+ taskLines.push(
267
+ `- ${t.task_name}${t.task_priority ? ` [${t.task_priority}]` : ''}`
268
+ );
269
+ }
270
+ }
271
+
272
+ if (upcomingTasks.length) {
273
+ taskLines.push(`\nUpcoming this week (${upcomingTasks.length}):`);
274
+ for (const t of upcomingTasks) {
275
+ taskLines.push(`- ${t.task_name} (due ${t.task_end_date})`);
276
+ }
277
+ }
278
+
279
+ sections.push(
280
+ truncateSection(
281
+ `## Tasks\n${taskLines.join('\n')}`,
282
+ SECTION_CHAR_LIMITS.tasks
283
+ )
284
+ );
285
+ }
286
+
287
+ // ── Calendar ──
288
+ const events = calendarResult.data;
289
+ if (events?.length) {
290
+ const eventLines: string[] = [];
291
+ for (const e of events) {
292
+ const start = new Date(e.start_at);
293
+ const end = new Date(e.end_at);
294
+
295
+ let dateStr: string, timeStr: string;
296
+ try {
297
+ dateStr = start.toLocaleDateString('en-US', {
298
+ weekday: 'short',
299
+ month: 'short',
300
+ day: 'numeric',
301
+ timeZone: timezone,
302
+ });
303
+ timeStr = `${start.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: timezone })}–${end.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: timezone })}`;
304
+ } catch (_) {
305
+ dateStr = start.toLocaleDateString('en-US', {
306
+ weekday: 'short',
307
+ month: 'short',
308
+ day: 'numeric',
309
+ });
310
+ timeStr = `${start.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}–${end.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}`;
311
+ }
312
+ const loc = e.location ? ` @ ${e.location}` : '';
313
+ eventLines.push(`- ${dateStr} ${timeStr}: ${e.title}${loc}`);
314
+ }
315
+
316
+ sections.push(
317
+ truncateSection(
318
+ `## Calendar (next 7 days)\n${eventLines.join('\n')}`,
319
+ SECTION_CHAR_LIMITS.calendar
320
+ )
321
+ );
322
+ }
323
+
324
+ // ── Finance ──
325
+ const wallets = walletsResult.data;
326
+ if (wallets?.length) {
327
+ const walletLines = wallets.map(
328
+ (w: { name: string | null; balance: number | null; currency: string }) =>
329
+ `- ${w.name || 'Wallet'}: ${w.balance ?? 0} ${w.currency}`
330
+ );
331
+
332
+ sections.push(
333
+ truncateSection(
334
+ `## Wallets\n${walletLines.join('\n')}`,
335
+ SECTION_CHAR_LIMITS.finance
336
+ )
337
+ );
338
+ }
339
+
340
+ // ── Memories ──
341
+ const memories = memoriesResult.data?.trim();
342
+ if (memories) {
343
+ sections.push(truncateSection(memories, SECTION_CHAR_LIMITS.memories));
344
+ }
345
+
346
+ // Combine all sections, respecting total budget
347
+ let combined = sections.join('\n\n');
348
+ if (combined.length > MAX_CONTEXT_CHARS) {
349
+ combined = `${combined.slice(0, MAX_CONTEXT_CHARS)}\n[context truncated]`;
350
+ }
351
+
352
+ return {
353
+ contextString: combined,
354
+ soul: soul
355
+ ? {
356
+ name: soul.name ?? undefined,
357
+ tone: soul.tone,
358
+ personality: soul.personality,
359
+ boundaries: soul.boundaries,
360
+ vibe: soul.vibe,
361
+ chat_tone: soul.chat_tone,
362
+ }
363
+ : null,
364
+ isFirstInteraction: !soul,
365
+ };
366
+ }
367
+
368
+ /** Truncate a section to a char limit, adding an ellipsis marker */
369
+ function truncateSection(text: string, maxChars: number): string {
370
+ if (text.length <= maxChars) return text;
371
+ return `${text.slice(0, maxChars - 20)}\n[...truncated]`;
372
+ }
@@ -0,0 +1 @@
1
+ export { tool } from 'ai';
@@ -0,0 +1,106 @@
1
+ import { z } from 'zod';
2
+ import { tool } from '../core';
3
+
4
+ export const calendarToolDefinitions = {
5
+ get_upcoming_events: tool({
6
+ description:
7
+ 'Get upcoming calendar events for the next N days. Events are automatically decrypted if E2EE is enabled.',
8
+ inputSchema: z.object({
9
+ days: z
10
+ .number()
11
+ .int()
12
+ .min(1)
13
+ .max(30)
14
+ .optional()
15
+ .describe('Number of days to look ahead (default: 7)'),
16
+ }),
17
+ }),
18
+
19
+ create_event: tool({
20
+ description:
21
+ 'Create a new calendar event. Events are automatically encrypted if E2EE is enabled.',
22
+ inputSchema: z
23
+ .object({
24
+ title: z.string().describe('Event title'),
25
+ startAt: z
26
+ .string()
27
+ .datetime({ offset: true })
28
+ .describe('Start time ISO 8601'),
29
+ endAt: z
30
+ .string()
31
+ .datetime({ offset: true })
32
+ .describe('End time ISO 8601'),
33
+ description: z
34
+ .string()
35
+ .nullish()
36
+ .describe('Event description, or null/omit'),
37
+ location: z.string().nullish().describe('Event location, or null/omit'),
38
+ })
39
+ .superRefine((value, ctx) => {
40
+ const startAt = new Date(value.startAt);
41
+ const endAt = new Date(value.endAt);
42
+
43
+ if (startAt >= endAt) {
44
+ ctx.addIssue({
45
+ code: z.ZodIssueCode.custom,
46
+ path: ['endAt'],
47
+ message: 'endAt must be later than startAt',
48
+ });
49
+ }
50
+ }),
51
+ }),
52
+
53
+ update_event: tool({
54
+ description:
55
+ 'Update an existing calendar event. Updated fields are encrypted automatically if E2EE is enabled.',
56
+ inputSchema: z
57
+ .object({
58
+ eventId: z.guid().describe('Event UUID'),
59
+ title: z.string().optional().describe('Updated event title'),
60
+ startAt: z.iso
61
+ .datetime({ offset: true })
62
+ .optional()
63
+ .describe('Updated start time ISO 8601'),
64
+ endAt: z.iso
65
+ .datetime({ offset: true })
66
+ .optional()
67
+ .describe('Updated end time ISO 8601'),
68
+ description: z
69
+ .string()
70
+ .nullish()
71
+ .describe('Updated event description, or null/omit'),
72
+ location: z
73
+ .string()
74
+ .nullish()
75
+ .describe('Updated event location, or null/omit'),
76
+ })
77
+ .refine(
78
+ (value) =>
79
+ value.title !== undefined ||
80
+ value.startAt !== undefined ||
81
+ value.endAt !== undefined ||
82
+ value.description !== undefined ||
83
+ value.location !== undefined,
84
+ { message: 'At least one field to update is required' }
85
+ ),
86
+ }),
87
+
88
+ delete_event: tool({
89
+ description: 'Delete a calendar event by ID.',
90
+ inputSchema: z.object({
91
+ eventId: z.guid().describe('Event UUID'),
92
+ }),
93
+ }),
94
+
95
+ check_e2ee_status: tool({
96
+ description:
97
+ 'Check whether end-to-end encryption is enabled for calendar events in this workspace.',
98
+ inputSchema: z.object({}),
99
+ }),
100
+
101
+ enable_e2ee: tool({
102
+ description:
103
+ 'Enable end-to-end encryption for calendar events. Once enabled, new events will be encrypted automatically.',
104
+ inputSchema: z.object({}),
105
+ }),
106
+ } as const;
@@ -0,0 +1,197 @@
1
+ import { z } from 'zod';
2
+ import { tool } from '../core';
3
+
4
+ export const financeToolDefinitions = {
5
+ log_transaction: tool({
6
+ description:
7
+ 'Log a financial transaction. Positive amount = income, negative = expense.',
8
+ inputSchema: z.object({
9
+ amount: z.number().describe('Amount (positive=income, negative=expense)'),
10
+ description: z.string().nullish().describe('What was this for?'),
11
+ walletId: z
12
+ .guid()
13
+ .nullish()
14
+ .describe('Wallet UUID. If null, uses the first wallet.'),
15
+ }),
16
+ }),
17
+
18
+ get_spending_summary: tool({
19
+ description: 'Get income/expense summary for the past N days.',
20
+ inputSchema: z.object({
21
+ days: z
22
+ .number()
23
+ .int()
24
+ .min(1)
25
+ .max(365)
26
+ .optional()
27
+ .describe('Number of past days to summarize (default: 30)'),
28
+ }),
29
+ }),
30
+
31
+ list_wallets: tool({
32
+ description: 'List all wallets in the workspace.',
33
+ inputSchema: z.object({}),
34
+ }),
35
+
36
+ create_wallet: tool({
37
+ description: 'Create a new wallet.',
38
+ inputSchema: z.object({
39
+ name: z.string().describe('Wallet name'),
40
+ currency: z.string().optional().describe('Currency code (e.g. USD, VND)'),
41
+ balance: z.number().optional().describe('Initial balance'),
42
+ type: z.string().optional().describe('Wallet type'),
43
+ }),
44
+ }),
45
+
46
+ update_wallet: tool({
47
+ description: 'Update wallet details.',
48
+ inputSchema: z.object({
49
+ walletId: z.guid().describe('Wallet UUID'),
50
+ name: z.string().optional().describe('New name'),
51
+ currency: z.string().optional().describe('New currency'),
52
+ balance: z.number().optional().describe('New balance'),
53
+ type: z.string().optional().describe('New wallet type'),
54
+ }),
55
+ }),
56
+
57
+ delete_wallet: tool({
58
+ description: 'Delete a wallet.',
59
+ inputSchema: z.object({
60
+ walletId: z.guid().describe('Wallet UUID'),
61
+ }),
62
+ }),
63
+
64
+ list_transactions: tool({
65
+ description: 'List transactions with optional filters.',
66
+ inputSchema: z.object({
67
+ walletId: z.guid().optional().describe('Filter by wallet UUID'),
68
+ categoryId: z.guid().optional().describe('Filter by category UUID'),
69
+ days: z
70
+ .number()
71
+ .int()
72
+ .min(1)
73
+ .max(365)
74
+ .optional()
75
+ .describe('Only last N days'),
76
+ limit: z
77
+ .number()
78
+ .int()
79
+ .min(1)
80
+ .max(100)
81
+ .optional()
82
+ .describe('Max results (default 50)'),
83
+ }),
84
+ }),
85
+
86
+ get_transaction: tool({
87
+ description: 'Get a single transaction by ID.',
88
+ inputSchema: z.object({
89
+ transactionId: z.guid().describe('Transaction UUID'),
90
+ }),
91
+ }),
92
+
93
+ update_transaction: tool({
94
+ description: 'Update a transaction.',
95
+ inputSchema: z.object({
96
+ transactionId: z.guid().describe('Transaction UUID'),
97
+ amount: z.number().optional().describe('New amount'),
98
+ description: z.string().optional().describe('New description'),
99
+ categoryId: z.guid().optional().describe('New category UUID'),
100
+ walletId: z.guid().optional().describe('New wallet UUID'),
101
+ }),
102
+ }),
103
+
104
+ delete_transaction: tool({
105
+ description: 'Delete a transaction.',
106
+ inputSchema: z.object({
107
+ transactionId: z.guid().describe('Transaction UUID'),
108
+ }),
109
+ }),
110
+
111
+ list_transaction_categories: tool({
112
+ description: 'List all transaction categories.',
113
+ inputSchema: z.object({}),
114
+ }),
115
+
116
+ create_transaction_category: tool({
117
+ description: 'Create a transaction category.',
118
+ inputSchema: z.object({
119
+ name: z.string().describe('Category name'),
120
+ isExpense: z
121
+ .boolean()
122
+ .optional()
123
+ .describe('Is this an expense category? Default true.'),
124
+ }),
125
+ }),
126
+
127
+ update_transaction_category: tool({
128
+ description: 'Update a transaction category.',
129
+ inputSchema: z.object({
130
+ categoryId: z.guid().describe('Category UUID'),
131
+ name: z.string().optional().describe('New name'),
132
+ isExpense: z.boolean().optional().describe('Is expense?'),
133
+ }),
134
+ }),
135
+
136
+ delete_transaction_category: tool({
137
+ description: 'Delete a transaction category.',
138
+ inputSchema: z.object({
139
+ categoryId: z.guid().describe('Category UUID'),
140
+ }),
141
+ }),
142
+
143
+ list_transaction_tags: tool({
144
+ description: 'List all transaction tags.',
145
+ inputSchema: z.object({}),
146
+ }),
147
+
148
+ create_transaction_tag: tool({
149
+ description: 'Create a transaction tag.',
150
+ inputSchema: z.object({
151
+ name: z.string().describe('Tag name'),
152
+ color: z
153
+ .string()
154
+ .regex(/^[#][0-9a-fA-F]{3,8}$/)
155
+ .optional()
156
+ .describe('Color hex'),
157
+ description: z.string().optional().describe('Tag description'),
158
+ }),
159
+ }),
160
+
161
+ update_transaction_tag: tool({
162
+ description: 'Update a transaction tag.',
163
+ inputSchema: z.object({
164
+ tagId: z.guid().describe('Tag UUID'),
165
+ name: z.string().optional().describe('New name'),
166
+ color: z
167
+ .string()
168
+ .regex(/^[#][0-9a-fA-F]{3,8}$/)
169
+ .optional()
170
+ .describe('New color'),
171
+ description: z.string().optional().describe('New description'),
172
+ }),
173
+ }),
174
+
175
+ delete_transaction_tag: tool({
176
+ description: 'Delete a transaction tag.',
177
+ inputSchema: z.object({
178
+ tagId: z.guid().describe('Tag UUID'),
179
+ }),
180
+ }),
181
+
182
+ set_default_currency: tool({
183
+ description:
184
+ 'Set the default currency for the workspace. Use standard currency codes.',
185
+ inputSchema: z.object({
186
+ currency: z
187
+ .string()
188
+ .regex(/^[A-Z]{3}$/, {
189
+ message:
190
+ 'Currency must be a valid ISO-4217 code (3 uppercase letters)',
191
+ })
192
+ .describe(
193
+ 'Currency code in ISO-4217 format (exactly 3 uppercase letters, e.g. USD, VND, EUR, JPY, GBP, KRW, THB, SGD)'
194
+ ),
195
+ }),
196
+ }),
197
+ } as const;