@resultdev/sdk 0.3.1 → 0.3.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @resultdev/sdk
2
2
 
3
+ ## 0.3.2
4
+
5
+ - AI calls made through the SDK now report returned token usage to the
6
+ matching Result app. Operations can show per-app AI usage even while the
7
+ upstream partnership AI counters remain unavailable.
8
+ - Caller AbortSignal listeners are now detached once a response body is
9
+ fully consumed, instead of accumulating on long-lived signals across
10
+ requests.
11
+
3
12
  ## 0.3.1
4
13
 
5
14
  - A missing `baseUrl` now throws `MISSING_BASE_URL` at client construction
@@ -1,4 +1,4 @@
1
- import { e as AuthSession, U as UserSchema, a as ResultConfig, G as AuthRefreshResponse, C as ChatCompletionRequest, B as ToolCall, H as Annotation, I as ImageGenerationRequest, j as EmbeddingsRequest, J as EmbeddingObject, h as CreateUserRequest, K as CreateUserResponse, o as ResultError, g as CreateSessionRequest, L as CreateSessionResponse, l as OAuthProvidersSchema, M as RefreshSessionResponse, N as GetProfileResponse, Q as SendVerificationEmailRequest, V as VerifyEmailRequest, W as VerifyEmailResponse, X as SendResetPasswordEmailRequest, Y as ExchangeResetPasswordTokenRequest, Z as ExchangeResetPasswordTokenResponse, _ as ResetPasswordResponse, $ as GetPublicAuthConfigResponse, S as SendRawEmailRequest, q as SendEmailResponse, v as SubscribeResponse, r as SocketMessage, P as PresenceMember, u as StorageFileSchema, a0 as ListObjectsResponseSchema, i as DeleteObjectsResponse, A as Analytics, w as Support } from './types-BmU3deRO.js';
1
+ import { e as AuthSession, U as UserSchema, a as ResultConfig, G as AuthRefreshResponse, C as ChatCompletionRequest, B as ToolCall, H as Annotation, I as ImageGenerationRequest, j as EmbeddingsRequest, J as EmbeddingObject, h as CreateUserRequest, K as CreateUserResponse, o as ResultError, g as CreateSessionRequest, L as CreateSessionResponse, l as OAuthProvidersSchema, M as RefreshSessionResponse, N as GetProfileResponse, Q as SendVerificationEmailRequest, V as VerifyEmailRequest, W as VerifyEmailResponse, X as SendResetPasswordEmailRequest, Y as ExchangeResetPasswordTokenRequest, Z as ExchangeResetPasswordTokenResponse, _ as ResetPasswordResponse, $ as GetPublicAuthConfigResponse, S as SendRawEmailRequest, q as SendEmailResponse, v as SubscribeResponse, r as SocketMessage, P as PresenceMember, u as StorageFileSchema, a0 as ListObjectsResponseSchema, i as DeleteObjectsResponse, A as Analytics, w as Support } from './types-BTdjI7Yj.js';
2
2
  import * as _supabase_postgrest_js from '@supabase/postgrest-js';
3
3
  import { PostgrestClient } from '@supabase/postgrest-js';
4
4
 
@@ -200,6 +200,8 @@ declare class HttpClient {
200
200
  setRefreshToken(token: string | null): void;
201
201
  /** Returns the current default headers including the authorization header if set. */
202
202
  getHeaders(): Record<string, string>;
203
+ /** Returns the publishable project key used to identify SDK usage reports. */
204
+ getAnonKey(): string | undefined;
203
205
  refreshAccessToken(): Promise<AuthRefreshResponse>;
204
206
  /** Returns a token safe to use for a new connection handshake. */
205
207
  getValidAccessToken(leewaySeconds?: number): Promise<string | null>;
@@ -1,4 +1,4 @@
1
- import { e as AuthSession, U as UserSchema, a as ResultConfig, G as AuthRefreshResponse, C as ChatCompletionRequest, B as ToolCall, H as Annotation, I as ImageGenerationRequest, j as EmbeddingsRequest, J as EmbeddingObject, h as CreateUserRequest, K as CreateUserResponse, o as ResultError, g as CreateSessionRequest, L as CreateSessionResponse, l as OAuthProvidersSchema, M as RefreshSessionResponse, N as GetProfileResponse, Q as SendVerificationEmailRequest, V as VerifyEmailRequest, W as VerifyEmailResponse, X as SendResetPasswordEmailRequest, Y as ExchangeResetPasswordTokenRequest, Z as ExchangeResetPasswordTokenResponse, _ as ResetPasswordResponse, $ as GetPublicAuthConfigResponse, S as SendRawEmailRequest, q as SendEmailResponse, v as SubscribeResponse, r as SocketMessage, P as PresenceMember, u as StorageFileSchema, a0 as ListObjectsResponseSchema, i as DeleteObjectsResponse, A as Analytics, w as Support } from './types-BmU3deRO.cjs';
1
+ import { e as AuthSession, U as UserSchema, a as ResultConfig, G as AuthRefreshResponse, C as ChatCompletionRequest, B as ToolCall, H as Annotation, I as ImageGenerationRequest, j as EmbeddingsRequest, J as EmbeddingObject, h as CreateUserRequest, K as CreateUserResponse, o as ResultError, g as CreateSessionRequest, L as CreateSessionResponse, l as OAuthProvidersSchema, M as RefreshSessionResponse, N as GetProfileResponse, Q as SendVerificationEmailRequest, V as VerifyEmailRequest, W as VerifyEmailResponse, X as SendResetPasswordEmailRequest, Y as ExchangeResetPasswordTokenRequest, Z as ExchangeResetPasswordTokenResponse, _ as ResetPasswordResponse, $ as GetPublicAuthConfigResponse, S as SendRawEmailRequest, q as SendEmailResponse, v as SubscribeResponse, r as SocketMessage, P as PresenceMember, u as StorageFileSchema, a0 as ListObjectsResponseSchema, i as DeleteObjectsResponse, A as Analytics, w as Support } from './types-BTdjI7Yj.cjs';
2
2
  import * as _supabase_postgrest_js from '@supabase/postgrest-js';
3
3
  import { PostgrestClient } from '@supabase/postgrest-js';
4
4
 
@@ -200,6 +200,8 @@ declare class HttpClient {
200
200
  setRefreshToken(token: string | null): void;
201
201
  /** Returns the current default headers including the authorization header if set. */
202
202
  getHeaders(): Record<string, string>;
203
+ /** Returns the publishable project key used to identify SDK usage reports. */
204
+ getAnonKey(): string | undefined;
203
205
  refreshAccessToken(): Promise<AuthRefreshResponse>;
204
206
  /** Returns a token safe to use for a new connection handshake. */
205
207
  getValidAccessToken(leewaySeconds?: number): Promise<string | null>;
package/dist/index.cjs CHANGED
@@ -602,6 +602,7 @@ var HttpClient = class {
602
602
  }
603
603
  let controller;
604
604
  let timer;
605
+ let removeCallerAbortListener;
605
606
  if (this.timeout > 0 || callerSignal) {
606
607
  controller = new AbortController();
607
608
  if (this.timeout > 0) {
@@ -615,16 +616,11 @@ var HttpClient = class {
615
616
  callerSignal.addEventListener("abort", onCallerAbort, {
616
617
  once: true
617
618
  });
618
- controller.signal.addEventListener(
619
- "abort",
620
- () => {
621
- callerSignal.removeEventListener("abort", onCallerAbort);
622
- },
623
- { once: true }
624
- );
619
+ removeCallerAbortListener = () => callerSignal.removeEventListener("abort", onCallerAbort);
625
620
  }
626
621
  }
627
622
  }
623
+ let detachOnExit = true;
628
624
  try {
629
625
  const response = await this.fetch(url, {
630
626
  method,
@@ -645,14 +641,9 @@ var HttpClient = class {
645
641
  );
646
642
  continue;
647
643
  }
648
- if (timer !== void 0) {
649
- clearTimeout(timer);
650
- }
651
- return response;
644
+ detachOnExit = false;
645
+ return { response, releaseCallerSignal: removeCallerAbortListener };
652
646
  } catch (err) {
653
- if (timer !== void 0) {
654
- clearTimeout(timer);
655
- }
656
647
  if (err?.name === "AbortError") {
657
648
  if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
658
649
  throw new ResultError(
@@ -672,6 +663,13 @@ var HttpClient = class {
672
663
  0,
673
664
  "NETWORK_ERROR"
674
665
  );
666
+ } finally {
667
+ if (timer !== void 0) {
668
+ clearTimeout(timer);
669
+ }
670
+ if (detachOnExit) {
671
+ removeCallerAbortListener?.();
672
+ }
675
673
  }
676
674
  }
677
675
  throw lastError || new ResultError(
@@ -734,7 +732,7 @@ var HttpClient = class {
734
732
  });
735
733
  }
736
734
  this.logger.logRequest(method, url, requestHeaders, processedBody);
737
- const response = await this.fetchWithRetry({
735
+ const { response, releaseCallerSignal } = await this.fetchWithRetry({
738
736
  method,
739
737
  url,
740
738
  headers: requestHeaders,
@@ -757,6 +755,8 @@ var HttpClient = class {
757
755
  );
758
756
  }
759
757
  throw err;
758
+ } finally {
759
+ releaseCallerSignal?.();
760
760
  }
761
761
  this.logger.logResponse(
762
762
  method,
@@ -858,7 +858,7 @@ var HttpClient = class {
858
858
  const callerSignal = initSignal ?? request?.signal;
859
859
  const maxAttempts = IDEMPOTENT_METHODS.has(method.toUpperCase()) ? this.retryCount : 0;
860
860
  this.logger.logRequest(method, url, requestHeaders, body);
861
- const response = await this.fetchWithRetry({
861
+ const { response } = await this.fetchWithRetry({
862
862
  method,
863
863
  url,
864
864
  headers: requestHeaders,
@@ -959,6 +959,10 @@ var HttpClient = class {
959
959
  }
960
960
  return headers;
961
961
  }
962
+ /** Returns the publishable project key used to identify SDK usage reports. */
963
+ getAnonKey() {
964
+ return this.anonKey;
965
+ }
962
966
  async refreshAccessToken() {
963
967
  if (this.isRefreshing) {
964
968
  return this.refreshPromise;
@@ -1028,6 +1032,47 @@ var HttpClient = class {
1028
1032
  }
1029
1033
  };
1030
1034
 
1035
+ // src/modules/ai-usage.ts
1036
+ var USAGE_ENDPOINT = "https://beta.result.dev/api/usage/ai";
1037
+ async function reportAiUsage(http, requestId, model, kind, tokens) {
1038
+ const anonKey = http.getAnonKey();
1039
+ if (!anonKey || !Number.isSafeInteger(tokens) || tokens <= 0) {
1040
+ return;
1041
+ }
1042
+ const controller = new AbortController();
1043
+ const timeout = setTimeout(() => controller.abort(), 2e3);
1044
+ try {
1045
+ const request = {
1046
+ method: "POST",
1047
+ headers: { "content-type": "application/json" },
1048
+ body: JSON.stringify({
1049
+ requestId,
1050
+ backendUrl: http.baseUrl,
1051
+ anonKey,
1052
+ model,
1053
+ kind,
1054
+ tokens
1055
+ }),
1056
+ keepalive: true,
1057
+ signal: controller.signal
1058
+ };
1059
+ for (let attempt = 0; attempt < 2; attempt++) {
1060
+ try {
1061
+ const response = await http.fetch(USAGE_ENDPOINT, request);
1062
+ if (response.status < 500) return;
1063
+ } catch {
1064
+ if (controller.signal.aborted) return;
1065
+ }
1066
+ }
1067
+ } catch {
1068
+ } finally {
1069
+ clearTimeout(timeout);
1070
+ }
1071
+ }
1072
+ function aiUsageRequestId() {
1073
+ return globalThis.crypto?.randomUUID?.() ?? null;
1074
+ }
1075
+
1031
1076
  // src/modules/ai.ts
1032
1077
  var SUPPORTED_AUDIO_FORMATS = [
1033
1078
  "wav",
@@ -1115,6 +1160,7 @@ var ChatCompletions = class {
1115
1160
  }
1116
1161
  http;
1117
1162
  async create(params) {
1163
+ const usageRequestId = aiUsageRequestId();
1118
1164
  const contentSummary = summarizeContent(params.messages ?? []);
1119
1165
  assertSupportedContent(params.model, contentSummary);
1120
1166
  const backendParams = {
@@ -1159,7 +1205,7 @@ var ChatCompletions = class {
1159
1205
  contentSummary
1160
1206
  );
1161
1207
  }
1162
- return this.parseSSEStream(response2, params.model);
1208
+ return this.parseSSEStream(response2, params.model, usageRequestId);
1163
1209
  }
1164
1210
  let response;
1165
1211
  try {
@@ -1168,6 +1214,16 @@ var ChatCompletions = class {
1168
1214
  throw enrichUpstreamError(error, contentSummary);
1169
1215
  }
1170
1216
  const content = response.text || "";
1217
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1218
+ if (usageRequestId) {
1219
+ await reportAiUsage(
1220
+ this.http,
1221
+ usageRequestId,
1222
+ params.model,
1223
+ "chat",
1224
+ totalTokens
1225
+ );
1226
+ }
1171
1227
  return {
1172
1228
  id: `chatcmpl-${Date.now()}`,
1173
1229
  object: "chat.completion",
@@ -1194,14 +1250,14 @@ var ChatCompletions = class {
1194
1250
  usage: {
1195
1251
  prompt_tokens: response.metadata?.usage?.promptTokens || 0,
1196
1252
  completion_tokens: response.metadata?.usage?.completionTokens || 0,
1197
- total_tokens: response.metadata?.usage?.totalTokens || 0
1253
+ total_tokens: totalTokens
1198
1254
  }
1199
1255
  };
1200
1256
  }
1201
1257
  /**
1202
1258
  * Parse SSE stream into async iterable of OpenAI-like chunks
1203
1259
  */
1204
- async *parseSSEStream(response, model) {
1260
+ async *parseSSEStream(response, model, usageRequestId) {
1205
1261
  const reader = response.body?.getReader();
1206
1262
  if (!reader) {
1207
1263
  throw new ResultError(
@@ -1279,6 +1335,15 @@ var ChatCompletions = class {
1279
1335
  };
1280
1336
  }
1281
1337
  if (data.tokenUsage) {
1338
+ if (usageRequestId) {
1339
+ await reportAiUsage(
1340
+ this.http,
1341
+ usageRequestId,
1342
+ model,
1343
+ "chat",
1344
+ data.tokenUsage.totalTokens || 0
1345
+ );
1346
+ }
1282
1347
  yield {
1283
1348
  id: `chatcmpl-${Date.now()}`,
1284
1349
  object: "chat.completion.chunk",
@@ -1350,17 +1415,28 @@ var Embeddings = class {
1350
1415
  * ```
1351
1416
  */
1352
1417
  async create(params) {
1418
+ const usageRequestId = aiUsageRequestId();
1353
1419
  const response = await this.http.post(
1354
1420
  "/api/ai/embeddings",
1355
1421
  params
1356
1422
  );
1423
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1424
+ if (usageRequestId) {
1425
+ await reportAiUsage(
1426
+ this.http,
1427
+ usageRequestId,
1428
+ params.model,
1429
+ "embeddings",
1430
+ totalTokens
1431
+ );
1432
+ }
1357
1433
  return {
1358
1434
  object: response.object,
1359
1435
  data: response.data,
1360
1436
  model: response.metadata?.model,
1361
1437
  usage: response.metadata?.usage ? {
1362
1438
  prompt_tokens: response.metadata.usage.promptTokens || 0,
1363
- total_tokens: response.metadata.usage.totalTokens || 0
1439
+ total_tokens: totalTokens
1364
1440
  } : {
1365
1441
  prompt_tokens: 0,
1366
1442
  total_tokens: 0
@@ -1398,6 +1474,7 @@ var Images = class {
1398
1474
  * ```
1399
1475
  */
1400
1476
  async generate(params) {
1477
+ const usageRequestId = aiUsageRequestId();
1401
1478
  const response = await this.http.post(
1402
1479
  "/api/ai/image/generation",
1403
1480
  params
@@ -1411,6 +1488,16 @@ var Images = class {
1411
1488
  } else if (response.text) {
1412
1489
  data = [{ content: response.text }];
1413
1490
  }
1491
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1492
+ if (usageRequestId) {
1493
+ await reportAiUsage(
1494
+ this.http,
1495
+ usageRequestId,
1496
+ params.model,
1497
+ "image",
1498
+ totalTokens
1499
+ );
1500
+ }
1414
1501
  return {
1415
1502
  created: Math.floor(Date.now() / 1e3),
1416
1503
  data,
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { R as ResultClient } from './client-BrOD2EO5.cjs';
2
- export { A as AI, a as AccessTokenChangeEvent, b as Auth, c as AuthChangeEvent, d as AuthStateChangeCallback, C as ChatCompletion, e as ChatCompletionChoice, f as ChatCompletionChunk, g as ChatCompletionUsage, h as ConnectionState, D as Database, E as Emails, i as EmbeddingsResult, j as EventCallback, F as FunctionInvokeOptions, k as Functions, H as HttpClient, I as ImageGenerationResult, L as Logger, l as Realtime, S as Storage, m as StorageBucket, n as StorageResponse } from './client-BrOD2EO5.cjs';
3
- import { R as ResultAdminConfig, a as ResultConfig } from './types-BmU3deRO.cjs';
4
- export { A as Analytics, b as AnalyticsOptions, c as ApiError, d as AuthErrorResponse, e as AuthSession, C as ChatCompletionRequest, f as ChatMessageSchema, g as CreateSessionRequest, h as CreateUserRequest, D as DeleteObjectResult, i as DeleteObjectsResponse, E as ERROR_CODES, j as EmbeddingsRequest, k as ErrorCode, I as ImageGenerationRequest, O as OAUTH_PROVIDERS, l as OAuthProvidersSchema, P as PresenceMember, m as ProfileSchema, n as RealtimeErrorPayload, o as ResultError, p as ResultErrorCode, S as SendEmailOptions, q as SendEmailResponse, r as SocketMessage, s as StartThreadOptions, t as StartedThread, u as StorageFileSchema, v as SubscribeResponse, w as Support, x as SupportMessage, y as SupportOptions, z as SupportThread, T as Tool, B as ToolCall, F as ToolChoice, U as UserSchema } from './types-BmU3deRO.cjs';
1
+ import { R as ResultClient } from './client-N6MW9IE1.cjs';
2
+ export { A as AI, a as AccessTokenChangeEvent, b as Auth, c as AuthChangeEvent, d as AuthStateChangeCallback, C as ChatCompletion, e as ChatCompletionChoice, f as ChatCompletionChunk, g as ChatCompletionUsage, h as ConnectionState, D as Database, E as Emails, i as EmbeddingsResult, j as EventCallback, F as FunctionInvokeOptions, k as Functions, H as HttpClient, I as ImageGenerationResult, L as Logger, l as Realtime, S as Storage, m as StorageBucket, n as StorageResponse } from './client-N6MW9IE1.cjs';
3
+ import { R as ResultAdminConfig, a as ResultConfig } from './types-BTdjI7Yj.cjs';
4
+ export { A as Analytics, b as AnalyticsOptions, c as ApiError, d as AuthErrorResponse, e as AuthSession, C as ChatCompletionRequest, f as ChatMessageSchema, g as CreateSessionRequest, h as CreateUserRequest, D as DeleteObjectResult, i as DeleteObjectsResponse, E as ERROR_CODES, j as EmbeddingsRequest, k as ErrorCode, I as ImageGenerationRequest, O as OAUTH_PROVIDERS, l as OAuthProvidersSchema, P as PresenceMember, m as ProfileSchema, n as RealtimeErrorPayload, o as ResultError, p as ResultErrorCode, S as SendEmailOptions, q as SendEmailResponse, r as SocketMessage, s as StartThreadOptions, t as StartedThread, u as StorageFileSchema, v as SubscribeResponse, w as Support, x as SupportMessage, y as SupportOptions, z as SupportThread, T as Tool, B as ToolCall, F as ToolChoice, U as UserSchema } from './types-BTdjI7Yj.cjs';
5
5
  import '@supabase/postgrest-js';
6
6
 
7
7
  /**
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { R as ResultClient } from './client--Xjaaw1l.js';
2
- export { A as AI, a as AccessTokenChangeEvent, b as Auth, c as AuthChangeEvent, d as AuthStateChangeCallback, C as ChatCompletion, e as ChatCompletionChoice, f as ChatCompletionChunk, g as ChatCompletionUsage, h as ConnectionState, D as Database, E as Emails, i as EmbeddingsResult, j as EventCallback, F as FunctionInvokeOptions, k as Functions, H as HttpClient, I as ImageGenerationResult, L as Logger, l as Realtime, S as Storage, m as StorageBucket, n as StorageResponse } from './client--Xjaaw1l.js';
3
- import { R as ResultAdminConfig, a as ResultConfig } from './types-BmU3deRO.js';
4
- export { A as Analytics, b as AnalyticsOptions, c as ApiError, d as AuthErrorResponse, e as AuthSession, C as ChatCompletionRequest, f as ChatMessageSchema, g as CreateSessionRequest, h as CreateUserRequest, D as DeleteObjectResult, i as DeleteObjectsResponse, E as ERROR_CODES, j as EmbeddingsRequest, k as ErrorCode, I as ImageGenerationRequest, O as OAUTH_PROVIDERS, l as OAuthProvidersSchema, P as PresenceMember, m as ProfileSchema, n as RealtimeErrorPayload, o as ResultError, p as ResultErrorCode, S as SendEmailOptions, q as SendEmailResponse, r as SocketMessage, s as StartThreadOptions, t as StartedThread, u as StorageFileSchema, v as SubscribeResponse, w as Support, x as SupportMessage, y as SupportOptions, z as SupportThread, T as Tool, B as ToolCall, F as ToolChoice, U as UserSchema } from './types-BmU3deRO.js';
1
+ import { R as ResultClient } from './client-D5IGLPW4.js';
2
+ export { A as AI, a as AccessTokenChangeEvent, b as Auth, c as AuthChangeEvent, d as AuthStateChangeCallback, C as ChatCompletion, e as ChatCompletionChoice, f as ChatCompletionChunk, g as ChatCompletionUsage, h as ConnectionState, D as Database, E as Emails, i as EmbeddingsResult, j as EventCallback, F as FunctionInvokeOptions, k as Functions, H as HttpClient, I as ImageGenerationResult, L as Logger, l as Realtime, S as Storage, m as StorageBucket, n as StorageResponse } from './client-D5IGLPW4.js';
3
+ import { R as ResultAdminConfig, a as ResultConfig } from './types-BTdjI7Yj.js';
4
+ export { A as Analytics, b as AnalyticsOptions, c as ApiError, d as AuthErrorResponse, e as AuthSession, C as ChatCompletionRequest, f as ChatMessageSchema, g as CreateSessionRequest, h as CreateUserRequest, D as DeleteObjectResult, i as DeleteObjectsResponse, E as ERROR_CODES, j as EmbeddingsRequest, k as ErrorCode, I as ImageGenerationRequest, O as OAUTH_PROVIDERS, l as OAuthProvidersSchema, P as PresenceMember, m as ProfileSchema, n as RealtimeErrorPayload, o as ResultError, p as ResultErrorCode, S as SendEmailOptions, q as SendEmailResponse, r as SocketMessage, s as StartThreadOptions, t as StartedThread, u as StorageFileSchema, v as SubscribeResponse, w as Support, x as SupportMessage, y as SupportOptions, z as SupportThread, T as Tool, B as ToolCall, F as ToolChoice, U as UserSchema } from './types-BTdjI7Yj.js';
5
5
  import '@supabase/postgrest-js';
6
6
 
7
7
  /**
package/dist/index.js CHANGED
@@ -547,6 +547,7 @@ var HttpClient = class {
547
547
  }
548
548
  let controller;
549
549
  let timer;
550
+ let removeCallerAbortListener;
550
551
  if (this.timeout > 0 || callerSignal) {
551
552
  controller = new AbortController();
552
553
  if (this.timeout > 0) {
@@ -560,16 +561,11 @@ var HttpClient = class {
560
561
  callerSignal.addEventListener("abort", onCallerAbort, {
561
562
  once: true
562
563
  });
563
- controller.signal.addEventListener(
564
- "abort",
565
- () => {
566
- callerSignal.removeEventListener("abort", onCallerAbort);
567
- },
568
- { once: true }
569
- );
564
+ removeCallerAbortListener = () => callerSignal.removeEventListener("abort", onCallerAbort);
570
565
  }
571
566
  }
572
567
  }
568
+ let detachOnExit = true;
573
569
  try {
574
570
  const response = await this.fetch(url, {
575
571
  method,
@@ -590,14 +586,9 @@ var HttpClient = class {
590
586
  );
591
587
  continue;
592
588
  }
593
- if (timer !== void 0) {
594
- clearTimeout(timer);
595
- }
596
- return response;
589
+ detachOnExit = false;
590
+ return { response, releaseCallerSignal: removeCallerAbortListener };
597
591
  } catch (err) {
598
- if (timer !== void 0) {
599
- clearTimeout(timer);
600
- }
601
592
  if (err?.name === "AbortError") {
602
593
  if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
603
594
  throw new ResultError(
@@ -617,6 +608,13 @@ var HttpClient = class {
617
608
  0,
618
609
  "NETWORK_ERROR"
619
610
  );
611
+ } finally {
612
+ if (timer !== void 0) {
613
+ clearTimeout(timer);
614
+ }
615
+ if (detachOnExit) {
616
+ removeCallerAbortListener?.();
617
+ }
620
618
  }
621
619
  }
622
620
  throw lastError || new ResultError(
@@ -679,7 +677,7 @@ var HttpClient = class {
679
677
  });
680
678
  }
681
679
  this.logger.logRequest(method, url, requestHeaders, processedBody);
682
- const response = await this.fetchWithRetry({
680
+ const { response, releaseCallerSignal } = await this.fetchWithRetry({
683
681
  method,
684
682
  url,
685
683
  headers: requestHeaders,
@@ -702,6 +700,8 @@ var HttpClient = class {
702
700
  );
703
701
  }
704
702
  throw err;
703
+ } finally {
704
+ releaseCallerSignal?.();
705
705
  }
706
706
  this.logger.logResponse(
707
707
  method,
@@ -803,7 +803,7 @@ var HttpClient = class {
803
803
  const callerSignal = initSignal ?? request?.signal;
804
804
  const maxAttempts = IDEMPOTENT_METHODS.has(method.toUpperCase()) ? this.retryCount : 0;
805
805
  this.logger.logRequest(method, url, requestHeaders, body);
806
- const response = await this.fetchWithRetry({
806
+ const { response } = await this.fetchWithRetry({
807
807
  method,
808
808
  url,
809
809
  headers: requestHeaders,
@@ -904,6 +904,10 @@ var HttpClient = class {
904
904
  }
905
905
  return headers;
906
906
  }
907
+ /** Returns the publishable project key used to identify SDK usage reports. */
908
+ getAnonKey() {
909
+ return this.anonKey;
910
+ }
907
911
  async refreshAccessToken() {
908
912
  if (this.isRefreshing) {
909
913
  return this.refreshPromise;
@@ -973,6 +977,47 @@ var HttpClient = class {
973
977
  }
974
978
  };
975
979
 
980
+ // src/modules/ai-usage.ts
981
+ var USAGE_ENDPOINT = "https://beta.result.dev/api/usage/ai";
982
+ async function reportAiUsage(http, requestId, model, kind, tokens) {
983
+ const anonKey = http.getAnonKey();
984
+ if (!anonKey || !Number.isSafeInteger(tokens) || tokens <= 0) {
985
+ return;
986
+ }
987
+ const controller = new AbortController();
988
+ const timeout = setTimeout(() => controller.abort(), 2e3);
989
+ try {
990
+ const request = {
991
+ method: "POST",
992
+ headers: { "content-type": "application/json" },
993
+ body: JSON.stringify({
994
+ requestId,
995
+ backendUrl: http.baseUrl,
996
+ anonKey,
997
+ model,
998
+ kind,
999
+ tokens
1000
+ }),
1001
+ keepalive: true,
1002
+ signal: controller.signal
1003
+ };
1004
+ for (let attempt = 0; attempt < 2; attempt++) {
1005
+ try {
1006
+ const response = await http.fetch(USAGE_ENDPOINT, request);
1007
+ if (response.status < 500) return;
1008
+ } catch {
1009
+ if (controller.signal.aborted) return;
1010
+ }
1011
+ }
1012
+ } catch {
1013
+ } finally {
1014
+ clearTimeout(timeout);
1015
+ }
1016
+ }
1017
+ function aiUsageRequestId() {
1018
+ return globalThis.crypto?.randomUUID?.() ?? null;
1019
+ }
1020
+
976
1021
  // src/modules/ai.ts
977
1022
  var SUPPORTED_AUDIO_FORMATS = [
978
1023
  "wav",
@@ -1060,6 +1105,7 @@ var ChatCompletions = class {
1060
1105
  }
1061
1106
  http;
1062
1107
  async create(params) {
1108
+ const usageRequestId = aiUsageRequestId();
1063
1109
  const contentSummary = summarizeContent(params.messages ?? []);
1064
1110
  assertSupportedContent(params.model, contentSummary);
1065
1111
  const backendParams = {
@@ -1104,7 +1150,7 @@ var ChatCompletions = class {
1104
1150
  contentSummary
1105
1151
  );
1106
1152
  }
1107
- return this.parseSSEStream(response2, params.model);
1153
+ return this.parseSSEStream(response2, params.model, usageRequestId);
1108
1154
  }
1109
1155
  let response;
1110
1156
  try {
@@ -1113,6 +1159,16 @@ var ChatCompletions = class {
1113
1159
  throw enrichUpstreamError(error, contentSummary);
1114
1160
  }
1115
1161
  const content = response.text || "";
1162
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1163
+ if (usageRequestId) {
1164
+ await reportAiUsage(
1165
+ this.http,
1166
+ usageRequestId,
1167
+ params.model,
1168
+ "chat",
1169
+ totalTokens
1170
+ );
1171
+ }
1116
1172
  return {
1117
1173
  id: `chatcmpl-${Date.now()}`,
1118
1174
  object: "chat.completion",
@@ -1139,14 +1195,14 @@ var ChatCompletions = class {
1139
1195
  usage: {
1140
1196
  prompt_tokens: response.metadata?.usage?.promptTokens || 0,
1141
1197
  completion_tokens: response.metadata?.usage?.completionTokens || 0,
1142
- total_tokens: response.metadata?.usage?.totalTokens || 0
1198
+ total_tokens: totalTokens
1143
1199
  }
1144
1200
  };
1145
1201
  }
1146
1202
  /**
1147
1203
  * Parse SSE stream into async iterable of OpenAI-like chunks
1148
1204
  */
1149
- async *parseSSEStream(response, model) {
1205
+ async *parseSSEStream(response, model, usageRequestId) {
1150
1206
  const reader = response.body?.getReader();
1151
1207
  if (!reader) {
1152
1208
  throw new ResultError(
@@ -1224,6 +1280,15 @@ var ChatCompletions = class {
1224
1280
  };
1225
1281
  }
1226
1282
  if (data.tokenUsage) {
1283
+ if (usageRequestId) {
1284
+ await reportAiUsage(
1285
+ this.http,
1286
+ usageRequestId,
1287
+ model,
1288
+ "chat",
1289
+ data.tokenUsage.totalTokens || 0
1290
+ );
1291
+ }
1227
1292
  yield {
1228
1293
  id: `chatcmpl-${Date.now()}`,
1229
1294
  object: "chat.completion.chunk",
@@ -1295,17 +1360,28 @@ var Embeddings = class {
1295
1360
  * ```
1296
1361
  */
1297
1362
  async create(params) {
1363
+ const usageRequestId = aiUsageRequestId();
1298
1364
  const response = await this.http.post(
1299
1365
  "/api/ai/embeddings",
1300
1366
  params
1301
1367
  );
1368
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1369
+ if (usageRequestId) {
1370
+ await reportAiUsage(
1371
+ this.http,
1372
+ usageRequestId,
1373
+ params.model,
1374
+ "embeddings",
1375
+ totalTokens
1376
+ );
1377
+ }
1302
1378
  return {
1303
1379
  object: response.object,
1304
1380
  data: response.data,
1305
1381
  model: response.metadata?.model,
1306
1382
  usage: response.metadata?.usage ? {
1307
1383
  prompt_tokens: response.metadata.usage.promptTokens || 0,
1308
- total_tokens: response.metadata.usage.totalTokens || 0
1384
+ total_tokens: totalTokens
1309
1385
  } : {
1310
1386
  prompt_tokens: 0,
1311
1387
  total_tokens: 0
@@ -1343,6 +1419,7 @@ var Images = class {
1343
1419
  * ```
1344
1420
  */
1345
1421
  async generate(params) {
1422
+ const usageRequestId = aiUsageRequestId();
1346
1423
  const response = await this.http.post(
1347
1424
  "/api/ai/image/generation",
1348
1425
  params
@@ -1356,6 +1433,16 @@ var Images = class {
1356
1433
  } else if (response.text) {
1357
1434
  data = [{ content: response.text }];
1358
1435
  }
1436
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1437
+ if (usageRequestId) {
1438
+ await reportAiUsage(
1439
+ this.http,
1440
+ usageRequestId,
1441
+ params.model,
1442
+ "image",
1443
+ totalTokens
1444
+ );
1445
+ }
1359
1446
  return {
1360
1447
  created: Math.floor(Date.now() / 1e3),
1361
1448
  data,
@@ -1,4 +1,4 @@
1
- import { a as ResultConfig, o as ResultError } from './types-BmU3deRO.js';
1
+ import { a as ResultConfig, o as ResultError } from './types-BTdjI7Yj.js';
2
2
 
3
3
  declare const DEFAULT_ACCESS_TOKEN_COOKIE = "result_access_token";
4
4
  declare const DEFAULT_REFRESH_TOKEN_COOKIE = "result_refresh_token";
@@ -1,4 +1,4 @@
1
- import { a as ResultConfig, o as ResultError } from './types-BmU3deRO.cjs';
1
+ import { a as ResultConfig, o as ResultError } from './types-BTdjI7Yj.cjs';
2
2
 
3
3
  declare const DEFAULT_ACCESS_TOKEN_COOKIE = "result_access_token";
4
4
  declare const DEFAULT_REFRESH_TOKEN_COOKIE = "result_refresh_token";
@@ -1,2 +1,2 @@
1
- export { b as AuthCookieNames, c as AuthCookieOptions, A as AuthCookieSettings, d as CookieOptions, e as CookieReader, C as CookieStore, a as CookieWriter, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, s as setAuthCookies, u as updateSession } from '../middleware-VCWoai4o.cjs';
2
- import '../types-BmU3deRO.cjs';
1
+ export { b as AuthCookieNames, c as AuthCookieOptions, A as AuthCookieSettings, d as CookieOptions, e as CookieReader, C as CookieStore, a as CookieWriter, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, s as setAuthCookies, u as updateSession } from '../middleware-CGn44ocX.cjs';
2
+ import '../types-BTdjI7Yj.cjs';
@@ -1,2 +1,2 @@
1
- export { b as AuthCookieNames, c as AuthCookieOptions, A as AuthCookieSettings, d as CookieOptions, e as CookieReader, C as CookieStore, a as CookieWriter, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, s as setAuthCookies, u as updateSession } from '../middleware-Uh09_lFn.js';
2
- import '../types-BmU3deRO.js';
1
+ export { b as AuthCookieNames, c as AuthCookieOptions, A as AuthCookieSettings, d as CookieOptions, e as CookieReader, C as CookieStore, a as CookieWriter, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, s as setAuthCookies, u as updateSession } from '../middleware-8UNqItQG.js';
2
+ import '../types-BTdjI7Yj.js';
package/dist/ssr.cjs CHANGED
@@ -789,6 +789,7 @@ var HttpClient = class {
789
789
  }
790
790
  let controller;
791
791
  let timer;
792
+ let removeCallerAbortListener;
792
793
  if (this.timeout > 0 || callerSignal) {
793
794
  controller = new AbortController();
794
795
  if (this.timeout > 0) {
@@ -802,16 +803,11 @@ var HttpClient = class {
802
803
  callerSignal.addEventListener("abort", onCallerAbort, {
803
804
  once: true
804
805
  });
805
- controller.signal.addEventListener(
806
- "abort",
807
- () => {
808
- callerSignal.removeEventListener("abort", onCallerAbort);
809
- },
810
- { once: true }
811
- );
806
+ removeCallerAbortListener = () => callerSignal.removeEventListener("abort", onCallerAbort);
812
807
  }
813
808
  }
814
809
  }
810
+ let detachOnExit = true;
815
811
  try {
816
812
  const response = await this.fetch(url, {
817
813
  method,
@@ -832,14 +828,9 @@ var HttpClient = class {
832
828
  );
833
829
  continue;
834
830
  }
835
- if (timer !== void 0) {
836
- clearTimeout(timer);
837
- }
838
- return response;
831
+ detachOnExit = false;
832
+ return { response, releaseCallerSignal: removeCallerAbortListener };
839
833
  } catch (err) {
840
- if (timer !== void 0) {
841
- clearTimeout(timer);
842
- }
843
834
  if (err?.name === "AbortError") {
844
835
  if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
845
836
  throw new ResultError(
@@ -859,6 +850,13 @@ var HttpClient = class {
859
850
  0,
860
851
  "NETWORK_ERROR"
861
852
  );
853
+ } finally {
854
+ if (timer !== void 0) {
855
+ clearTimeout(timer);
856
+ }
857
+ if (detachOnExit) {
858
+ removeCallerAbortListener?.();
859
+ }
862
860
  }
863
861
  }
864
862
  throw lastError || new ResultError(
@@ -921,7 +919,7 @@ var HttpClient = class {
921
919
  });
922
920
  }
923
921
  this.logger.logRequest(method, url, requestHeaders, processedBody);
924
- const response = await this.fetchWithRetry({
922
+ const { response, releaseCallerSignal } = await this.fetchWithRetry({
925
923
  method,
926
924
  url,
927
925
  headers: requestHeaders,
@@ -944,6 +942,8 @@ var HttpClient = class {
944
942
  );
945
943
  }
946
944
  throw err;
945
+ } finally {
946
+ releaseCallerSignal?.();
947
947
  }
948
948
  this.logger.logResponse(
949
949
  method,
@@ -1045,7 +1045,7 @@ var HttpClient = class {
1045
1045
  const callerSignal = initSignal ?? request?.signal;
1046
1046
  const maxAttempts = IDEMPOTENT_METHODS.has(method.toUpperCase()) ? this.retryCount : 0;
1047
1047
  this.logger.logRequest(method, url, requestHeaders, body);
1048
- const response = await this.fetchWithRetry({
1048
+ const { response } = await this.fetchWithRetry({
1049
1049
  method,
1050
1050
  url,
1051
1051
  headers: requestHeaders,
@@ -1146,6 +1146,10 @@ var HttpClient = class {
1146
1146
  }
1147
1147
  return headers;
1148
1148
  }
1149
+ /** Returns the publishable project key used to identify SDK usage reports. */
1150
+ getAnonKey() {
1151
+ return this.anonKey;
1152
+ }
1149
1153
  async refreshAccessToken() {
1150
1154
  if (this.isRefreshing) {
1151
1155
  return this.refreshPromise;
@@ -1215,6 +1219,47 @@ var HttpClient = class {
1215
1219
  }
1216
1220
  };
1217
1221
 
1222
+ // src/modules/ai-usage.ts
1223
+ var USAGE_ENDPOINT = "https://beta.result.dev/api/usage/ai";
1224
+ async function reportAiUsage(http, requestId, model, kind, tokens) {
1225
+ const anonKey = http.getAnonKey();
1226
+ if (!anonKey || !Number.isSafeInteger(tokens) || tokens <= 0) {
1227
+ return;
1228
+ }
1229
+ const controller = new AbortController();
1230
+ const timeout = setTimeout(() => controller.abort(), 2e3);
1231
+ try {
1232
+ const request = {
1233
+ method: "POST",
1234
+ headers: { "content-type": "application/json" },
1235
+ body: JSON.stringify({
1236
+ requestId,
1237
+ backendUrl: http.baseUrl,
1238
+ anonKey,
1239
+ model,
1240
+ kind,
1241
+ tokens
1242
+ }),
1243
+ keepalive: true,
1244
+ signal: controller.signal
1245
+ };
1246
+ for (let attempt = 0; attempt < 2; attempt++) {
1247
+ try {
1248
+ const response = await http.fetch(USAGE_ENDPOINT, request);
1249
+ if (response.status < 500) return;
1250
+ } catch {
1251
+ if (controller.signal.aborted) return;
1252
+ }
1253
+ }
1254
+ } catch {
1255
+ } finally {
1256
+ clearTimeout(timeout);
1257
+ }
1258
+ }
1259
+ function aiUsageRequestId() {
1260
+ return globalThis.crypto?.randomUUID?.() ?? null;
1261
+ }
1262
+
1218
1263
  // src/modules/ai.ts
1219
1264
  var SUPPORTED_AUDIO_FORMATS = [
1220
1265
  "wav",
@@ -1302,6 +1347,7 @@ var ChatCompletions = class {
1302
1347
  }
1303
1348
  http;
1304
1349
  async create(params) {
1350
+ const usageRequestId = aiUsageRequestId();
1305
1351
  const contentSummary = summarizeContent(params.messages ?? []);
1306
1352
  assertSupportedContent(params.model, contentSummary);
1307
1353
  const backendParams = {
@@ -1346,7 +1392,7 @@ var ChatCompletions = class {
1346
1392
  contentSummary
1347
1393
  );
1348
1394
  }
1349
- return this.parseSSEStream(response2, params.model);
1395
+ return this.parseSSEStream(response2, params.model, usageRequestId);
1350
1396
  }
1351
1397
  let response;
1352
1398
  try {
@@ -1355,6 +1401,16 @@ var ChatCompletions = class {
1355
1401
  throw enrichUpstreamError(error, contentSummary);
1356
1402
  }
1357
1403
  const content = response.text || "";
1404
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1405
+ if (usageRequestId) {
1406
+ await reportAiUsage(
1407
+ this.http,
1408
+ usageRequestId,
1409
+ params.model,
1410
+ "chat",
1411
+ totalTokens
1412
+ );
1413
+ }
1358
1414
  return {
1359
1415
  id: `chatcmpl-${Date.now()}`,
1360
1416
  object: "chat.completion",
@@ -1381,14 +1437,14 @@ var ChatCompletions = class {
1381
1437
  usage: {
1382
1438
  prompt_tokens: response.metadata?.usage?.promptTokens || 0,
1383
1439
  completion_tokens: response.metadata?.usage?.completionTokens || 0,
1384
- total_tokens: response.metadata?.usage?.totalTokens || 0
1440
+ total_tokens: totalTokens
1385
1441
  }
1386
1442
  };
1387
1443
  }
1388
1444
  /**
1389
1445
  * Parse SSE stream into async iterable of OpenAI-like chunks
1390
1446
  */
1391
- async *parseSSEStream(response, model) {
1447
+ async *parseSSEStream(response, model, usageRequestId) {
1392
1448
  const reader = response.body?.getReader();
1393
1449
  if (!reader) {
1394
1450
  throw new ResultError(
@@ -1466,6 +1522,15 @@ var ChatCompletions = class {
1466
1522
  };
1467
1523
  }
1468
1524
  if (data.tokenUsage) {
1525
+ if (usageRequestId) {
1526
+ await reportAiUsage(
1527
+ this.http,
1528
+ usageRequestId,
1529
+ model,
1530
+ "chat",
1531
+ data.tokenUsage.totalTokens || 0
1532
+ );
1533
+ }
1469
1534
  yield {
1470
1535
  id: `chatcmpl-${Date.now()}`,
1471
1536
  object: "chat.completion.chunk",
@@ -1537,17 +1602,28 @@ var Embeddings = class {
1537
1602
  * ```
1538
1603
  */
1539
1604
  async create(params) {
1605
+ const usageRequestId = aiUsageRequestId();
1540
1606
  const response = await this.http.post(
1541
1607
  "/api/ai/embeddings",
1542
1608
  params
1543
1609
  );
1610
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1611
+ if (usageRequestId) {
1612
+ await reportAiUsage(
1613
+ this.http,
1614
+ usageRequestId,
1615
+ params.model,
1616
+ "embeddings",
1617
+ totalTokens
1618
+ );
1619
+ }
1544
1620
  return {
1545
1621
  object: response.object,
1546
1622
  data: response.data,
1547
1623
  model: response.metadata?.model,
1548
1624
  usage: response.metadata?.usage ? {
1549
1625
  prompt_tokens: response.metadata.usage.promptTokens || 0,
1550
- total_tokens: response.metadata.usage.totalTokens || 0
1626
+ total_tokens: totalTokens
1551
1627
  } : {
1552
1628
  prompt_tokens: 0,
1553
1629
  total_tokens: 0
@@ -1585,6 +1661,7 @@ var Images = class {
1585
1661
  * ```
1586
1662
  */
1587
1663
  async generate(params) {
1664
+ const usageRequestId = aiUsageRequestId();
1588
1665
  const response = await this.http.post(
1589
1666
  "/api/ai/image/generation",
1590
1667
  params
@@ -1598,6 +1675,16 @@ var Images = class {
1598
1675
  } else if (response.text) {
1599
1676
  data = [{ content: response.text }];
1600
1677
  }
1678
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1679
+ if (usageRequestId) {
1680
+ await reportAiUsage(
1681
+ this.http,
1682
+ usageRequestId,
1683
+ params.model,
1684
+ "image",
1685
+ totalTokens
1686
+ );
1687
+ }
1601
1688
  return {
1602
1689
  created: Math.floor(Date.now() / 1e3),
1603
1690
  data,
package/dist/ssr.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { R as ResultClient } from './client-BrOD2EO5.cjs';
2
- import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-BmU3deRO.cjs';
3
- import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-VCWoai4o.cjs';
4
- export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-VCWoai4o.cjs';
1
+ import { R as ResultClient } from './client-N6MW9IE1.cjs';
2
+ import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-BTdjI7Yj.cjs';
3
+ import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-CGn44ocX.cjs';
4
+ export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-CGn44ocX.cjs';
5
5
  import '@supabase/postgrest-js';
6
6
 
7
7
  interface CreateAuthActionsOptions extends Omit<ResultConfig, "accessToken" | "edgeFunctionToken" | "isServerMode" | "auth">, AuthCookieSettings {
package/dist/ssr.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { R as ResultClient } from './client--Xjaaw1l.js';
2
- import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-BmU3deRO.js';
3
- import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-Uh09_lFn.js';
4
- export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-Uh09_lFn.js';
1
+ import { R as ResultClient } from './client-D5IGLPW4.js';
2
+ import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-BTdjI7Yj.js';
3
+ import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-8UNqItQG.js';
4
+ export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-8UNqItQG.js';
5
5
  import '@supabase/postgrest-js';
6
6
 
7
7
  interface CreateAuthActionsOptions extends Omit<ResultConfig, "accessToken" | "edgeFunctionToken" | "isServerMode" | "auth">, AuthCookieSettings {
package/dist/ssr.js CHANGED
@@ -740,6 +740,7 @@ var HttpClient = class {
740
740
  }
741
741
  let controller;
742
742
  let timer;
743
+ let removeCallerAbortListener;
743
744
  if (this.timeout > 0 || callerSignal) {
744
745
  controller = new AbortController();
745
746
  if (this.timeout > 0) {
@@ -753,16 +754,11 @@ var HttpClient = class {
753
754
  callerSignal.addEventListener("abort", onCallerAbort, {
754
755
  once: true
755
756
  });
756
- controller.signal.addEventListener(
757
- "abort",
758
- () => {
759
- callerSignal.removeEventListener("abort", onCallerAbort);
760
- },
761
- { once: true }
762
- );
757
+ removeCallerAbortListener = () => callerSignal.removeEventListener("abort", onCallerAbort);
763
758
  }
764
759
  }
765
760
  }
761
+ let detachOnExit = true;
766
762
  try {
767
763
  const response = await this.fetch(url, {
768
764
  method,
@@ -783,14 +779,9 @@ var HttpClient = class {
783
779
  );
784
780
  continue;
785
781
  }
786
- if (timer !== void 0) {
787
- clearTimeout(timer);
788
- }
789
- return response;
782
+ detachOnExit = false;
783
+ return { response, releaseCallerSignal: removeCallerAbortListener };
790
784
  } catch (err) {
791
- if (timer !== void 0) {
792
- clearTimeout(timer);
793
- }
794
785
  if (err?.name === "AbortError") {
795
786
  if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
796
787
  throw new ResultError(
@@ -810,6 +801,13 @@ var HttpClient = class {
810
801
  0,
811
802
  "NETWORK_ERROR"
812
803
  );
804
+ } finally {
805
+ if (timer !== void 0) {
806
+ clearTimeout(timer);
807
+ }
808
+ if (detachOnExit) {
809
+ removeCallerAbortListener?.();
810
+ }
813
811
  }
814
812
  }
815
813
  throw lastError || new ResultError(
@@ -872,7 +870,7 @@ var HttpClient = class {
872
870
  });
873
871
  }
874
872
  this.logger.logRequest(method, url, requestHeaders, processedBody);
875
- const response = await this.fetchWithRetry({
873
+ const { response, releaseCallerSignal } = await this.fetchWithRetry({
876
874
  method,
877
875
  url,
878
876
  headers: requestHeaders,
@@ -895,6 +893,8 @@ var HttpClient = class {
895
893
  );
896
894
  }
897
895
  throw err;
896
+ } finally {
897
+ releaseCallerSignal?.();
898
898
  }
899
899
  this.logger.logResponse(
900
900
  method,
@@ -996,7 +996,7 @@ var HttpClient = class {
996
996
  const callerSignal = initSignal ?? request?.signal;
997
997
  const maxAttempts = IDEMPOTENT_METHODS.has(method.toUpperCase()) ? this.retryCount : 0;
998
998
  this.logger.logRequest(method, url, requestHeaders, body);
999
- const response = await this.fetchWithRetry({
999
+ const { response } = await this.fetchWithRetry({
1000
1000
  method,
1001
1001
  url,
1002
1002
  headers: requestHeaders,
@@ -1097,6 +1097,10 @@ var HttpClient = class {
1097
1097
  }
1098
1098
  return headers;
1099
1099
  }
1100
+ /** Returns the publishable project key used to identify SDK usage reports. */
1101
+ getAnonKey() {
1102
+ return this.anonKey;
1103
+ }
1100
1104
  async refreshAccessToken() {
1101
1105
  if (this.isRefreshing) {
1102
1106
  return this.refreshPromise;
@@ -1166,6 +1170,47 @@ var HttpClient = class {
1166
1170
  }
1167
1171
  };
1168
1172
 
1173
+ // src/modules/ai-usage.ts
1174
+ var USAGE_ENDPOINT = "https://beta.result.dev/api/usage/ai";
1175
+ async function reportAiUsage(http, requestId, model, kind, tokens) {
1176
+ const anonKey = http.getAnonKey();
1177
+ if (!anonKey || !Number.isSafeInteger(tokens) || tokens <= 0) {
1178
+ return;
1179
+ }
1180
+ const controller = new AbortController();
1181
+ const timeout = setTimeout(() => controller.abort(), 2e3);
1182
+ try {
1183
+ const request = {
1184
+ method: "POST",
1185
+ headers: { "content-type": "application/json" },
1186
+ body: JSON.stringify({
1187
+ requestId,
1188
+ backendUrl: http.baseUrl,
1189
+ anonKey,
1190
+ model,
1191
+ kind,
1192
+ tokens
1193
+ }),
1194
+ keepalive: true,
1195
+ signal: controller.signal
1196
+ };
1197
+ for (let attempt = 0; attempt < 2; attempt++) {
1198
+ try {
1199
+ const response = await http.fetch(USAGE_ENDPOINT, request);
1200
+ if (response.status < 500) return;
1201
+ } catch {
1202
+ if (controller.signal.aborted) return;
1203
+ }
1204
+ }
1205
+ } catch {
1206
+ } finally {
1207
+ clearTimeout(timeout);
1208
+ }
1209
+ }
1210
+ function aiUsageRequestId() {
1211
+ return globalThis.crypto?.randomUUID?.() ?? null;
1212
+ }
1213
+
1169
1214
  // src/modules/ai.ts
1170
1215
  var SUPPORTED_AUDIO_FORMATS = [
1171
1216
  "wav",
@@ -1253,6 +1298,7 @@ var ChatCompletions = class {
1253
1298
  }
1254
1299
  http;
1255
1300
  async create(params) {
1301
+ const usageRequestId = aiUsageRequestId();
1256
1302
  const contentSummary = summarizeContent(params.messages ?? []);
1257
1303
  assertSupportedContent(params.model, contentSummary);
1258
1304
  const backendParams = {
@@ -1297,7 +1343,7 @@ var ChatCompletions = class {
1297
1343
  contentSummary
1298
1344
  );
1299
1345
  }
1300
- return this.parseSSEStream(response2, params.model);
1346
+ return this.parseSSEStream(response2, params.model, usageRequestId);
1301
1347
  }
1302
1348
  let response;
1303
1349
  try {
@@ -1306,6 +1352,16 @@ var ChatCompletions = class {
1306
1352
  throw enrichUpstreamError(error, contentSummary);
1307
1353
  }
1308
1354
  const content = response.text || "";
1355
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1356
+ if (usageRequestId) {
1357
+ await reportAiUsage(
1358
+ this.http,
1359
+ usageRequestId,
1360
+ params.model,
1361
+ "chat",
1362
+ totalTokens
1363
+ );
1364
+ }
1309
1365
  return {
1310
1366
  id: `chatcmpl-${Date.now()}`,
1311
1367
  object: "chat.completion",
@@ -1332,14 +1388,14 @@ var ChatCompletions = class {
1332
1388
  usage: {
1333
1389
  prompt_tokens: response.metadata?.usage?.promptTokens || 0,
1334
1390
  completion_tokens: response.metadata?.usage?.completionTokens || 0,
1335
- total_tokens: response.metadata?.usage?.totalTokens || 0
1391
+ total_tokens: totalTokens
1336
1392
  }
1337
1393
  };
1338
1394
  }
1339
1395
  /**
1340
1396
  * Parse SSE stream into async iterable of OpenAI-like chunks
1341
1397
  */
1342
- async *parseSSEStream(response, model) {
1398
+ async *parseSSEStream(response, model, usageRequestId) {
1343
1399
  const reader = response.body?.getReader();
1344
1400
  if (!reader) {
1345
1401
  throw new ResultError(
@@ -1417,6 +1473,15 @@ var ChatCompletions = class {
1417
1473
  };
1418
1474
  }
1419
1475
  if (data.tokenUsage) {
1476
+ if (usageRequestId) {
1477
+ await reportAiUsage(
1478
+ this.http,
1479
+ usageRequestId,
1480
+ model,
1481
+ "chat",
1482
+ data.tokenUsage.totalTokens || 0
1483
+ );
1484
+ }
1420
1485
  yield {
1421
1486
  id: `chatcmpl-${Date.now()}`,
1422
1487
  object: "chat.completion.chunk",
@@ -1488,17 +1553,28 @@ var Embeddings = class {
1488
1553
  * ```
1489
1554
  */
1490
1555
  async create(params) {
1556
+ const usageRequestId = aiUsageRequestId();
1491
1557
  const response = await this.http.post(
1492
1558
  "/api/ai/embeddings",
1493
1559
  params
1494
1560
  );
1561
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1562
+ if (usageRequestId) {
1563
+ await reportAiUsage(
1564
+ this.http,
1565
+ usageRequestId,
1566
+ params.model,
1567
+ "embeddings",
1568
+ totalTokens
1569
+ );
1570
+ }
1495
1571
  return {
1496
1572
  object: response.object,
1497
1573
  data: response.data,
1498
1574
  model: response.metadata?.model,
1499
1575
  usage: response.metadata?.usage ? {
1500
1576
  prompt_tokens: response.metadata.usage.promptTokens || 0,
1501
- total_tokens: response.metadata.usage.totalTokens || 0
1577
+ total_tokens: totalTokens
1502
1578
  } : {
1503
1579
  prompt_tokens: 0,
1504
1580
  total_tokens: 0
@@ -1536,6 +1612,7 @@ var Images = class {
1536
1612
  * ```
1537
1613
  */
1538
1614
  async generate(params) {
1615
+ const usageRequestId = aiUsageRequestId();
1539
1616
  const response = await this.http.post(
1540
1617
  "/api/ai/image/generation",
1541
1618
  params
@@ -1549,6 +1626,16 @@ var Images = class {
1549
1626
  } else if (response.text) {
1550
1627
  data = [{ content: response.text }];
1551
1628
  }
1629
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1630
+ if (usageRequestId) {
1631
+ await reportAiUsage(
1632
+ this.http,
1633
+ usageRequestId,
1634
+ params.model,
1635
+ "image",
1636
+ totalTokens
1637
+ );
1638
+ }
1552
1639
  return {
1553
1640
  created: Math.floor(Date.now() / 1e3),
1554
1641
  data,
@@ -528,7 +528,8 @@ interface ResultConfig {
528
528
  /**
529
529
  * The base URL of the Result backend API (the NEXT_PUBLIC_BACKEND_URL
530
530
  * value from .env.local). Required at runtime: constructing a client
531
- * without it throws MISSING_BASE_URL.
531
+ * without it throws MISSING_BASE_URL. (Kept optional in the type because
532
+ * the SSR entrypoints intentionally fill it from process.env.)
532
533
  */
533
534
  baseUrl?: string;
534
535
  /**
@@ -528,7 +528,8 @@ interface ResultConfig {
528
528
  /**
529
529
  * The base URL of the Result backend API (the NEXT_PUBLIC_BACKEND_URL
530
530
  * value from .env.local). Required at runtime: constructing a client
531
- * without it throws MISSING_BASE_URL.
531
+ * without it throws MISSING_BASE_URL. (Kept optional in the type because
532
+ * the SSR entrypoints intentionally fill it from process.env.)
532
533
  */
533
534
  baseUrl?: string;
534
535
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resultdev/sdk",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Result Backend SDK - authentication, database, storage, functions, realtime, AI, email, web analytics and customer support for apps built on Result.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://docs.result.dev",
@@ -59,8 +59,19 @@
59
59
  }
60
60
  }
61
61
  },
62
+ "scripts": {
63
+ "build": "tsup",
64
+ "test": "vitest run",
65
+ "check-types": "tsc --noEmit -p tsconfig.json"
66
+ },
62
67
  "dependencies": {
63
68
  "@supabase/postgrest-js": "^1.21.3",
64
69
  "socket.io-client": "^4.8.1"
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "^24.0.0",
73
+ "tsup": "^8.5.0",
74
+ "typescript": "^5.9.2",
75
+ "vitest": "^3.2.4"
65
76
  }
66
- }
77
+ }