@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.
- package/README.md +76 -0
- package/package.json +106 -0
- package/src/api-key-hash.ts +28 -0
- package/src/calendar/events.ts +34 -0
- package/src/calendar/route.ts +114 -0
- package/src/chat/credit-source.ts +1 -0
- package/src/chat/google/chat-request-schema.ts +150 -0
- package/src/chat/google/default-system-instruction.ts +198 -0
- package/src/chat/google/message-file-processing.ts +212 -0
- package/src/chat/google/mira-step-preparation.ts +221 -0
- package/src/chat/google/new/route.ts +368 -0
- package/src/chat/google/route-auth.ts +81 -0
- package/src/chat/google/route-chat-resolution.ts +98 -0
- package/src/chat/google/route-credits.ts +61 -0
- package/src/chat/google/route-message-preparation.ts +331 -0
- package/src/chat/google/route-mira-runtime.ts +206 -0
- package/src/chat/google/route.ts +632 -0
- package/src/chat/google/stream-finish-persistence.ts +722 -0
- package/src/chat/google/summary/route.ts +153 -0
- package/src/chat/mira-render-ui-policy.ts +540 -0
- package/src/chat/mira-system-instruction.ts +484 -0
- package/src/chat-sdk/adapters.ts +389 -0
- package/src/chat-sdk/registry.ts +197 -0
- package/src/chat-sdk.ts +33 -0
- package/src/core.ts +3 -0
- package/src/credits/cap-output-tokens.ts +90 -0
- package/src/credits/check-credits.ts +232 -0
- package/src/credits/constants.ts +30 -0
- package/src/credits/index.ts +46 -0
- package/src/credits/model-mapping.ts +92 -0
- package/src/credits/reservations.ts +514 -0
- package/src/credits/resolve-plan-model.ts +219 -0
- package/src/credits/sync-gateway-models.ts +351 -0
- package/src/credits/types.ts +109 -0
- package/src/credits/use-ai-credits.ts +3 -0
- package/src/embeddings/metered.ts +283 -0
- package/src/executions/route.ts +137 -0
- package/src/generate/route.ts +411 -0
- package/src/hooks.ts +7 -0
- package/src/meetings/summary/route.ts +7 -0
- package/src/meetings/transcription/route.ts +134 -0
- package/src/memory/client.ts +158 -0
- package/src/memory/config.ts +38 -0
- package/src/memory/index.ts +32 -0
- package/src/memory/ingest.ts +51 -0
- package/src/memory/middleware.ts +35 -0
- package/src/memory/operations.ts +480 -0
- package/src/memory/scope.ts +102 -0
- package/src/memory/settings.ts +121 -0
- package/src/memory/types.ts +101 -0
- package/src/memory/workspace.ts +36 -0
- package/src/memory.ts +1 -0
- package/src/mind/patch.ts +146 -0
- package/src/mind/route.ts +687 -0
- package/src/mind/tools.ts +1500 -0
- package/src/mind/types.ts +20 -0
- package/src/object/core.ts +3 -0
- package/src/object/flashcards/route.ts +140 -0
- package/src/object/quizzes/explanation/route.ts +145 -0
- package/src/object/quizzes/route.ts +142 -0
- package/src/object/types.ts +187 -0
- package/src/object/year-plan/route.ts +196 -0
- package/src/react.ts +1 -0
- package/src/scheduling/algorithm.ts +791 -0
- package/src/scheduling/default.ts +36 -0
- package/src/scheduling/duration-optimizer.ts +689 -0
- package/src/scheduling/index.ts +79 -0
- package/src/scheduling/priority-calculator.ts +187 -0
- package/src/scheduling/recurrence-calculator.ts +621 -0
- package/src/scheduling/templates.ts +892 -0
- package/src/scheduling/types.ts +136 -0
- package/src/scheduling/web-adapter.ts +308 -0
- package/src/scheduling.ts +6 -0
- package/src/supported-actions.ts +1 -0
- package/src/supported-providers.ts +6 -0
- package/src/tools/context-builder.ts +372 -0
- package/src/tools/core.ts +1 -0
- package/src/tools/definitions/calendar.ts +106 -0
- package/src/tools/definitions/finance.ts +197 -0
- package/src/tools/definitions/image.ts +74 -0
- package/src/tools/definitions/memory.ts +83 -0
- package/src/tools/definitions/meta.ts +154 -0
- package/src/tools/definitions/render-ui.ts +81 -0
- package/src/tools/definitions/tasks.ts +343 -0
- package/src/tools/definitions/time-tracking.ts +381 -0
- package/src/tools/definitions/workspace-context.ts +45 -0
- package/src/tools/definitions/workspace-user-chat.ts +111 -0
- package/src/tools/executors/calendar.ts +371 -0
- package/src/tools/executors/chat.ts +15 -0
- package/src/tools/executors/finance.ts +638 -0
- package/src/tools/executors/helpers/encryption.ts +107 -0
- package/src/tools/executors/image.ts +247 -0
- package/src/tools/executors/markitdown.ts +684 -0
- package/src/tools/executors/memory.ts +277 -0
- package/src/tools/executors/parallel-checks.ts +176 -0
- package/src/tools/executors/qr.ts +170 -0
- package/src/tools/executors/scope-helpers.ts +192 -0
- package/src/tools/executors/search.ts +149 -0
- package/src/tools/executors/settings.ts +40 -0
- package/src/tools/executors/tasks.ts +1087 -0
- package/src/tools/executors/theme.ts +23 -0
- package/src/tools/executors/timer/timer-categories-executor.ts +110 -0
- package/src/tools/executors/timer/timer-category-mutations.ts +240 -0
- package/src/tools/executors/timer/timer-goal-mutations.ts +323 -0
- package/src/tools/executors/timer/timer-goals-executor.ts +272 -0
- package/src/tools/executors/timer/timer-helpers.ts +372 -0
- package/src/tools/executors/timer/timer-mutation-schemas.ts +160 -0
- package/src/tools/executors/timer/timer-mutation-types.ts +212 -0
- package/src/tools/executors/timer/timer-mutations.ts +19 -0
- package/src/tools/executors/timer/timer-queries.ts +18 -0
- package/src/tools/executors/timer/timer-session-lifecycle.ts +299 -0
- package/src/tools/executors/timer/timer-session-mutations.ts +10 -0
- package/src/tools/executors/timer/timer-session-queries.ts +153 -0
- package/src/tools/executors/timer/timer-session-updates.ts +200 -0
- package/src/tools/executors/timer/timer-sessions-executor.ts +91 -0
- package/src/tools/executors/timer/timer-stats-executor.ts +157 -0
- package/src/tools/executors/timer.ts +22 -0
- package/src/tools/executors/user.ts +60 -0
- package/src/tools/executors/workspace.ts +135 -0
- package/src/tools/json-render-catalog.ts +875 -0
- package/src/tools/mira-tool-definitions.ts +55 -0
- package/src/tools/mira-tool-dispatcher.ts +265 -0
- package/src/tools/mira-tool-metadata.ts +164 -0
- package/src/tools/mira-tool-names.ts +95 -0
- package/src/tools/mira-tool-render-ui.ts +54 -0
- package/src/tools/mira-tool-types.ts +17 -0
- package/src/tools/mira-tools.ts +167 -0
- package/src/tools/normalize-render-ui-input.ts +321 -0
- package/src/tools/workspace-context.ts +233 -0
- 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;
|