@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,514 @@
1
+ import type { AiFeature } from '@tuturuuu/ai/credits/constants';
2
+ import { resolveGatewayModelId } from '@tuturuuu/ai/credits/model-mapping';
3
+ import type {
4
+ CreditReservationCommitResult,
5
+ CreditReservationReleaseResult,
6
+ CreditReservationResult,
7
+ MeteredEmbeddingReservationResult,
8
+ } from '@tuturuuu/ai/credits/types';
9
+ import { createAdminClient } from '@tuturuuu/supabase/next/server';
10
+ import type { Json } from '@tuturuuu/types';
11
+ import {
12
+ decrementAiCreditChargeInFlight,
13
+ incrementAiCreditChargeInFlight,
14
+ invalidateAiCreditSnapshot,
15
+ } from '@tuturuuu/utils/ai-temp-auth';
16
+
17
+ type ReserveFixedCreditsRpcParams = {
18
+ p_ws_id: string;
19
+ p_user_id?: string | null;
20
+ p_amount: number;
21
+ p_model_id: string;
22
+ p_feature: string;
23
+ p_metadata: Json;
24
+ p_expires_in_seconds?: number;
25
+ };
26
+
27
+ type ReserveFixedCreditsRpcRow = {
28
+ success?: boolean;
29
+ reservation_id?: string | null;
30
+ remaining_credits?: number | string;
31
+ error_code?: string | null;
32
+ };
33
+
34
+ type CommitFixedCreditReservationRpcParams = {
35
+ p_reservation_id: string;
36
+ p_metadata: Json;
37
+ };
38
+
39
+ type CommitFixedCreditReservationRpcRow = {
40
+ success?: boolean;
41
+ credits_deducted?: number | string;
42
+ remaining_credits?: number | string;
43
+ error_code?: string | null;
44
+ };
45
+
46
+ type ReleaseFixedCreditReservationRpcParams = {
47
+ p_reservation_id: string;
48
+ p_metadata: Json;
49
+ };
50
+
51
+ type ReleaseFixedCreditReservationRpcRow = {
52
+ success?: boolean;
53
+ remaining_credits?: number | string;
54
+ error_code?: string | null;
55
+ };
56
+
57
+ type ReserveMeteredEmbeddingCreditsRpcParams = {
58
+ p_ws_id: string;
59
+ p_user_id?: string | null;
60
+ p_model_id: string;
61
+ p_input_tokens: number;
62
+ p_feature?: string;
63
+ p_metadata: Json;
64
+ p_expires_in_seconds?: number;
65
+ };
66
+
67
+ type ReserveMeteredEmbeddingCreditsRpcRow = {
68
+ success?: boolean;
69
+ reservation_id?: string | null;
70
+ credits_reserved?: number | string;
71
+ cost_usd?: number | string;
72
+ remaining_credits?: number | string;
73
+ error_code?: string | null;
74
+ };
75
+
76
+ type CommitMeteredEmbeddingCreditsRpcParams = {
77
+ p_reservation_id: string;
78
+ p_metadata: Json;
79
+ };
80
+
81
+ type ReleaseMeteredEmbeddingCreditsRpcParams = {
82
+ p_reservation_id: string;
83
+ p_metadata: Json;
84
+ };
85
+
86
+ type RpcError = { message: string } | null;
87
+
88
+ type CreditReservationRpcCaller = {
89
+ rpc: unknown;
90
+ };
91
+
92
+ async function getRpcCaller(
93
+ rpcCaller?: CreditReservationRpcCaller
94
+ ): Promise<CreditReservationRpcCaller> {
95
+ if (rpcCaller) {
96
+ return rpcCaller;
97
+ }
98
+
99
+ return createAdminClient();
100
+ }
101
+
102
+ function rpcErrorFromUnknown(error: unknown): RpcError {
103
+ return {
104
+ message: error instanceof Error ? error.message : 'Unknown RPC failure',
105
+ };
106
+ }
107
+
108
+ export async function reserveFixedAiCredits(
109
+ params: {
110
+ wsId: string;
111
+ userId?: string;
112
+ amount: number;
113
+ modelId: string;
114
+ feature: AiFeature;
115
+ metadata?: Record<string, unknown>;
116
+ expiresInSeconds?: number;
117
+ },
118
+ rpcCaller?: CreditReservationRpcCaller
119
+ ): Promise<CreditReservationResult> {
120
+ const sbAdmin = await getRpcCaller(rpcCaller);
121
+ const gatewayModelId = resolveGatewayModelId(params.modelId);
122
+ let inFlightMarked = false;
123
+ const rpc = (sbAdmin.rpc as (...args: unknown[]) => unknown).bind(
124
+ sbAdmin
125
+ ) as (
126
+ fn: 'reserve_fixed_ai_credits',
127
+ args: ReserveFixedCreditsRpcParams
128
+ ) => Promise<{
129
+ data: ReserveFixedCreditsRpcRow[] | null;
130
+ error: RpcError;
131
+ }>;
132
+
133
+ if (params.userId) {
134
+ inFlightMarked = await incrementAiCreditChargeInFlight({
135
+ wsId: params.wsId,
136
+ userId: params.userId,
137
+ });
138
+ }
139
+
140
+ let data: ReserveFixedCreditsRpcRow[] | null = null;
141
+ let error: RpcError = null;
142
+ try {
143
+ const result = await rpc('reserve_fixed_ai_credits', {
144
+ p_ws_id: params.wsId,
145
+ p_user_id: params.userId ?? null,
146
+ p_amount: params.amount,
147
+ p_model_id: gatewayModelId,
148
+ p_feature: params.feature,
149
+ p_metadata: (params.metadata ?? {}) as Json,
150
+ ...((params.expiresInSeconds ?? 0) > 0
151
+ ? { p_expires_in_seconds: params.expiresInSeconds }
152
+ : {}),
153
+ });
154
+ data = result.data;
155
+ error = result.error;
156
+ } catch (caughtError) {
157
+ data = null;
158
+ error = rpcErrorFromUnknown(caughtError);
159
+ } finally {
160
+ if (inFlightMarked && params.userId) {
161
+ await decrementAiCreditChargeInFlight({
162
+ wsId: params.wsId,
163
+ userId: params.userId,
164
+ });
165
+ }
166
+ }
167
+
168
+ if (error) {
169
+ console.error('Error reserving AI credits:', error);
170
+ return {
171
+ success: false,
172
+ reservationId: null,
173
+ remainingCredits: 0,
174
+ errorCode: 'RESERVATION_FAILED',
175
+ };
176
+ }
177
+
178
+ const row = Array.isArray(data) ? data[0] : data;
179
+ if (!row) {
180
+ return {
181
+ success: false,
182
+ reservationId: null,
183
+ remainingCredits: 0,
184
+ errorCode: 'NO_RESULT',
185
+ };
186
+ }
187
+
188
+ return {
189
+ success: row.success ?? false,
190
+ reservationId: row.reservation_id ?? null,
191
+ remainingCredits: Number(row.remaining_credits ?? 0),
192
+ errorCode: row.error_code ?? null,
193
+ };
194
+ }
195
+
196
+ export async function reserveMeteredEmbeddingCredits(
197
+ params: {
198
+ expiresInSeconds?: number;
199
+ inputTokens: number;
200
+ metadata?: Record<string, unknown>;
201
+ modelId: string;
202
+ userId?: string | null;
203
+ wsId: string;
204
+ },
205
+ rpcCaller?: CreditReservationRpcCaller
206
+ ): Promise<MeteredEmbeddingReservationResult> {
207
+ const sbAdmin = await getRpcCaller(rpcCaller);
208
+ const gatewayModelId = resolveGatewayModelId(params.modelId);
209
+ const rpc = (sbAdmin.rpc as (...args: unknown[]) => unknown).bind(
210
+ sbAdmin
211
+ ) as (
212
+ fn: 'reserve_metered_embedding_credits',
213
+ args: ReserveMeteredEmbeddingCreditsRpcParams
214
+ ) => Promise<{
215
+ data: ReserveMeteredEmbeddingCreditsRpcRow[] | null;
216
+ error: RpcError;
217
+ }>;
218
+
219
+ let data: ReserveMeteredEmbeddingCreditsRpcRow[] | null = null;
220
+ let error: RpcError = null;
221
+ try {
222
+ const result = await rpc('reserve_metered_embedding_credits', {
223
+ p_ws_id: params.wsId,
224
+ p_user_id: params.userId ?? null,
225
+ p_model_id: gatewayModelId,
226
+ p_input_tokens: params.inputTokens,
227
+ p_feature: 'embeddings',
228
+ p_metadata: (params.metadata ?? {}) as Json,
229
+ ...((params.expiresInSeconds ?? 0) > 0
230
+ ? { p_expires_in_seconds: params.expiresInSeconds }
231
+ : {}),
232
+ });
233
+ data = result.data;
234
+ error = result.error;
235
+ } catch (caughtError) {
236
+ data = null;
237
+ error = rpcErrorFromUnknown(caughtError);
238
+ }
239
+
240
+ if (error) {
241
+ console.error('Error reserving metered embedding credits:', error);
242
+ return {
243
+ success: false,
244
+ reservationId: null,
245
+ creditsReserved: 0,
246
+ costUsd: 0,
247
+ remainingCredits: 0,
248
+ errorCode: 'RESERVATION_FAILED',
249
+ };
250
+ }
251
+
252
+ const row = Array.isArray(data) ? data[0] : data;
253
+ if (!row) {
254
+ return {
255
+ success: false,
256
+ reservationId: null,
257
+ creditsReserved: 0,
258
+ costUsd: 0,
259
+ remainingCredits: 0,
260
+ errorCode: 'NO_RESULT',
261
+ };
262
+ }
263
+
264
+ return {
265
+ success: row.success ?? false,
266
+ reservationId: row.reservation_id ?? null,
267
+ creditsReserved: Number(row.credits_reserved ?? 0),
268
+ costUsd: Number(row.cost_usd ?? 0),
269
+ remainingCredits: Number(row.remaining_credits ?? 0),
270
+ errorCode: row.error_code ?? null,
271
+ };
272
+ }
273
+
274
+ export async function commitMeteredEmbeddingCredits(
275
+ reservationId: string,
276
+ metadata?: Record<string, unknown>,
277
+ rpcCaller?: CreditReservationRpcCaller
278
+ ): Promise<CreditReservationCommitResult> {
279
+ const sbAdmin = await getRpcCaller(rpcCaller);
280
+ const wsId = typeof metadata?.wsId === 'string' ? metadata.wsId : null;
281
+ const userId = typeof metadata?.userId === 'string' ? metadata.userId : null;
282
+ const rpc = (sbAdmin.rpc as (...args: unknown[]) => unknown).bind(
283
+ sbAdmin
284
+ ) as (
285
+ fn: 'commit_metered_embedding_credits',
286
+ args: CommitMeteredEmbeddingCreditsRpcParams
287
+ ) => Promise<{
288
+ data: CommitFixedCreditReservationRpcRow[] | null;
289
+ error: RpcError;
290
+ }>;
291
+
292
+ let data: CommitFixedCreditReservationRpcRow[] | null = null;
293
+ let error: RpcError = null;
294
+ try {
295
+ const result = await rpc('commit_metered_embedding_credits', {
296
+ p_reservation_id: reservationId,
297
+ p_metadata: (metadata ?? {}) as Json,
298
+ });
299
+ data = result.data;
300
+ error = result.error;
301
+ } catch (caughtError) {
302
+ data = null;
303
+ error = rpcErrorFromUnknown(caughtError);
304
+ }
305
+
306
+ if (error) {
307
+ console.error('Error committing metered embedding credits:', error);
308
+ return {
309
+ success: false,
310
+ creditsDeducted: 0,
311
+ remainingCredits: 0,
312
+ errorCode: 'COMMIT_FAILED',
313
+ };
314
+ }
315
+
316
+ const row = Array.isArray(data) ? data[0] : data;
317
+ if (!row) {
318
+ return {
319
+ success: false,
320
+ creditsDeducted: 0,
321
+ remainingCredits: 0,
322
+ errorCode: 'NO_RESULT',
323
+ };
324
+ }
325
+
326
+ const result = {
327
+ success: row.success ?? false,
328
+ creditsDeducted: Number(row.credits_deducted ?? 0),
329
+ remainingCredits: Number(row.remaining_credits ?? 0),
330
+ errorCode: row.error_code ?? null,
331
+ };
332
+
333
+ if (result.success && wsId && userId) {
334
+ await invalidateAiCreditSnapshot({ wsId, userId });
335
+ }
336
+
337
+ return result;
338
+ }
339
+
340
+ export async function releaseMeteredEmbeddingCredits(
341
+ reservationId: string,
342
+ metadata?: Record<string, unknown>,
343
+ rpcCaller?: CreditReservationRpcCaller
344
+ ): Promise<CreditReservationReleaseResult> {
345
+ const sbAdmin = await getRpcCaller(rpcCaller);
346
+ const rpc = (sbAdmin.rpc as (...args: unknown[]) => unknown).bind(
347
+ sbAdmin
348
+ ) as (
349
+ fn: 'release_metered_embedding_credits',
350
+ args: ReleaseMeteredEmbeddingCreditsRpcParams
351
+ ) => Promise<{
352
+ data: ReleaseFixedCreditReservationRpcRow[] | null;
353
+ error: RpcError;
354
+ }>;
355
+
356
+ let data: ReleaseFixedCreditReservationRpcRow[] | null = null;
357
+ let error: RpcError = null;
358
+ try {
359
+ const result = await rpc('release_metered_embedding_credits', {
360
+ p_reservation_id: reservationId,
361
+ p_metadata: (metadata ?? {}) as Json,
362
+ });
363
+ data = result.data;
364
+ error = result.error;
365
+ } catch (caughtError) {
366
+ data = null;
367
+ error = rpcErrorFromUnknown(caughtError);
368
+ }
369
+
370
+ if (error) {
371
+ console.error('Error releasing metered embedding credits:', error);
372
+ return {
373
+ success: false,
374
+ remainingCredits: 0,
375
+ errorCode: 'RELEASE_FAILED',
376
+ };
377
+ }
378
+
379
+ const row = Array.isArray(data) ? data[0] : data;
380
+ if (!row) {
381
+ return {
382
+ success: false,
383
+ remainingCredits: 0,
384
+ errorCode: 'NO_RESULT',
385
+ };
386
+ }
387
+
388
+ return {
389
+ success: row.success ?? false,
390
+ remainingCredits: Number(row.remaining_credits ?? 0),
391
+ errorCode: row.error_code ?? null,
392
+ };
393
+ }
394
+
395
+ export async function commitFixedAiCreditReservation(
396
+ reservationId: string,
397
+ metadata?: Record<string, unknown>,
398
+ rpcCaller?: CreditReservationRpcCaller
399
+ ): Promise<CreditReservationCommitResult> {
400
+ const sbAdmin = await getRpcCaller(rpcCaller);
401
+ const wsId = typeof metadata?.wsId === 'string' ? metadata.wsId : null;
402
+ const userId = typeof metadata?.userId === 'string' ? metadata.userId : null;
403
+ let inFlightMarked = false;
404
+ const rpc = (sbAdmin.rpc as (...args: unknown[]) => unknown).bind(
405
+ sbAdmin
406
+ ) as (
407
+ fn: 'commit_fixed_ai_credit_reservation',
408
+ args: CommitFixedCreditReservationRpcParams
409
+ ) => Promise<{
410
+ data: CommitFixedCreditReservationRpcRow[] | null;
411
+ error: RpcError;
412
+ }>;
413
+
414
+ if (wsId && userId) {
415
+ inFlightMarked = await incrementAiCreditChargeInFlight({ wsId, userId });
416
+ }
417
+
418
+ let data: CommitFixedCreditReservationRpcRow[] | null = null;
419
+ let error: RpcError = null;
420
+ try {
421
+ const result = await rpc('commit_fixed_ai_credit_reservation', {
422
+ p_reservation_id: reservationId,
423
+ p_metadata: (metadata ?? {}) as Json,
424
+ });
425
+ data = result.data;
426
+ error = result.error;
427
+ } catch (caughtError) {
428
+ data = null;
429
+ error = rpcErrorFromUnknown(caughtError);
430
+ } finally {
431
+ if (inFlightMarked && wsId && userId) {
432
+ await decrementAiCreditChargeInFlight({ wsId, userId });
433
+ }
434
+ }
435
+
436
+ if (error) {
437
+ console.error('Error committing AI credit reservation:', error);
438
+ return {
439
+ success: false,
440
+ creditsDeducted: 0,
441
+ remainingCredits: 0,
442
+ errorCode: 'COMMIT_FAILED',
443
+ };
444
+ }
445
+
446
+ const row = Array.isArray(data) ? data[0] : data;
447
+ if (!row) {
448
+ return {
449
+ success: false,
450
+ creditsDeducted: 0,
451
+ remainingCredits: 0,
452
+ errorCode: 'NO_RESULT',
453
+ };
454
+ }
455
+
456
+ const result = {
457
+ success: row.success ?? false,
458
+ creditsDeducted: Number(row.credits_deducted ?? 0),
459
+ remainingCredits: Number(row.remaining_credits ?? 0),
460
+ errorCode: row.error_code ?? null,
461
+ };
462
+
463
+ if (result.success && wsId && userId) {
464
+ await invalidateAiCreditSnapshot({ wsId, userId });
465
+ }
466
+
467
+ return result;
468
+ }
469
+
470
+ export async function releaseFixedAiCreditReservation(
471
+ reservationId: string,
472
+ metadata?: Record<string, unknown>,
473
+ rpcCaller?: CreditReservationRpcCaller
474
+ ): Promise<CreditReservationReleaseResult> {
475
+ const sbAdmin = await getRpcCaller(rpcCaller);
476
+ const rpc = (sbAdmin.rpc as (...args: unknown[]) => unknown).bind(
477
+ sbAdmin
478
+ ) as (
479
+ fn: 'release_fixed_ai_credit_reservation',
480
+ args: ReleaseFixedCreditReservationRpcParams
481
+ ) => Promise<{
482
+ data: ReleaseFixedCreditReservationRpcRow[] | null;
483
+ error: RpcError;
484
+ }>;
485
+
486
+ const { data, error } = await rpc('release_fixed_ai_credit_reservation', {
487
+ p_reservation_id: reservationId,
488
+ p_metadata: (metadata ?? {}) as Json,
489
+ });
490
+
491
+ if (error) {
492
+ console.error('Error releasing AI credit reservation:', error);
493
+ return {
494
+ success: false,
495
+ remainingCredits: 0,
496
+ errorCode: 'RELEASE_FAILED',
497
+ };
498
+ }
499
+
500
+ const row = Array.isArray(data) ? data[0] : data;
501
+ if (!row) {
502
+ return {
503
+ success: false,
504
+ remainingCredits: 0,
505
+ errorCode: 'NO_RESULT',
506
+ };
507
+ }
508
+
509
+ return {
510
+ success: row.success ?? false,
511
+ remainingCredits: Number(row.remaining_credits ?? 0),
512
+ errorCode: row.error_code ?? null,
513
+ };
514
+ }