@tuturuuu/ai 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tuturuuu/ai",
3
3
  "license": "MIT",
4
- "version": "0.12.0",
4
+ "version": "0.13.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/tutur3u/platform",
@@ -76,7 +76,7 @@
76
76
  "@streamdown/math": "^1.0.2",
77
77
  "@streamdown/mermaid": "^1.0.2",
78
78
  "@tuturuuu/google": "0.1.0",
79
- "@tuturuuu/internal-api": "0.38.0",
79
+ "@tuturuuu/internal-api": "0.38.1",
80
80
  "@tuturuuu/supabase": "0.5.1",
81
81
  "@tuturuuu/utils": "0.28.0",
82
82
  "@vercel/sandbox": "^3.2.1",
@@ -115,8 +115,9 @@ export async function calculateAiStudioUsageCost(
115
115
  }
116
116
 
117
117
  export type BeginAiStudioRunInput = {
118
+ rejectExisting?: boolean;
118
119
  actorId: string;
119
- apiKeyId: string;
120
+ apiKeyId?: string;
120
121
  feature: string;
121
122
  idempotencyKey?: string | null;
122
123
  metadata?: Json;
@@ -165,7 +166,8 @@ export async function beginAiStudioRun(
165
166
  const { data, error } = await sbAdmin
166
167
  .schema('private')
167
168
  .rpc('begin_ai_studio_run', {
168
- p_api_key_id: input.apiKeyId,
169
+ // SQL accepts NULL for first-party workspace reservations; generated RPC types do not express nullable arguments.
170
+ p_api_key_id: input.apiKeyId ?? (null as unknown as string),
169
171
  p_feature: input.feature,
170
172
  p_idempotency_key: input.idempotencyKey ?? undefined,
171
173
  p_metadata: input.metadata,
@@ -177,6 +179,19 @@ export async function beginAiStudioRun(
177
179
  });
178
180
 
179
181
  const result = data?.[0];
182
+ // The transactional RPC returns NULL remaining_credits for an existing run;
183
+ // a concurrent insert loses the unique idempotency constraint and rolls back.
184
+ // Fence before provider execution without a second, fallible claim update.
185
+ if (
186
+ input.rejectExisting &&
187
+ (error?.code === '23505' ||
188
+ (result?.success && result.remaining_credits === null))
189
+ ) {
190
+ throw new AiStudioError(
191
+ 'This sponsored request is already running or completed.',
192
+ { code: 'invalid_request_error', status: 409 }
193
+ );
194
+ }
180
195
  if (error || !result?.success || !result.run_id || !result.reservation_id) {
181
196
  throw reservationError(result?.error_code ?? null);
182
197
  }