@superblocksteam/library 2.0.89 → 2.0.90

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/dist/lib/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { n as consoleLogAttributes, t as early_console_buffer_default } from "../early-console-buffer-D4wVuyBf.js";
2
- import { A as useSuperblocksProfiles, B as PropsCategory, C as addNewPromise, D as getAppMode, E as SuperblocksContextProvider, F as iframeMessageHandler, H as createPropertiesPanelDefinition, I as isEmbeddedBySuperblocksFirstParty, L as isEditMode, M as sendNotification, N as colors$1, O as useSuperblocksContext, P as editorBridge, R as createManagedPropsList, S as api_hmr_tracker_default, T as resolveById, U as getEditStore, V as Section, _ as root_store_default, a as FixWithClarkButton, b as createIframeSpan, c as ErrorContent, d as ErrorMessage$1, f as ErrorStack, g as StyledClarkIcon, h as SecondaryButton, i as getWidgetRectAnchorName, j as useSuperblocksUser, k as useSuperblocksGroups, l as ErrorDetails, m as ErrorTitle, n as useJSXContext, o as ActionsContainer, p as ErrorSummary, r as getWidgetAnchorName, s as ErrorContainer, u as ErrorIconContainer, v as startEditorSync, w as rejectById, x as getContextFromTraceHeaders, y as generateId, z as Prop } from "../jsx-wrapper-DL2O6Y1G.js";
2
+ import { A as useSuperblocksGroups, B as createManagedPropsList, C as addNewPromise, D as getAppMode, E as SuperblocksContextProvider, F as editorBridge, G as getEditStore, H as PropsCategory, I as iframeMessageHandler, L as isEmbeddedBySuperblocksFirstParty, M as useSuperblocksUser, N as sendNotification, O as useSuperblocksContext, P as colors$1, R as sendMessageImmediately, S as api_hmr_tracker_default, T as resolveById, U as Section, V as Prop, W as createPropertiesPanelDefinition, _ as root_store_default, a as FixWithClarkButton, b as createIframeSpan, c as ErrorContent, d as ErrorMessage$1, f as ErrorStack, g as StyledClarkIcon, h as SecondaryButton, i as getWidgetRectAnchorName, j as useSuperblocksProfiles, k as useSuperblocksDataTags, l as ErrorDetails, m as ErrorTitle, n as useJSXContext, o as ActionsContainer, p as ErrorSummary, r as getWidgetAnchorName, s as ErrorContainer, u as ErrorIconContainer, v as startEditorSync, w as rejectById, x as getContextFromTraceHeaders, y as generateId, z as isEditMode } from "../jsx-wrapper-C8LEtdzp.js";
3
3
  import { n as initTracerProviderWithOrigin } from "../utils-BGEEeYie.js";
4
4
  import { action, autorun, computed, makeAutoObservable, makeObservable, observable, reaction, when } from "mobx";
5
5
  import { Dim, NATIVE_COMPONENT_TYPES, NO_SELECT_ATTRIBUTE, Property, Property as Property$1, SELECTOR_ID_ATTRIBUTE, SOURCE_ID_ATTRIBUTE, generateSourceId, getBindingIdentifier } from "@superblocksteam/library-shared";
6
6
  import { UNSAFE_DataRouterContext, generatePath, useLocation, useNavigate, useParams, useRouteError } from "react-router";
7
7
  import * as React$1 from "react";
8
- import React, { Suspense, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
8
+ import React, { Suspense, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
9
9
  import { isEqual } from "lodash";
10
10
  import { ISocketWithClientAuth, OBS_TAG_APPLICATION_ID, connectWebSocket, createISocketClient } from "@superblocksteam/shared";
11
11
  import { AiContextMode, AiGenerationState, ViteMessageKind } from "@superblocksteam/library-shared/types";
@@ -16,6 +16,8 @@ import defaultTheme from "tailwindcss/defaultTheme";
16
16
  import postcss from "postcss";
17
17
  import colors from "tailwindcss/colors";
18
18
  import { Observer, observer } from "mobx-react-lite";
19
+ import { execute, isStreamingApi } from "@superblocksteam/sdk-api";
20
+ import zodToJsonSchema from "zod-to-json-schema";
19
21
  import * as Dialog from "@radix-ui/react-dialog";
20
22
  import posthog from "posthog-js";
21
23
  import { Graph } from "@dagrejs/graphlib";
@@ -776,6 +778,102 @@ function registerHtmlElements() {
776
778
  registerComponent("meta", htmlTagSectionsTemplate({ hasChildren: false }));
777
779
  }
778
780
 
781
+ //#endregion
782
+ //#region src/lib/internal-details/lib/jwt-utils.ts
783
+ /**
784
+ * Known claim keys that are standard or Superblocks-specific.
785
+ * These are excluded from customClaims to avoid duplication.
786
+ */
787
+ const KNOWN_CLAIM_KEYS = new Set([
788
+ "sub",
789
+ "email",
790
+ "user_email",
791
+ "name",
792
+ "groups",
793
+ "iss",
794
+ "aud",
795
+ "exp",
796
+ "nbf",
797
+ "iat",
798
+ "jti",
799
+ "userId",
800
+ "user_id",
801
+ "organizationId",
802
+ "org_id"
803
+ ]);
804
+ /**
805
+ * Decodes a base64url-encoded string.
806
+ *
807
+ * JWT uses base64url encoding which replaces + with - and / with _.
808
+ * This function handles the conversion and decoding.
809
+ */
810
+ function base64UrlDecode(str) {
811
+ const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
812
+ const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
813
+ try {
814
+ const binaryStr = atob(padded);
815
+ const bytes = Uint8Array.from(binaryStr, (c) => c.charCodeAt(0));
816
+ return new TextDecoder().decode(bytes);
817
+ } catch (error) {
818
+ console.warn("[jwt-utils] Failed to decode base64url string -- JWT payload cannot be parsed:", error);
819
+ return "";
820
+ }
821
+ }
822
+ /**
823
+ * Parses a JWT token and extracts the payload claims.
824
+ *
825
+ * @param token - The JWT token string (with or without "Bearer " prefix)
826
+ * @returns The decoded claims object, or null if parsing fails
827
+ */
828
+ function parseJwtClaims(token) {
829
+ if (!token) return null;
830
+ const parts = (token.startsWith("Bearer ") ? token.slice(7) : token).split(".");
831
+ if (parts.length !== 3) return null;
832
+ try {
833
+ const payloadJson = base64UrlDecode(parts[1]);
834
+ return JSON.parse(payloadJson);
835
+ } catch (error) {
836
+ console.warn("[jwt-utils] Failed to parse JWT claims:", error);
837
+ return null;
838
+ }
839
+ }
840
+ /**
841
+ * Extracts SDK user information from JWT claims.
842
+ *
843
+ * Maps standard JWT claims to the SdkApiUser interface expected
844
+ * by the SDK's executeApi function.
845
+ *
846
+ * @param claims - Parsed JWT claims
847
+ * @returns SdkApiUser object with extracted user info
848
+ */
849
+ function extractSdkUserFromClaims(claims) {
850
+ const userId = claims.sub || claims.userId || claims.user_id || "anonymous";
851
+ const email = claims.email ?? claims.user_email;
852
+ const groups = Array.isArray(claims.groups) ? claims.groups : [];
853
+ const customClaims = {};
854
+ for (const [key, value] of Object.entries(claims)) if (!KNOWN_CLAIM_KEYS.has(key)) customClaims[key] = value;
855
+ return {
856
+ userId,
857
+ email,
858
+ name: claims.name,
859
+ groups,
860
+ customClaims: Object.freeze(customClaims)
861
+ };
862
+ }
863
+ /**
864
+ * Parses a JWT token and extracts SDK user information.
865
+ *
866
+ * Convenience function that combines parseJwtClaims and extractSdkUserFromClaims.
867
+ *
868
+ * @param token - The JWT token string
869
+ * @returns SdkApiUser object, or null if parsing fails
870
+ */
871
+ function extractSdkUserFromJwt(token) {
872
+ const claims = parseJwtClaims(token);
873
+ if (!claims) return null;
874
+ return extractSdkUserFromClaims(claims);
875
+ }
876
+
779
877
  //#endregion
780
878
  //#region src/lib/internal-details/handle-bootstrap-response.ts
781
879
  const handleBootstrapResponse = (event) => {
@@ -783,6 +881,14 @@ const handleBootstrapResponse = (event) => {
783
881
  root_store_default.userId = event.data.payload.userId;
784
882
  root_store_default.apis.agentUrls = event.data.payload.agentUrls;
785
883
  root_store_default.apis.agents = event.data.payload.agents;
884
+ const sdkApiEnabled = !!event.data.payload.featureFlags?.["clark.sdk-api.enabled"];
885
+ root_store_default.setSdkApiEnabled(sdkApiEnabled);
886
+ const sdkUser = extractSdkUserFromJwt(event.data.payload.token);
887
+ if (sdkUser) root_store_default.setSdkUser(sdkUser);
888
+ else if (event.data.payload.userId) {
889
+ console.log("[handle-bootstrap-response] Auth0 JWT not available for SDK user extraction. Custom claims (groups, email) will not be available. Falling back to userId from payload.");
890
+ root_store_default.setSdkUser({ userId: event.data.payload.userId });
891
+ } else console.log("[handle-bootstrap-response] No user info available for SDK API execution. Neither auth0 JWT nor userId was provided in the bootstrap payload.");
786
892
  root_store_default.apis.setTokens(event.data.payload.token, event.data.payload.accessToken);
787
893
  root_store_default.apis.notifyBootstrapComplete();
788
894
  const featureFlags = event.data.payload.featureFlags;
@@ -853,53 +959,294 @@ function useGetCurrentUserQuery() {
853
959
  }
854
960
 
855
961
  //#endregion
856
- //#region src/lib/internal-details/use-api.ts
857
- const getApiPath = (name) => {
858
- let apiPath = name;
859
- if (!apiPath.startsWith("/apis")) apiPath = `/apis/${name}/api.yaml`;
860
- return apiPath;
861
- };
962
+ //#region src/lib/internal-details/lib/utils/zod-to-typescript.ts
862
963
  /**
863
- * React hook for executing Superblocks APIs with type-safe inputs and outputs.
964
+ * Zod schema conversion utilities.
864
965
  *
865
- * @param name - The name of the registered API to execute
866
- * @returns An object with `run` and `cancel` functions
966
+ * Converts Zod schemas to TypeScript type strings and JSON Schema for display in the UI.
967
+ */
968
+ /**
969
+ * Convert a Zod schema to JSON Schema format.
867
970
  *
868
- * @example
869
- * ```typescript
870
- * const myApi = useApiStateful("getUserData");
971
+ * @param schema - The Zod schema to convert
972
+ * @returns JSON Schema representation
973
+ */
974
+ function convertToJsonSchema(schema) {
975
+ try {
976
+ return zodToJsonSchema(schema, {
977
+ $refStrategy: "none",
978
+ errorMessages: false
979
+ });
980
+ } catch (error) {
981
+ console.warn("[zod-to-typescript] Failed to convert to JSON Schema:", error);
982
+ return { type: "unknown" };
983
+ }
984
+ }
985
+ /**
986
+ * Convert a JSON Schema to TypeScript type string.
871
987
  *
872
- * try {
873
- * const result = await myApi.run({ userId: "123" });
874
- * console.log(result);
875
- * } catch (error) {
876
- * // error is a string describing what went wrong
877
- * console.error(error);
878
- * }
879
- * ```
988
+ * @param schema - The JSON Schema to convert
989
+ * @param indent - Current indentation level
990
+ * @returns TypeScript type string representation
991
+ */
992
+ function jsonSchemaToTypescript(schema, indent = 0) {
993
+ const indentStr = " ".repeat(indent);
994
+ const nextIndent = " ".repeat(indent + 1);
995
+ if (schema.$ref) return schema.$ref.split("/").pop() || "unknown";
996
+ if (schema.const !== void 0) return typeof schema.const === "string" ? `"${schema.const}"` : String(schema.const);
997
+ if (schema.enum) return schema.enum.map((v) => typeof v === "string" ? `"${v}"` : String(v)).join(" | ");
998
+ if (schema.anyOf) return schema.anyOf.map((s) => jsonSchemaToTypescript(s, indent)).join(" | ");
999
+ if (schema.oneOf) return schema.oneOf.map((s) => jsonSchemaToTypescript(s, indent)).join(" | ");
1000
+ if (schema.allOf) return schema.allOf.map((s) => jsonSchemaToTypescript(s, indent)).join(" & ");
1001
+ const nullable = schema.nullable ? " | null" : "";
1002
+ switch (Array.isArray(schema.type) ? schema.type[0] : schema.type || "unknown") {
1003
+ case "string": return "string" + nullable;
1004
+ case "number":
1005
+ case "integer": return "number" + nullable;
1006
+ case "boolean": return "boolean" + nullable;
1007
+ case "null": return "null";
1008
+ case "array":
1009
+ if (schema.items) {
1010
+ if (Array.isArray(schema.items)) return `[${schema.items.map((s) => jsonSchemaToTypescript(s, indent)).join(", ")}]` + nullable;
1011
+ const itemType = jsonSchemaToTypescript(schema.items, indent);
1012
+ return (itemType.includes(" | ") || itemType.includes(" & ") ? `(${itemType})[]` : `${itemType}[]`) + nullable;
1013
+ }
1014
+ return "unknown[]" + nullable;
1015
+ case "object": {
1016
+ if (!schema.properties || Object.keys(schema.properties).length === 0) {
1017
+ if (schema.additionalProperties) return `Record<string, ${typeof schema.additionalProperties === "object" ? jsonSchemaToTypescript(schema.additionalProperties, indent) : "unknown"}>` + nullable;
1018
+ return "{}" + nullable;
1019
+ }
1020
+ const required = schema.required || [];
1021
+ return `{\n${Object.entries(schema.properties).map(([key, propSchema]) => {
1022
+ const isRequired = required.includes(key);
1023
+ const propType = jsonSchemaToTypescript(propSchema, indent + 1);
1024
+ return `${nextIndent}${key}${isRequired ? "" : "?"}: ${propType}`;
1025
+ }).join(";\n")};\n${indentStr}}` + nullable;
1026
+ }
1027
+ default: return "unknown" + nullable;
1028
+ }
1029
+ }
1030
+ /**
1031
+ * Convert a Zod schema to both TypeScript string and JSON Schema.
1032
+ *
1033
+ * @param schema - The Zod schema to convert
1034
+ * @returns Object containing both representations
1035
+ */
1036
+ function convertZodSchema(schema) {
1037
+ const jsonSchema = convertToJsonSchema(schema);
1038
+ return {
1039
+ typescript: jsonSchemaToTypescript(jsonSchema),
1040
+ jsonSchema
1041
+ };
1042
+ }
1043
+
1044
+ //#endregion
1045
+ //#region src/lib/internal-details/lib/sdk-api-registry.ts
1046
+ /**
1047
+ * SDK API Registry for the library iframe.
1048
+ *
1049
+ * Tracks registered SDK APIs and notifies ui-code-mode when APIs are
1050
+ * registered or unregistered (for display in the sidebar).
1051
+ */
1052
+ /**
1053
+ * Set of registered API names to prevent duplicate registrations.
1054
+ */
1055
+ const registeredApis = /* @__PURE__ */ new Set();
1056
+ /**
1057
+ * Extract metadata from a compiled API for display in the UI.
1058
+ *
1059
+ * @param name - The API name
1060
+ * @param api - The compiled API (regular or streaming)
1061
+ * @param sourceCode - Optional TypeScript source code
1062
+ * @returns SdkApiMetadata for the API
1063
+ */
1064
+ function extractSdkApiMetadata(name, api, sourceCode) {
1065
+ const streaming = isStreamingApi(api);
1066
+ const inputSchema = convertZodSchema(api.inputSchema);
1067
+ let outputSchema;
1068
+ let chunkSchema;
1069
+ if (streaming) chunkSchema = convertZodSchema(api.chunkSchema);
1070
+ else outputSchema = convertZodSchema(api.outputSchema);
1071
+ return {
1072
+ name,
1073
+ inputSchema,
1074
+ outputSchema,
1075
+ chunkSchema,
1076
+ isStreaming: streaming,
1077
+ integrations: (api.integrations ?? []).map((integration) => ({
1078
+ key: integration.key,
1079
+ pluginId: integration.pluginId,
1080
+ name: integration.key,
1081
+ id: integration.id
1082
+ })),
1083
+ sourceCode
1084
+ };
1085
+ }
1086
+ /**
1087
+ * Register an SDK API and notify ui-code-mode.
880
1088
  *
881
- * ## Error Handling
1089
+ * @param name - The API name
1090
+ * @param api - The compiled API
1091
+ * @param sourceCode - Optional TypeScript source code for display in the UI
1092
+ * @returns true if the API was newly registered, false if already existed
1093
+ */
1094
+ function registerSdkApi(name, api, sourceCode) {
1095
+ const isNew = !registeredApis.has(name);
1096
+ registeredApis.add(name);
1097
+ sendMessageImmediately({
1098
+ type: "sdk-api-registered",
1099
+ payload: {
1100
+ name,
1101
+ metadata: extractSdkApiMetadata(name, api, sourceCode)
1102
+ }
1103
+ });
1104
+ console.debug(`[sdk-api-registry] ${isNew ? "Registered" : "Updated"} SDK API: ${name}`);
1105
+ return isNew;
1106
+ }
1107
+ /**
1108
+ * Update the source code for an already-registered SDK API.
1109
+ * This is called by the dev server when an API file changes.
882
1110
  *
883
- * When an API execution fails, the `run` function throws a **string** error message.
884
- * The error format can be one of:
1111
+ * @param name - The API name
1112
+ * @param sourceCode - The new TypeScript source code
1113
+ */
1114
+ function updateSdkApiSourceCode(name, sourceCode) {
1115
+ if (!registeredApis.has(name)) {
1116
+ console.warn(`[sdk-api-registry] Cannot update source for unregistered API: ${name}`);
1117
+ return;
1118
+ }
1119
+ sendMessageImmediately({
1120
+ type: "sdk-api-source-updated",
1121
+ payload: {
1122
+ name,
1123
+ sourceCode
1124
+ }
1125
+ });
1126
+ console.debug(`[sdk-api-registry] Updated source code for SDK API: ${name}`);
1127
+ }
1128
+ /**
1129
+ * Clear all registered SDK APIs.
1130
+ * Useful for testing or when the application reloads.
1131
+ */
1132
+ function clearSdkApiRegistry() {
1133
+ registeredApis.clear();
1134
+ }
1135
+
1136
+ //#endregion
1137
+ //#region src/lib/internal-details/lib/sdk-api-discovery.ts
1138
+ /**
1139
+ * SDK API Discovery Module.
885
1140
  *
886
- * - **Client errors (4xx)**: System error message from the server
887
- * - Example: `"Authentication failed"`, `"Invalid request parameters"`
1141
+ * Discovers and registers all SDK APIs by importing the app's API registry
1142
+ * at server/apis/index.ts. This enables SDK APIs to appear in the sidebar
1143
+ * immediately, rather than waiting for them to be executed.
1144
+ */
1145
+ /**
1146
+ * Discover and register all SDK APIs from the app's API registry.
888
1147
  *
889
- * - **API execution errors**: Formatted message from unhandled API errors
890
- * - Example: `"Error in API: Division by zero"`, `"Error in API: Database connection failed"`
1148
+ * Imports server/apis/index.ts (the same registry used by useApi for type
1149
+ * inference) and registers each exported API with the SDK API registry.
1150
+ */
1151
+ async function discoverAndRegisterSdkApis() {
1152
+ if (!root_store_default.sdkApiEnabled) return;
1153
+ try {
1154
+ const apis = (await import(new URL("/server/apis/index.ts", window.location.origin).href)).default;
1155
+ const apiNames = Object.keys(apis);
1156
+ if (apiNames.length === 0) {
1157
+ console.debug("[sdk-api-discovery] No SDK APIs found in registry");
1158
+ return;
1159
+ }
1160
+ console.debug(`[sdk-api-discovery] Discovering ${apiNames.length} SDK APIs:`, apiNames);
1161
+ const sourcePromises = apiNames.map(async (name) => {
1162
+ try {
1163
+ const resp = await fetch(`/sb-raw-source/server/apis/${name}/api.ts`);
1164
+ return resp.ok ? await resp.text() : void 0;
1165
+ } catch {
1166
+ return;
1167
+ }
1168
+ });
1169
+ const sources = await Promise.all(sourcePromises);
1170
+ const results = await Promise.allSettled(apiNames.map(async (name, i) => {
1171
+ const apiModule = apis[name];
1172
+ if (!apiModule) throw new Error(`API ${name} not found in registry`);
1173
+ registerSdkApi(name, apiModule, sources[i]);
1174
+ }));
1175
+ const succeeded = results.filter((r) => r.status === "fulfilled").length;
1176
+ const failed = results.filter((r) => r.status === "rejected").length;
1177
+ console.debug(`[sdk-api-discovery] Registered ${succeeded} APIs, ${failed} failed`);
1178
+ } catch (error) {
1179
+ console.error("[sdk-api-discovery] Failed to discover SDK APIs:", error);
1180
+ }
1181
+ }
1182
+ if (import.meta.hot) import.meta.hot.accept("/server/apis/index.ts", () => {
1183
+ if (!root_store_default.sdkApiEnabled) return;
1184
+ console.debug("[sdk-api-discovery] API registry updated via HMR, re-running discovery");
1185
+ clearSdkApiRegistry();
1186
+ discoverAndRegisterSdkApis();
1187
+ });
1188
+
1189
+ //#endregion
1190
+ //#region src/lib/internal-details/use-api.ts
1191
+ /**
1192
+ * Unified API hook module.
891
1193
  *
892
- * - **Infrastructure errors**: High-level error messages
893
- * - Example: `"No application ID was found"`, `"No active agent found"`
1194
+ * Provides both the legacy YAML-based API system (`useApiStateful`) and the
1195
+ * new SDK-based API system (`useSdkApi`). The public `useApi` hook delegates
1196
+ * to one or the other based on the `sdkApiEnabled` feature flag on rootStore.
894
1197
  *
895
- * The error is also logged to the console before being thrown.
1198
+ * The flag is set once at bootstrap time and never changes within a session,
1199
+ * so the conditional hook delegation is safe.
1200
+ */
1201
+ /**
1202
+ * Get the API path for an API by name.
896
1203
  *
897
- * ## Usage Notes
1204
+ * When SDK APIs are enabled, paths point to `/server/apis/<name>/api.ts`.
1205
+ * When using legacy YAML mode, paths point to `/apis/<name>/api.yaml`.
1206
+ */
1207
+ const getApiPath = (name) => {
1208
+ if (root_store_default.sdkApiEnabled) {
1209
+ let apiPath$1 = name;
1210
+ if (!apiPath$1.startsWith("/server/apis")) apiPath$1 = `/server/apis/${name}/api.ts`;
1211
+ return apiPath$1;
1212
+ }
1213
+ let apiPath = name;
1214
+ if (!apiPath.startsWith("/apis")) apiPath = `/apis/${name}/api.yaml`;
1215
+ return apiPath;
1216
+ };
1217
+ /**
1218
+ * Resolve integration configs from the parent before API execution.
1219
+ * Only used when SDK API mode is enabled.
898
1220
  *
899
- * - Always wrap `run()` calls in try/catch blocks to handle errors gracefully
900
- * - Errors are thrown as strings, not Error objects
901
- * - If the API executes successfully but returns no data, `run()` returns `undefined`
902
- * - Use `cancel()` to abort in-flight API requests
1221
+ * @deprecated Kept for backward compatibility with edit mode.
1222
+ */
1223
+ async function resolveIntegrations(integrations) {
1224
+ if (integrations.length === 0) return [];
1225
+ return new Promise((resolve, reject) => {
1226
+ const callbackId = addNewPromise((result) => {
1227
+ resolve(result);
1228
+ }, false, reject);
1229
+ sendMessageImmediately({
1230
+ type: "sdk-resolve-integrations",
1231
+ payload: {
1232
+ integrations: integrations.map((i) => ({
1233
+ key: i.key,
1234
+ pluginId: i.pluginId,
1235
+ id: i.id
1236
+ })),
1237
+ callbackId
1238
+ }
1239
+ });
1240
+ });
1241
+ }
1242
+ /**
1243
+ * React hook for executing YAML-based Superblocks APIs.
1244
+ *
1245
+ * This is the legacy API system that uses `rootStore.apis.runApiByPath`.
1246
+ * It remains the default when the `sdkApiEnabled` flag is off.
1247
+ *
1248
+ * @param name - The name of the registered API to execute
1249
+ * @returns An object with `run` and `cancel` functions
903
1250
  */
904
1251
  function useApiStateful(name) {
905
1252
  const { sourceId } = useJSXContext();
@@ -923,6 +1270,49 @@ function useApiStateful(name) {
923
1270
  }, [name])
924
1271
  };
925
1272
  }
1273
+ /**
1274
+ * React hook for calling SDK-based APIs via postMessage to the parent frame.
1275
+ * Only active when `rootStore.sdkApiEnabled` is true.
1276
+ */
1277
+ function useSdkApi(apiName) {
1278
+ return {
1279
+ run: useCallback(async (inputs) => {
1280
+ return new Promise((resolve, reject) => {
1281
+ const callbackId = addNewPromise((result) => {
1282
+ const executionResult = result;
1283
+ if (executionResult.success) resolve(executionResult.output);
1284
+ else reject(executionResult.error ?? /* @__PURE__ */ new Error("API execution failed"));
1285
+ }, false, reject);
1286
+ sendMessageImmediately({
1287
+ type: "sdk-execute-api",
1288
+ payload: {
1289
+ apiName,
1290
+ input: inputs ?? {},
1291
+ callbackId
1292
+ }
1293
+ });
1294
+ });
1295
+ }, [apiName]),
1296
+ cancel: useCallback(async () => {
1297
+ throw new Error(`Cancellation is not yet implemented for SDK APIs. API "${apiName}" will continue executing.`);
1298
+ }, [apiName])
1299
+ };
1300
+ }
1301
+ function useApi(apiName) {
1302
+ if (root_store_default.sdkApiEnabled) return useSdkApi(apiName);
1303
+ return useApiStateful(apiName);
1304
+ }
1305
+ /**
1306
+ * Creates a typed version of useApi bound to a specific API registry type.
1307
+ *
1308
+ * This enables full type inference using only type imports from the server,
1309
+ * keeping backend code out of the client bundle.
1310
+ *
1311
+ * @returns A typed useApi function
1312
+ */
1313
+ function useTypedApi() {
1314
+ return useApi;
1315
+ }
926
1316
 
927
1317
  //#endregion
928
1318
  //#region src/lib/internal-details/embed-store.ts
@@ -1634,10 +2024,21 @@ var AsyncSocket = class {
1634
2024
  //#endregion
1635
2025
  //#region src/edit-mode/source-update-api.ts
1636
2026
  const PING_INTERVAL_MS = 15e3;
2027
+ function safeStringifyError(error) {
2028
+ if (!error) return "Unknown error";
2029
+ if (typeof error === "string") return error;
2030
+ if (error instanceof Error) return error.message;
2031
+ try {
2032
+ return JSON.stringify(error);
2033
+ } catch {
2034
+ return String(error);
2035
+ }
2036
+ }
1637
2037
  var OperationAPI = class {
1638
2038
  retryAttempts = 0;
1639
2039
  asyncSocket = new AsyncSocket();
1640
2040
  isocket = null;
2041
+ currentAuthorization;
1641
2042
  constructor(serverUrl) {
1642
2043
  this.serverUrl = serverUrl;
1643
2044
  }
@@ -1645,6 +2046,7 @@ var OperationAPI = class {
1645
2046
  * @throws {Error} if the websocket connection can't be initiated after 3 attempts
1646
2047
  */
1647
2048
  async connect({ peerId, userId, authorization, applicationId, traceContext, connectionType, connectionTarget }) {
2049
+ this.currentAuthorization = authorization;
1648
2050
  try {
1649
2051
  const result = await connectSocket(this.serverUrl, {
1650
2052
  peerId,
@@ -1657,7 +2059,6 @@ var OperationAPI = class {
1657
2059
  onClose: this.handleSocketClose({
1658
2060
  peerId,
1659
2061
  userId,
1660
- authorization,
1661
2062
  applicationId
1662
2063
  })
1663
2064
  });
@@ -1666,13 +2067,12 @@ var OperationAPI = class {
1666
2067
  this.asyncSocket.setSocket(result.socket);
1667
2068
  this.isocket = result.isocket;
1668
2069
  } else throw new Error("Failed to create socket connection");
1669
- } catch {
2070
+ } catch (error) {
1670
2071
  this.retryAttempts++;
1671
- console.info(`App<>Dev box initial connection failed, retrying attempt ${this.retryAttempts}...`);
2072
+ console.info(`App<>Dev box initial connection failed, retrying attempt ${this.retryAttempts}...`, safeStringifyError(error));
1672
2073
  await this.retryConnection({
1673
2074
  peerId,
1674
2075
  userId,
1675
- authorization,
1676
2076
  applicationId
1677
2077
  });
1678
2078
  }
@@ -1689,12 +2089,13 @@ var OperationAPI = class {
1689
2089
  * This is called when the token is refreshed in the parent window.
1690
2090
  */
1691
2091
  updateAuthorization(newAuthorization) {
2092
+ this.currentAuthorization = newAuthorization;
1692
2093
  if (this.isocket) {
1693
2094
  this.isocket.setAuthorization(newAuthorization);
1694
2095
  console.log("[internal] [OperationAPI] Updated iframe socket authorization token");
1695
2096
  } else console.warn("[internal] [OperationAPI] Cannot update authorization: socket not yet initialized");
1696
2097
  }
1697
- handleSocketClose({ peerId, userId, authorization, applicationId }) {
2098
+ handleSocketClose({ peerId, userId, applicationId }) {
1698
2099
  return async (event) => {
1699
2100
  if (event.code === 1008) {
1700
2101
  sendNotification({
@@ -1708,24 +2109,34 @@ var OperationAPI = class {
1708
2109
  }
1709
2110
  console.info(`[internal] [OperationAPI] App<>Dev box Socket closed, retrying attempt ${this.retryAttempts + 1}...`);
1710
2111
  root_store_default.editStore?.connectionManager.disconnect();
1711
- await this.retryConnection({
2112
+ try {
2113
+ await this.retryConnection({
2114
+ peerId,
2115
+ userId,
2116
+ applicationId
2117
+ });
2118
+ } catch (error) {
2119
+ console.error("[internal] [OperationAPI] Failed to reconnect after all retry attempts", safeStringifyError(error));
2120
+ this.asyncSocket.clearSocket();
2121
+ sendNotification({
2122
+ message: "Connection lost",
2123
+ description: "Failed to reconnect to the dev server. Please refresh the page.",
2124
+ type: "error",
2125
+ duration: 600
2126
+ });
2127
+ }
2128
+ };
2129
+ }
2130
+ async retryConnection({ peerId, userId, applicationId }) {
2131
+ if (this.retryAttempts < 3) {
2132
+ if (!this.currentAuthorization) console.warn("[internal] [OperationAPI] Retrying connection without authorization token");
2133
+ await this.connect({
1712
2134
  peerId,
1713
2135
  userId,
1714
- authorization,
2136
+ authorization: this.currentAuthorization,
1715
2137
  applicationId
1716
2138
  });
1717
- if (this.retryAttempts >= 3) this.asyncSocket.clearSocket();
1718
- this.retryAttempts++;
1719
- };
1720
- }
1721
- async retryConnection({ peerId, userId, authorization, applicationId }) {
1722
- if (this.retryAttempts < 3) await this.connect({
1723
- peerId,
1724
- userId,
1725
- authorization,
1726
- applicationId
1727
- });
1728
- else {
2139
+ } else {
1729
2140
  console.info(`[internal] [OperationAPI] App<>Dev box Socket closed, failed to reconnect after 3 attempts. Throwing error.`);
1730
2141
  throw new Error("Failed to reconnect after 3 attempts");
1731
2142
  }
@@ -1805,6 +2216,11 @@ async function connectSocket(serverUrl, { peerId, userId, applicationId, authori
1805
2216
  name: err.name
1806
2217
  };
1807
2218
  }
2219
+ }],
2220
+ sdkApiSourceUpdated: [async (payload) => {
2221
+ const { apiName, sourceCode } = payload;
2222
+ console.debug(`[source-update-api] SDK API source updated: ${apiName}`);
2223
+ updateSdkApiSourceCode(apiName, sourceCode);
1808
2224
  }]
1809
2225
  }, [], trace.getTracer("superblocks-ui-framework"), {
1810
2226
  onClose: (event) => {
@@ -3480,16 +3896,66 @@ function EditorHotkeys({ children }) {
3480
3896
  //#endregion
3481
3897
  //#region src/edit-mode/screenshot-handler.ts
3482
3898
  let cleanupCallback$1 = void 0;
3899
+ const MAX_SCREENSHOT_DIMENSION = 8e3 * .98;
3483
3900
  /**
3484
3901
  * Calculate the scale factor for screenshot capture to keep output image
3485
3902
  * dimensions under API limits (Claude's limit is 8000px).
3486
3903
  */
3487
3904
  function calculateScreenshotScale(elementWidth, elementHeight) {
3488
- const MAX_SCREENSHOT_DIMENSION = 8e3 * .98;
3489
3905
  const maxOutputDim = Math.max(elementWidth, elementHeight);
3490
3906
  return maxOutputDim > MAX_SCREENSHOT_DIMENSION ? MAX_SCREENSHOT_DIMENSION / maxOutputDim : 1;
3491
3907
  }
3492
3908
  /**
3909
+ * Measure the full content dimensions for a target element, accounting for
3910
+ * cases where scrolling happens in a nested child rather than the target
3911
+ * itself. When the target is the root element its own scrollHeight may only
3912
+ * reflect the viewport while a child container holds the real content extent.
3913
+ */
3914
+ function measureContentDimensions(targetElement, rootElement) {
3915
+ let width$1 = Math.max(targetElement.scrollWidth, targetElement.offsetWidth);
3916
+ let height$1 = Math.max(targetElement.scrollHeight, targetElement.offsetHeight);
3917
+ if (targetElement === rootElement) {
3918
+ width$1 = Math.max(width$1, document.documentElement.scrollWidth, document.body.scrollWidth);
3919
+ height$1 = Math.max(height$1, document.documentElement.scrollHeight, document.body.scrollHeight);
3920
+ }
3921
+ return {
3922
+ width: width$1,
3923
+ height: height$1
3924
+ };
3925
+ }
3926
+ /**
3927
+ * Load a data-URL image and return its actual pixel dimensions.
3928
+ */
3929
+ function loadImage(dataUrl) {
3930
+ return new Promise((resolve, reject) => {
3931
+ const img = new Image();
3932
+ img.onload = () => resolve(img);
3933
+ img.onerror = reject;
3934
+ img.src = dataUrl;
3935
+ });
3936
+ }
3937
+ /**
3938
+ * Downscale a data-URL image so that neither dimension exceeds
3939
+ * MAX_SCREENSHOT_DIMENSION. Returns the original if already within limits.
3940
+ */
3941
+ async function ensureWithinDimensionLimit(dataUrl) {
3942
+ const img = await loadImage(dataUrl);
3943
+ const { naturalWidth: width$1, naturalHeight: height$1 } = img;
3944
+ const maxDim = Math.max(width$1, height$1);
3945
+ if (maxDim <= MAX_SCREENSHOT_DIMENSION) return dataUrl;
3946
+ console.warn(`[Library Screenshot Handler] Output image ${width$1}x${height$1} exceeds ${MAX_SCREENSHOT_DIMENSION}px limit, resizing…`);
3947
+ const scale = MAX_SCREENSHOT_DIMENSION / maxDim;
3948
+ const targetWidth = Math.round(width$1 * scale);
3949
+ const targetHeight = Math.round(height$1 * scale);
3950
+ const canvas = document.createElement("canvas");
3951
+ canvas.width = targetWidth;
3952
+ canvas.height = targetHeight;
3953
+ const ctx = canvas.getContext("2d");
3954
+ if (!ctx) throw new Error("Canvas 2d context not available");
3955
+ ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
3956
+ return canvas.toDataURL("image/webp", .85);
3957
+ }
3958
+ /**
3493
3959
  * Counts loading indicators in the DOM that signal data is still being
3494
3960
  * fetched or the UI hasn't finished rendering.
3495
3961
  */
@@ -3556,8 +4022,7 @@ function initializeScreenshotHandler() {
3556
4022
  targetLabel = `"${selector}"`;
3557
4023
  console.log(`${LOG_PREFIX} Selector "${selector}" matched element (${matched.offsetWidth}x${matched.offsetHeight})`);
3558
4024
  }
3559
- const elementWidth = Math.max(targetElement.scrollWidth, targetElement.offsetWidth);
3560
- const elementHeight = Math.max(targetElement.scrollHeight, targetElement.offsetHeight);
4025
+ const { width: elementWidth, height: elementHeight } = measureContentDimensions(targetElement, rootElement);
3561
4026
  const scale = calculateScreenshotScale(elementWidth, elementHeight);
3562
4027
  const captureStartTime = performance.now();
3563
4028
  console.log(`${LOG_PREFIX} Starting domToWebp capture of ${targetLabel} (element: ${elementWidth}x${elementHeight}, scale: ${scale.toFixed(2)}, output: ~${Math.round(elementWidth * scale)}x${Math.round(elementHeight * scale)})...`);
@@ -3575,11 +4040,11 @@ function initializeScreenshotHandler() {
3575
4040
  } }
3576
4041
  });
3577
4042
  const captureMs = performance.now() - captureStartTime;
3578
- const dataUrlSizeKB = (dataUrl.length / 1024).toFixed(1);
3579
- console.log(`${LOG_PREFIX} domToWebp completed in ${captureMs.toFixed(1)}ms, output size: ${dataUrlSizeKB}KB`);
4043
+ console.log(`${LOG_PREFIX} domToWebp completed in ${captureMs.toFixed(1)}ms, output size: ${(dataUrl.length / 1024).toFixed(1)}KB`);
4044
+ const safeDataUrl = await ensureWithinDimensionLimit(dataUrl);
3580
4045
  const totalMs = performance.now() - handlerStartTime;
3581
4046
  console.log(`${LOG_PREFIX} Screenshot captured successfully! Total: ${totalMs.toFixed(1)}ms (import: ${importMs.toFixed(1)}ms, capture: ${captureMs.toFixed(1)}ms)`);
3582
- editorBridge.sendCaptureScreenshotResponse(callbackId, dataUrl);
4047
+ editorBridge.sendCaptureScreenshotResponse(callbackId, safeDataUrl);
3583
4048
  } catch (error) {
3584
4049
  const errorMessage = error instanceof Error ? error.message : String(error);
3585
4050
  const totalMs = performance.now() - handlerStartTime;
@@ -3704,10 +4169,15 @@ if (import.meta.hot) {
3704
4169
  });
3705
4170
  import.meta.hot.on("vite:beforeUpdate", (args) => {
3706
4171
  try {
3707
- const pathStr = (args?.updates ?? []).map((update) => update.path).join(", ");
4172
+ const paths = (args?.updates ?? []).map((update) => update.path);
4173
+ const pathStr = paths.join(", ");
3708
4174
  console.debug("[internal] [iframe-wrappers] vite:beforeUpdate", pathStr);
3709
- } catch (_error) {
3710
- console.debug("[internal] [iframe-wrappers] vite:beforeUpdate");
4175
+ if (paths.some((path) => path.includes("server/apis/index.ts")) && root_store_default.sdkApiEnabled) {
4176
+ console.debug("[internal] [iframe-wrappers] API registry updated, re-discovering APIs");
4177
+ discoverAndRegisterSdkApis();
4178
+ }
4179
+ } catch (error) {
4180
+ console.warn("[internal] [iframe-wrappers] vite:beforeUpdate error:", error);
3711
4181
  }
3712
4182
  });
3713
4183
  }
@@ -3768,6 +4238,11 @@ const IframeConnected = observer(function IframeConnected$1(props) {
3768
4238
  console.debug("[internal] [iframe-wrappers] sending ready");
3769
4239
  editorBridge.sendReady();
3770
4240
  }, []);
4241
+ useEffect(() => {
4242
+ return reaction(() => root_store_default.sdkApiEnabled, (enabled) => {
4243
+ if (enabled) discoverAndRegisterSdkApis();
4244
+ }, { fireImmediately: true });
4245
+ }, []);
3771
4246
  useEffect(() => {
3772
4247
  return linkModifierInterceptor({ isEditMode: true });
3773
4248
  }, []);
@@ -5991,6 +6466,174 @@ function RouteLoadError() {
5991
6466
  ] }) });
5992
6467
  }
5993
6468
 
6469
+ //#endregion
6470
+ //#region src/lib/internal-details/use-streaming-api.ts
6471
+ /**
6472
+ * React hook for calling streaming SDK APIs with real-time chunk delivery.
6473
+ *
6474
+ * Returns an AsyncGenerator that yields chunks as they arrive, allowing
6475
+ * natural iteration with `for await...of`.
6476
+ *
6477
+ * @param api - The imported CompiledStreamingApi definition
6478
+ * @returns Object with `stream` function and `cancel` function
6479
+ *
6480
+ * @example
6481
+ * ```typescript
6482
+ * import StreamChatApi from "../apis/StreamChat/api";
6483
+ * import { useStreamingApi } from "@superblocksteam/library";
6484
+ *
6485
+ * const { stream, cancel } = useStreamingApi(StreamChatApi);
6486
+ *
6487
+ * // Execute and iterate over chunks
6488
+ * const generator = stream({ prompt: "Hello!" });
6489
+ *
6490
+ * for await (const chunk of generator) {
6491
+ * console.log(chunk.text);
6492
+ * // Update UI with each chunk
6493
+ * }
6494
+ * ```
6495
+ *
6496
+ * @example With try/catch for error handling
6497
+ * ```typescript
6498
+ * const { stream } = useStreamingApi(StreamChatApi);
6499
+ *
6500
+ * try {
6501
+ * for await (const chunk of stream({ prompt: "Hello!" })) {
6502
+ * appendToOutput(chunk.text);
6503
+ * }
6504
+ * console.log("Stream completed!");
6505
+ * } catch (error) {
6506
+ * console.error("Stream error:", error);
6507
+ * }
6508
+ * ```
6509
+ */
6510
+ function useStreamingApi(api) {
6511
+ const sdkApiEnabled = root_store_default.sdkApiEnabled;
6512
+ const cancelRef = useRef(null);
6513
+ return {
6514
+ stream: useCallback((inputs) => {
6515
+ if (!sdkApiEnabled) throw new Error("useStreamingApi requires SDK API mode to be enabled. This hook is only available in fullstack applications.");
6516
+ return createStreamGenerator(api, inputs, cancelRef);
6517
+ }, [api, sdkApiEnabled]),
6518
+ cancel: useCallback(() => {
6519
+ if (cancelRef.current) cancelRef.current();
6520
+ }, [])
6521
+ };
6522
+ }
6523
+ /**
6524
+ * Creates an async generator for streaming API execution.
6525
+ */
6526
+ async function* createStreamGenerator(api, inputs, cancelRef) {
6527
+ if (!isStreamingApi(api)) throw new Error("Expected a streaming API. Use useApi() for non-streaming APIs.");
6528
+ const executeQuery = async (integrationId, request, input) => {
6529
+ return new Promise((resolve, reject) => {
6530
+ sendMessageImmediately({
6531
+ type: "sdk-execute-query",
6532
+ payload: {
6533
+ integrationId,
6534
+ request,
6535
+ input,
6536
+ callbackId: addNewPromise(resolve, false, reject)
6537
+ }
6538
+ });
6539
+ });
6540
+ };
6541
+ const executeStreamingQuery = async function* (integrationId, request) {
6542
+ const chunkQueue = [];
6543
+ let resolveNext = null;
6544
+ let streamComplete = false;
6545
+ let streamError = null;
6546
+ const streamId = `stream-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
6547
+ const handleChunk = (event) => {
6548
+ if (event.source !== window.parent) return;
6549
+ if (event.data?.type === "sdk-stream-chunk" && event.data?.payload?.streamId === streamId) {
6550
+ const { chunk, done, error } = event.data.payload;
6551
+ if (error) {
6552
+ streamError = new Error(error.message || "Stream error");
6553
+ streamComplete = true;
6554
+ if (resolveNext) {
6555
+ resolveNext({
6556
+ value: void 0,
6557
+ done: true
6558
+ });
6559
+ resolveNext = null;
6560
+ }
6561
+ return;
6562
+ }
6563
+ if (done) {
6564
+ streamComplete = true;
6565
+ if (resolveNext) {
6566
+ resolveNext({
6567
+ value: void 0,
6568
+ done: true
6569
+ });
6570
+ resolveNext = null;
6571
+ }
6572
+ return;
6573
+ }
6574
+ if (chunk !== void 0) if (resolveNext) {
6575
+ resolveNext({
6576
+ value: chunk,
6577
+ done: false
6578
+ });
6579
+ resolveNext = null;
6580
+ } else chunkQueue.push(chunk);
6581
+ }
6582
+ };
6583
+ window.addEventListener("message", handleChunk);
6584
+ cancelRef.current = () => {
6585
+ streamComplete = true;
6586
+ if (resolveNext) {
6587
+ resolveNext({
6588
+ value: void 0,
6589
+ done: true
6590
+ });
6591
+ resolveNext = null;
6592
+ }
6593
+ window.removeEventListener("message", handleChunk);
6594
+ sendMessageImmediately({
6595
+ type: "sdk-cancel-stream",
6596
+ payload: { streamId }
6597
+ });
6598
+ };
6599
+ sendMessageImmediately({
6600
+ type: "sdk-execute-streaming-query",
6601
+ payload: {
6602
+ integrationId,
6603
+ request,
6604
+ streamId
6605
+ }
6606
+ });
6607
+ try {
6608
+ while (!streamComplete || chunkQueue.length > 0) {
6609
+ if (streamError) throw streamError;
6610
+ if (chunkQueue.length > 0) yield chunkQueue.shift();
6611
+ else if (!streamComplete) {
6612
+ const result = await new Promise((resolve) => {
6613
+ resolveNext = resolve;
6614
+ });
6615
+ if (!result.done) yield result.value;
6616
+ }
6617
+ }
6618
+ if (streamError) throw streamError;
6619
+ } finally {
6620
+ window.removeEventListener("message", handleChunk);
6621
+ cancelRef.current = null;
6622
+ }
6623
+ };
6624
+ const resolvedIntegrations = await resolveIntegrations(api.integrations ?? []);
6625
+ const generator = execute(api, {
6626
+ input: inputs ?? {},
6627
+ integrations: resolvedIntegrations,
6628
+ executionId: `sdk-stream-${Date.now()}`,
6629
+ env: {},
6630
+ user: root_store_default.sdkUser,
6631
+ executeQuery,
6632
+ executeStreamingQuery
6633
+ });
6634
+ for await (const chunk of generator) yield chunk;
6635
+ }
6636
+
5994
6637
  //#endregion
5995
6638
  //#region src/lib/user-facing/global-functions.ts
5996
6639
  async function logoutIntegrations() {
@@ -6170,5 +6813,5 @@ early_console_buffer_default.getInstance().patchEarly();
6170
6813
  registerHtmlElements();
6171
6814
 
6172
6815
  //#endregion
6173
- export { App, PageNotFound, Prop, Property, PropsCategory, RouteLoadError, Section, Superblocks, sb_provider_default as SuperblocksProvider, embedStore, getAppMode, logoutIntegrations, registerComponent, tailwindStylesCategory, useApiStateful as useApi, useEmbedEvent, useEmbedProperties, useEmitEmbedEvent, useSuperblocksGroups, useSuperblocksProfiles, useSuperblocksUser };
6816
+ export { App, PageNotFound, Prop, Property, PropsCategory, RouteLoadError, Section, Superblocks, sb_provider_default as SuperblocksProvider, createElement, embedStore, getAppMode, logoutIntegrations, registerComponent, tailwindStylesCategory, useApi, useApiStateful, useEmbedEvent, useEmbedProperties, useEmitEmbedEvent, useStreamingApi, useSuperblocksDataTags, useSuperblocksGroups, useSuperblocksProfiles, useSuperblocksUser, useTypedApi };
6174
6817
  //# sourceMappingURL=index.js.map