@resultdev/sdk 0.3.0 → 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,25 @@
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
+
12
+ ## 0.3.1
13
+
14
+ - A missing `baseUrl` now throws `MISSING_BASE_URL` at client construction
15
+ instead of silently falling back to a localhost dev URL. The fallback made
16
+ deployed sites whose `NEXT_PUBLIC_BACKEND_URL` was undefined in the
17
+ browser bundle (for example when the env var is read through an imported
18
+ `process` module, which defeats build-time inlining) call `localhost` from
19
+ production pages, which surfaced as Chrome's "access other apps and
20
+ services on this device" permission prompt. The error's fix hint names the
21
+ env var and the inlining rule.
22
+
3
23
  ## 0.3.0
4
24
 
5
25
  Reliability release: every fix here comes from live verification against a
@@ -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-DSVtGaIA.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.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>;
@@ -996,7 +998,7 @@ type AccessTokenChangeEvent = typeof AuthChangeEvent.SIGNED_IN | typeof AuthChan
996
998
  * import { ResultClient } from '@resultdev/sdk';
997
999
  *
998
1000
  * const client = new ResultClient({
999
- * baseUrl: 'http://localhost:7130'
1001
+ * baseUrl: process.env.NEXT_PUBLIC_BACKEND_URL!
1000
1002
  * });
1001
1003
  *
1002
1004
  * // Authentication
@@ -1027,7 +1029,7 @@ type AccessTokenChangeEvent = typeof AuthChangeEvent.SIGNED_IN | typeof AuthChan
1027
1029
  *
1028
1030
  * // Enable debug logging
1029
1031
  * const debugClient = new ResultClient({
1030
- * baseUrl: 'http://localhost:7130',
1032
+ * baseUrl: process.env.NEXT_PUBLIC_BACKEND_URL!,
1031
1033
  * debug: true
1032
1034
  * });
1033
1035
  * ```
@@ -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-DSVtGaIA.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.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>;
@@ -996,7 +998,7 @@ type AccessTokenChangeEvent = typeof AuthChangeEvent.SIGNED_IN | typeof AuthChan
996
998
  * import { ResultClient } from '@resultdev/sdk';
997
999
  *
998
1000
  * const client = new ResultClient({
999
- * baseUrl: 'http://localhost:7130'
1001
+ * baseUrl: process.env.NEXT_PUBLIC_BACKEND_URL!
1000
1002
  * });
1001
1003
  *
1002
1004
  * // Authentication
@@ -1027,7 +1029,7 @@ type AccessTokenChangeEvent = typeof AuthChangeEvent.SIGNED_IN | typeof AuthChan
1027
1029
  *
1028
1030
  * // Enable debug logging
1029
1031
  * const debugClient = new ResultClient({
1030
- * baseUrl: 'http://localhost:7130',
1032
+ * baseUrl: process.env.NEXT_PUBLIC_BACKEND_URL!,
1031
1033
  * debug: true
1032
1034
  * });
1033
1035
  * ```
package/dist/index.cjs CHANGED
@@ -503,7 +503,15 @@ var HttpClient = class {
503
503
  */
504
504
  constructor(config, tokenManager, logger) {
505
505
  this.config = config;
506
- this.baseUrl = config.baseUrl || "http://localhost:7130";
506
+ if (!config.baseUrl) {
507
+ throw new ResultError(
508
+ "createClient needs a baseUrl, but it was undefined at runtime.",
509
+ 0,
510
+ "MISSING_BASE_URL",
511
+ "Pass the NEXT_PUBLIC_BACKEND_URL value from .env.local. In browser code, write process.env.NEXT_PUBLIC_BACKEND_URL as a literal expression (never through an imported process module) so the bundler inlines it, and set it as a deployments build variable before deploying."
512
+ );
513
+ }
514
+ this.baseUrl = config.baseUrl;
507
515
  this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
508
516
  this.anonKey = config.anonKey;
509
517
  this.defaultHeaders = {
@@ -594,6 +602,7 @@ var HttpClient = class {
594
602
  }
595
603
  let controller;
596
604
  let timer;
605
+ let removeCallerAbortListener;
597
606
  if (this.timeout > 0 || callerSignal) {
598
607
  controller = new AbortController();
599
608
  if (this.timeout > 0) {
@@ -607,16 +616,11 @@ var HttpClient = class {
607
616
  callerSignal.addEventListener("abort", onCallerAbort, {
608
617
  once: true
609
618
  });
610
- controller.signal.addEventListener(
611
- "abort",
612
- () => {
613
- callerSignal.removeEventListener("abort", onCallerAbort);
614
- },
615
- { once: true }
616
- );
619
+ removeCallerAbortListener = () => callerSignal.removeEventListener("abort", onCallerAbort);
617
620
  }
618
621
  }
619
622
  }
623
+ let detachOnExit = true;
620
624
  try {
621
625
  const response = await this.fetch(url, {
622
626
  method,
@@ -637,14 +641,9 @@ var HttpClient = class {
637
641
  );
638
642
  continue;
639
643
  }
640
- if (timer !== void 0) {
641
- clearTimeout(timer);
642
- }
643
- return response;
644
+ detachOnExit = false;
645
+ return { response, releaseCallerSignal: removeCallerAbortListener };
644
646
  } catch (err) {
645
- if (timer !== void 0) {
646
- clearTimeout(timer);
647
- }
648
647
  if (err?.name === "AbortError") {
649
648
  if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
650
649
  throw new ResultError(
@@ -664,6 +663,13 @@ var HttpClient = class {
664
663
  0,
665
664
  "NETWORK_ERROR"
666
665
  );
666
+ } finally {
667
+ if (timer !== void 0) {
668
+ clearTimeout(timer);
669
+ }
670
+ if (detachOnExit) {
671
+ removeCallerAbortListener?.();
672
+ }
667
673
  }
668
674
  }
669
675
  throw lastError || new ResultError(
@@ -726,7 +732,7 @@ var HttpClient = class {
726
732
  });
727
733
  }
728
734
  this.logger.logRequest(method, url, requestHeaders, processedBody);
729
- const response = await this.fetchWithRetry({
735
+ const { response, releaseCallerSignal } = await this.fetchWithRetry({
730
736
  method,
731
737
  url,
732
738
  headers: requestHeaders,
@@ -749,6 +755,8 @@ var HttpClient = class {
749
755
  );
750
756
  }
751
757
  throw err;
758
+ } finally {
759
+ releaseCallerSignal?.();
752
760
  }
753
761
  this.logger.logResponse(
754
762
  method,
@@ -850,7 +858,7 @@ var HttpClient = class {
850
858
  const callerSignal = initSignal ?? request?.signal;
851
859
  const maxAttempts = IDEMPOTENT_METHODS.has(method.toUpperCase()) ? this.retryCount : 0;
852
860
  this.logger.logRequest(method, url, requestHeaders, body);
853
- const response = await this.fetchWithRetry({
861
+ const { response } = await this.fetchWithRetry({
854
862
  method,
855
863
  url,
856
864
  headers: requestHeaders,
@@ -951,6 +959,10 @@ var HttpClient = class {
951
959
  }
952
960
  return headers;
953
961
  }
962
+ /** Returns the publishable project key used to identify SDK usage reports. */
963
+ getAnonKey() {
964
+ return this.anonKey;
965
+ }
954
966
  async refreshAccessToken() {
955
967
  if (this.isRefreshing) {
956
968
  return this.refreshPromise;
@@ -1020,6 +1032,47 @@ var HttpClient = class {
1020
1032
  }
1021
1033
  };
1022
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
+
1023
1076
  // src/modules/ai.ts
1024
1077
  var SUPPORTED_AUDIO_FORMATS = [
1025
1078
  "wav",
@@ -1107,6 +1160,7 @@ var ChatCompletions = class {
1107
1160
  }
1108
1161
  http;
1109
1162
  async create(params) {
1163
+ const usageRequestId = aiUsageRequestId();
1110
1164
  const contentSummary = summarizeContent(params.messages ?? []);
1111
1165
  assertSupportedContent(params.model, contentSummary);
1112
1166
  const backendParams = {
@@ -1151,7 +1205,7 @@ var ChatCompletions = class {
1151
1205
  contentSummary
1152
1206
  );
1153
1207
  }
1154
- return this.parseSSEStream(response2, params.model);
1208
+ return this.parseSSEStream(response2, params.model, usageRequestId);
1155
1209
  }
1156
1210
  let response;
1157
1211
  try {
@@ -1160,6 +1214,16 @@ var ChatCompletions = class {
1160
1214
  throw enrichUpstreamError(error, contentSummary);
1161
1215
  }
1162
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
+ }
1163
1227
  return {
1164
1228
  id: `chatcmpl-${Date.now()}`,
1165
1229
  object: "chat.completion",
@@ -1186,14 +1250,14 @@ var ChatCompletions = class {
1186
1250
  usage: {
1187
1251
  prompt_tokens: response.metadata?.usage?.promptTokens || 0,
1188
1252
  completion_tokens: response.metadata?.usage?.completionTokens || 0,
1189
- total_tokens: response.metadata?.usage?.totalTokens || 0
1253
+ total_tokens: totalTokens
1190
1254
  }
1191
1255
  };
1192
1256
  }
1193
1257
  /**
1194
1258
  * Parse SSE stream into async iterable of OpenAI-like chunks
1195
1259
  */
1196
- async *parseSSEStream(response, model) {
1260
+ async *parseSSEStream(response, model, usageRequestId) {
1197
1261
  const reader = response.body?.getReader();
1198
1262
  if (!reader) {
1199
1263
  throw new ResultError(
@@ -1271,6 +1335,15 @@ var ChatCompletions = class {
1271
1335
  };
1272
1336
  }
1273
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
+ }
1274
1347
  yield {
1275
1348
  id: `chatcmpl-${Date.now()}`,
1276
1349
  object: "chat.completion.chunk",
@@ -1342,17 +1415,28 @@ var Embeddings = class {
1342
1415
  * ```
1343
1416
  */
1344
1417
  async create(params) {
1418
+ const usageRequestId = aiUsageRequestId();
1345
1419
  const response = await this.http.post(
1346
1420
  "/api/ai/embeddings",
1347
1421
  params
1348
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
+ }
1349
1433
  return {
1350
1434
  object: response.object,
1351
1435
  data: response.data,
1352
1436
  model: response.metadata?.model,
1353
1437
  usage: response.metadata?.usage ? {
1354
1438
  prompt_tokens: response.metadata.usage.promptTokens || 0,
1355
- total_tokens: response.metadata.usage.totalTokens || 0
1439
+ total_tokens: totalTokens
1356
1440
  } : {
1357
1441
  prompt_tokens: 0,
1358
1442
  total_tokens: 0
@@ -1390,6 +1474,7 @@ var Images = class {
1390
1474
  * ```
1391
1475
  */
1392
1476
  async generate(params) {
1477
+ const usageRequestId = aiUsageRequestId();
1393
1478
  const response = await this.http.post(
1394
1479
  "/api/ai/image/generation",
1395
1480
  params
@@ -1403,6 +1488,16 @@ var Images = class {
1403
1488
  } else if (response.text) {
1404
1489
  data = [{ content: response.text }];
1405
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
+ }
1406
1501
  return {
1407
1502
  created: Math.floor(Date.now() / 1e3),
1408
1503
  data,
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { R as ResultClient } from './client-CJv_awPt.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-CJv_awPt.cjs';
3
- import { R as ResultAdminConfig, a as ResultConfig } from './types-DSVtGaIA.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-DSVtGaIA.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-DrsZ1qIm.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-DrsZ1qIm.js';
3
- import { R as ResultAdminConfig, a as ResultConfig } from './types-DSVtGaIA.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-DSVtGaIA.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
@@ -448,7 +448,15 @@ var HttpClient = class {
448
448
  */
449
449
  constructor(config, tokenManager, logger) {
450
450
  this.config = config;
451
- this.baseUrl = config.baseUrl || "http://localhost:7130";
451
+ if (!config.baseUrl) {
452
+ throw new ResultError(
453
+ "createClient needs a baseUrl, but it was undefined at runtime.",
454
+ 0,
455
+ "MISSING_BASE_URL",
456
+ "Pass the NEXT_PUBLIC_BACKEND_URL value from .env.local. In browser code, write process.env.NEXT_PUBLIC_BACKEND_URL as a literal expression (never through an imported process module) so the bundler inlines it, and set it as a deployments build variable before deploying."
457
+ );
458
+ }
459
+ this.baseUrl = config.baseUrl;
452
460
  this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
453
461
  this.anonKey = config.anonKey;
454
462
  this.defaultHeaders = {
@@ -539,6 +547,7 @@ var HttpClient = class {
539
547
  }
540
548
  let controller;
541
549
  let timer;
550
+ let removeCallerAbortListener;
542
551
  if (this.timeout > 0 || callerSignal) {
543
552
  controller = new AbortController();
544
553
  if (this.timeout > 0) {
@@ -552,16 +561,11 @@ var HttpClient = class {
552
561
  callerSignal.addEventListener("abort", onCallerAbort, {
553
562
  once: true
554
563
  });
555
- controller.signal.addEventListener(
556
- "abort",
557
- () => {
558
- callerSignal.removeEventListener("abort", onCallerAbort);
559
- },
560
- { once: true }
561
- );
564
+ removeCallerAbortListener = () => callerSignal.removeEventListener("abort", onCallerAbort);
562
565
  }
563
566
  }
564
567
  }
568
+ let detachOnExit = true;
565
569
  try {
566
570
  const response = await this.fetch(url, {
567
571
  method,
@@ -582,14 +586,9 @@ var HttpClient = class {
582
586
  );
583
587
  continue;
584
588
  }
585
- if (timer !== void 0) {
586
- clearTimeout(timer);
587
- }
588
- return response;
589
+ detachOnExit = false;
590
+ return { response, releaseCallerSignal: removeCallerAbortListener };
589
591
  } catch (err) {
590
- if (timer !== void 0) {
591
- clearTimeout(timer);
592
- }
593
592
  if (err?.name === "AbortError") {
594
593
  if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
595
594
  throw new ResultError(
@@ -609,6 +608,13 @@ var HttpClient = class {
609
608
  0,
610
609
  "NETWORK_ERROR"
611
610
  );
611
+ } finally {
612
+ if (timer !== void 0) {
613
+ clearTimeout(timer);
614
+ }
615
+ if (detachOnExit) {
616
+ removeCallerAbortListener?.();
617
+ }
612
618
  }
613
619
  }
614
620
  throw lastError || new ResultError(
@@ -671,7 +677,7 @@ var HttpClient = class {
671
677
  });
672
678
  }
673
679
  this.logger.logRequest(method, url, requestHeaders, processedBody);
674
- const response = await this.fetchWithRetry({
680
+ const { response, releaseCallerSignal } = await this.fetchWithRetry({
675
681
  method,
676
682
  url,
677
683
  headers: requestHeaders,
@@ -694,6 +700,8 @@ var HttpClient = class {
694
700
  );
695
701
  }
696
702
  throw err;
703
+ } finally {
704
+ releaseCallerSignal?.();
697
705
  }
698
706
  this.logger.logResponse(
699
707
  method,
@@ -795,7 +803,7 @@ var HttpClient = class {
795
803
  const callerSignal = initSignal ?? request?.signal;
796
804
  const maxAttempts = IDEMPOTENT_METHODS.has(method.toUpperCase()) ? this.retryCount : 0;
797
805
  this.logger.logRequest(method, url, requestHeaders, body);
798
- const response = await this.fetchWithRetry({
806
+ const { response } = await this.fetchWithRetry({
799
807
  method,
800
808
  url,
801
809
  headers: requestHeaders,
@@ -896,6 +904,10 @@ var HttpClient = class {
896
904
  }
897
905
  return headers;
898
906
  }
907
+ /** Returns the publishable project key used to identify SDK usage reports. */
908
+ getAnonKey() {
909
+ return this.anonKey;
910
+ }
899
911
  async refreshAccessToken() {
900
912
  if (this.isRefreshing) {
901
913
  return this.refreshPromise;
@@ -965,6 +977,47 @@ var HttpClient = class {
965
977
  }
966
978
  };
967
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
+
968
1021
  // src/modules/ai.ts
969
1022
  var SUPPORTED_AUDIO_FORMATS = [
970
1023
  "wav",
@@ -1052,6 +1105,7 @@ var ChatCompletions = class {
1052
1105
  }
1053
1106
  http;
1054
1107
  async create(params) {
1108
+ const usageRequestId = aiUsageRequestId();
1055
1109
  const contentSummary = summarizeContent(params.messages ?? []);
1056
1110
  assertSupportedContent(params.model, contentSummary);
1057
1111
  const backendParams = {
@@ -1096,7 +1150,7 @@ var ChatCompletions = class {
1096
1150
  contentSummary
1097
1151
  );
1098
1152
  }
1099
- return this.parseSSEStream(response2, params.model);
1153
+ return this.parseSSEStream(response2, params.model, usageRequestId);
1100
1154
  }
1101
1155
  let response;
1102
1156
  try {
@@ -1105,6 +1159,16 @@ var ChatCompletions = class {
1105
1159
  throw enrichUpstreamError(error, contentSummary);
1106
1160
  }
1107
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
+ }
1108
1172
  return {
1109
1173
  id: `chatcmpl-${Date.now()}`,
1110
1174
  object: "chat.completion",
@@ -1131,14 +1195,14 @@ var ChatCompletions = class {
1131
1195
  usage: {
1132
1196
  prompt_tokens: response.metadata?.usage?.promptTokens || 0,
1133
1197
  completion_tokens: response.metadata?.usage?.completionTokens || 0,
1134
- total_tokens: response.metadata?.usage?.totalTokens || 0
1198
+ total_tokens: totalTokens
1135
1199
  }
1136
1200
  };
1137
1201
  }
1138
1202
  /**
1139
1203
  * Parse SSE stream into async iterable of OpenAI-like chunks
1140
1204
  */
1141
- async *parseSSEStream(response, model) {
1205
+ async *parseSSEStream(response, model, usageRequestId) {
1142
1206
  const reader = response.body?.getReader();
1143
1207
  if (!reader) {
1144
1208
  throw new ResultError(
@@ -1216,6 +1280,15 @@ var ChatCompletions = class {
1216
1280
  };
1217
1281
  }
1218
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
+ }
1219
1292
  yield {
1220
1293
  id: `chatcmpl-${Date.now()}`,
1221
1294
  object: "chat.completion.chunk",
@@ -1287,17 +1360,28 @@ var Embeddings = class {
1287
1360
  * ```
1288
1361
  */
1289
1362
  async create(params) {
1363
+ const usageRequestId = aiUsageRequestId();
1290
1364
  const response = await this.http.post(
1291
1365
  "/api/ai/embeddings",
1292
1366
  params
1293
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
+ }
1294
1378
  return {
1295
1379
  object: response.object,
1296
1380
  data: response.data,
1297
1381
  model: response.metadata?.model,
1298
1382
  usage: response.metadata?.usage ? {
1299
1383
  prompt_tokens: response.metadata.usage.promptTokens || 0,
1300
- total_tokens: response.metadata.usage.totalTokens || 0
1384
+ total_tokens: totalTokens
1301
1385
  } : {
1302
1386
  prompt_tokens: 0,
1303
1387
  total_tokens: 0
@@ -1335,6 +1419,7 @@ var Images = class {
1335
1419
  * ```
1336
1420
  */
1337
1421
  async generate(params) {
1422
+ const usageRequestId = aiUsageRequestId();
1338
1423
  const response = await this.http.post(
1339
1424
  "/api/ai/image/generation",
1340
1425
  params
@@ -1348,6 +1433,16 @@ var Images = class {
1348
1433
  } else if (response.text) {
1349
1434
  data = [{ content: response.text }];
1350
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
+ }
1351
1446
  return {
1352
1447
  created: Math.floor(Date.now() / 1e3),
1353
1448
  data,