@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,107 @@
1
+ /**
2
+ * Shared encryption/decryption helpers for AI tool executors.
3
+ *
4
+ * Uses dynamic imports to avoid pulling server-only modules into the
5
+ * `packages/ai` dependency graph at build time. This mirrors the pattern
6
+ * used by the image generation tool for `createAdminClient`.
7
+ */
8
+
9
+ type CalendarEventRow = {
10
+ id: string;
11
+ title: string;
12
+ description?: string;
13
+ location?: string | null;
14
+ is_encrypted?: boolean;
15
+ [key: string]: unknown;
16
+ };
17
+
18
+ /**
19
+ * Retrieve the workspace encryption key (read-only).
20
+ * Returns `null` when E2EE is not configured for the workspace.
21
+ */
22
+ export async function getWorkspaceKeyForTools(
23
+ wsId: string
24
+ ): Promise<Buffer | null> {
25
+ try {
26
+ const { isEncryptionEnabled, getMasterKey, decryptWorkspaceKey } =
27
+ await import('@tuturuuu/utils/encryption');
28
+
29
+ if (!isEncryptionEnabled()) return null;
30
+
31
+ const { createAdminClient } = await import(
32
+ '@tuturuuu/supabase/next/server'
33
+ );
34
+ const sbAdmin = await createAdminClient();
35
+ const masterKey = getMasterKey();
36
+
37
+ const { data, error } = await sbAdmin
38
+ .from('workspace_encryption_keys')
39
+ .select('encrypted_key')
40
+ .eq('ws_id', wsId)
41
+ .maybeSingle();
42
+
43
+ if (error || !data) return null;
44
+
45
+ return await decryptWorkspaceKey(
46
+ (data as { encrypted_key: string }).encrypted_key,
47
+ masterKey
48
+ );
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Decrypt an array of calendar events in-place if any are encrypted.
56
+ */
57
+ export async function decryptEventsForTools<T extends CalendarEventRow>(
58
+ events: T[],
59
+ wsId: string
60
+ ): Promise<T[]> {
61
+ const hasEncrypted = events.some((e) => e.is_encrypted);
62
+ if (!hasEncrypted) return events;
63
+
64
+ const key = await getWorkspaceKeyForTools(wsId);
65
+ if (!key) return events;
66
+
67
+ const { decryptCalendarEvents } = await import('@tuturuuu/utils/encryption');
68
+ return decryptCalendarEvents(events, key);
69
+ }
70
+
71
+ /**
72
+ * Encrypt the sensitive fields of a calendar event before storage.
73
+ * Returns the original data untouched when E2EE is not enabled.
74
+ */
75
+ export async function encryptEventFieldsForTools(
76
+ fields: { title: string; description: string; location: string | null },
77
+ wsId: string
78
+ ): Promise<{
79
+ title: string;
80
+ description: string;
81
+ location: string | null;
82
+ is_encrypted: boolean;
83
+ }> {
84
+ const key = await getWorkspaceKeyForTools(wsId);
85
+ if (!key) {
86
+ return { ...fields, is_encrypted: false };
87
+ }
88
+
89
+ const { encryptCalendarEventFields } = await import(
90
+ '@tuturuuu/utils/encryption'
91
+ );
92
+ const encrypted = encryptCalendarEventFields(
93
+ {
94
+ title: fields.title,
95
+ description: fields.description,
96
+ location: fields.location ?? undefined,
97
+ },
98
+ key
99
+ );
100
+
101
+ return {
102
+ title: encrypted.title,
103
+ description: encrypted.description,
104
+ location: encrypted.location ?? null,
105
+ is_encrypted: true,
106
+ };
107
+ }
@@ -0,0 +1,247 @@
1
+ import { google } from '@ai-sdk/google';
2
+ import { isGoogleModelId, toBareModelName } from '../../credits/model-mapping';
3
+ import {
4
+ PlanModelResolutionError,
5
+ resolvePlanModel,
6
+ } from '../../credits/resolve-plan-model';
7
+ import type { MiraToolContext } from '../mira-tools';
8
+
9
+ export async function executeGenerateImage(
10
+ args: Record<string, unknown>,
11
+ ctx: MiraToolContext
12
+ ): Promise<unknown> {
13
+ const billingWsId = ctx.creditWsId ?? ctx.wsId;
14
+ const prompt = args.prompt as string;
15
+ const aspectRatio = (args.aspectRatio as string) ?? '1:1';
16
+ let selectedModel: string;
17
+
18
+ try {
19
+ const resolvedModel = await resolvePlanModel({
20
+ capability: 'image',
21
+ requestedModel:
22
+ typeof args.model === 'string' ? (args.model as string) : undefined,
23
+ wsId: billingWsId,
24
+ });
25
+ selectedModel = resolvedModel.modelId;
26
+ } catch (error) {
27
+ if (error instanceof PlanModelResolutionError) {
28
+ return {
29
+ success: false,
30
+ error: error.message,
31
+ };
32
+ }
33
+
34
+ throw error;
35
+ }
36
+
37
+ const { checkAiCredits } = await import('../../credits/check-credits');
38
+ const {
39
+ commitFixedAiCreditReservation,
40
+ releaseFixedAiCreditReservation,
41
+ reserveFixedAiCredits,
42
+ } = await import('../../credits/reservations');
43
+ let commitResult: Awaited<
44
+ ReturnType<typeof commitFixedAiCreditReservation>
45
+ > | null = null;
46
+ const { createAdminClient } = await import('@tuturuuu/supabase/next/server');
47
+ const creditCheck = await checkAiCredits(
48
+ billingWsId,
49
+ selectedModel,
50
+ 'image_generation',
51
+ { userId: ctx.userId }
52
+ );
53
+
54
+ if (!creditCheck.allowed) {
55
+ const errorMessages: Record<string, string> = {
56
+ FEATURE_NOT_ALLOWED:
57
+ 'Image generation is not available on your current plan.',
58
+ MODEL_NOT_ALLOWED: `The model ${selectedModel} is not enabled for your workspace.`,
59
+ CREDITS_EXHAUSTED: 'You have run out of AI credits for image generation.',
60
+ NO_ALLOCATION: 'Image generation is not configured for your workspace.',
61
+ };
62
+ return {
63
+ success: false,
64
+ error:
65
+ errorMessages[creditCheck.errorCode ?? ''] ??
66
+ 'Image generation is not available. Please check your AI credit settings.',
67
+ };
68
+ }
69
+
70
+ const sbAdmin = await createAdminClient();
71
+ const reservationMetadata = {
72
+ aspectRatio,
73
+ model: selectedModel,
74
+ feature: 'image_generation',
75
+ };
76
+ // Full metadata including prompt for storage — never logged directly.
77
+ const fullReservationMetadata = {
78
+ prompt,
79
+ wsId: billingWsId,
80
+ userId: ctx.userId,
81
+ ...reservationMetadata,
82
+ };
83
+
84
+ const reservation = await reserveFixedAiCredits(
85
+ {
86
+ wsId: billingWsId,
87
+ userId: ctx.userId,
88
+ amount: 1,
89
+ modelId: selectedModel,
90
+ feature: 'image_generation',
91
+ metadata: fullReservationMetadata,
92
+ },
93
+ sbAdmin
94
+ );
95
+
96
+ if (!reservation.success || !reservation.reservationId) {
97
+ return {
98
+ success: false,
99
+ error:
100
+ reservation.errorCode === 'INSUFFICIENT_CREDITS'
101
+ ? 'You have run out of AI credits for image generation.'
102
+ : 'Failed to reserve AI credits for image generation.',
103
+ };
104
+ }
105
+
106
+ const { generateImage, gateway } = await import('ai');
107
+
108
+ const imageId = crypto.randomUUID();
109
+ const storagePath = `${ctx.wsId}/mira/images/${imageId}.png`;
110
+
111
+ try {
112
+ const model = isGoogleModelId(selectedModel)
113
+ ? google.image(toBareModelName(selectedModel))
114
+ : gateway.image(selectedModel);
115
+ const { image } = await generateImage({
116
+ model,
117
+ prompt,
118
+ aspectRatio: aspectRatio as `${number}:${number}`,
119
+ });
120
+
121
+ const { error: uploadError } = await sbAdmin.storage
122
+ .from('workspaces')
123
+ .upload(storagePath, image.uint8Array, {
124
+ contentType: 'image/png',
125
+ upsert: false,
126
+ });
127
+
128
+ if (uploadError) {
129
+ throw new Error(`Upload failed: ${uploadError.message}`);
130
+ }
131
+
132
+ const { data: urlData, error: urlError } = await sbAdmin.storage
133
+ .from('workspaces')
134
+ .createSignedUrl(storagePath, 60 * 60 * 24 * 30);
135
+
136
+ if (urlError || !urlData) {
137
+ throw new Error(
138
+ `Signed URL failed: ${urlError?.message ?? 'No data returned'}`
139
+ );
140
+ }
141
+
142
+ commitResult = await commitFixedAiCreditReservation(
143
+ reservation.reservationId,
144
+ {
145
+ ...fullReservationMetadata,
146
+ storagePath,
147
+ },
148
+ sbAdmin
149
+ );
150
+
151
+ if (!commitResult.success) {
152
+ throw new Error('Failed to finalize AI credit deduction.');
153
+ }
154
+
155
+ return {
156
+ success: true,
157
+ imageUrl: urlData.signedUrl,
158
+ storagePath,
159
+ prompt,
160
+ };
161
+ } catch (error) {
162
+ let commitOrReleaseError: Error | null = null;
163
+
164
+ // Attempt to release the reservation first, with defensive error handling.
165
+ // Releasing before storage cleanup ensures we know the reservation's final
166
+ // state before deciding whether to keep or remove the image.
167
+ let releaseResult: Awaited<
168
+ ReturnType<typeof releaseFixedAiCreditReservation>
169
+ > | null = null;
170
+ try {
171
+ releaseResult = await releaseFixedAiCreditReservation(
172
+ reservation.reservationId,
173
+ {
174
+ ...reservationMetadata,
175
+ storagePath,
176
+ error:
177
+ error instanceof Error
178
+ ? error.message
179
+ : 'Unknown image generation error',
180
+ },
181
+ sbAdmin
182
+ );
183
+
184
+ if (!releaseResult.success) {
185
+ console.error('Failed to release AI credit reservation', {
186
+ reservationId: reservation.reservationId,
187
+ storagePath,
188
+ releaseResult,
189
+ commitResult,
190
+ });
191
+ commitOrReleaseError = new Error(
192
+ `AI credit reservation release failed (${releaseResult.errorCode ?? 'UNKNOWN'}): ${JSON.stringify(
193
+ {
194
+ commitResult,
195
+ releaseResult,
196
+ }
197
+ )}`
198
+ );
199
+ }
200
+ } catch (releaseError) {
201
+ console.error('Failed to release AI credit reservation', {
202
+ reservationId: reservation.reservationId,
203
+ storagePath,
204
+ releaseError:
205
+ releaseError instanceof Error
206
+ ? releaseError.message
207
+ : String(releaseError),
208
+ });
209
+ commitOrReleaseError = new Error(
210
+ `Failed to release reservation: ${
211
+ releaseError instanceof Error
212
+ ? releaseError.message
213
+ : 'Unknown release error'
214
+ }`
215
+ );
216
+ }
217
+
218
+ // Only delete the image if the credit commit did NOT succeed AND
219
+ // the reservation was not already committed (confirmed by release).
220
+ // If commit succeeded (e.g. HTTP response was lost), keep the image
221
+ // so the user isn't charged for nothing.
222
+ const alreadyCommitted =
223
+ releaseResult?.errorCode === 'RESERVATION_ALREADY_COMMITTED';
224
+ if (storagePath && !commitResult?.success && !alreadyCommitted) {
225
+ const { error: removeError } = await sbAdmin.storage
226
+ .from('workspaces')
227
+ .remove([storagePath]);
228
+ if (removeError) {
229
+ console.error('Failed to cleanup image upload', {
230
+ storagePath,
231
+ error: removeError.message,
232
+ });
233
+ }
234
+ }
235
+
236
+ return {
237
+ success: false,
238
+ error: commitOrReleaseError?.message
239
+ ? `${commitOrReleaseError.message} ${
240
+ error instanceof Error ? `Original error: ${error.message}` : ''
241
+ }`.trim()
242
+ : error instanceof Error
243
+ ? error.message
244
+ : 'Image generation failed. Please try again.',
245
+ };
246
+ }
247
+ }