@sagepilot-ai/react-native-sdk 0.2.5 → 0.3.1

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/README.md CHANGED
@@ -12,13 +12,21 @@ The SDK opens the Sagepilot-hosted chat experience inside a React Native WebView
12
12
  - A Sagepilot workspace id and chat channel id
13
13
  - Secure token storage for production apps
14
14
 
15
- ## Install
15
+ ## Install And Configure
16
+
17
+ ### 1. Install The Base Chat SDK
18
+
19
+ Use this install when your app wants Sagepilot chat without Sagepilot's Android CameraX implementation:
16
20
 
17
21
  ```bash
18
- npm install @sagepilot-ai/react-native-sdk react-native-webview
22
+ npm install @sagepilot-ai/react-native-sdk@latest react-native-webview
19
23
  ```
20
24
 
21
- For secure persisted sessions, also install one storage library:
25
+ This installs the base SDK only. It includes `SagepilotChat.configure`, hosted chat WebView, auth/session handling, the native bridge, attachment bridge protocol, `SagepilotFilePickerAdapter`, and custom picker support. It does not include CameraX or Android CameraX Gradle dependencies.
26
+
27
+ ### 2. Add Secure Session Storage
28
+
29
+ For production apps, install one secure storage library so Sagepilot customer sessions survive app restarts.
22
30
 
23
31
  ```bash
24
32
  npm install react-native-keychain
@@ -30,6 +38,94 @@ Expo apps can use `expo-secure-store` instead:
30
38
  npx expo install expo-secure-store
31
39
  ```
32
40
 
41
+ ### 3. Configure The SDK
42
+
43
+ Configure Sagepilot once when your app starts. The minimum working setup is:
44
+
45
+ ```ts
46
+ import { SagepilotChat } from "@sagepilot-ai/react-native-sdk";
47
+
48
+ await SagepilotChat.configure({
49
+ key: "workspace_id:channel_id"
50
+ });
51
+ ```
52
+
53
+ For production, pass secure token storage:
54
+
55
+ ```ts
56
+ import * as Keychain from "react-native-keychain";
57
+ import {
58
+ SagepilotChat,
59
+ createKeychainTokenStorage
60
+ } from "@sagepilot-ai/react-native-sdk";
61
+
62
+ await SagepilotChat.configure({
63
+ key: "workspace_id:channel_id",
64
+ tokenStorage: createKeychainTokenStorage(Keychain)
65
+ });
66
+ ```
67
+
68
+ Then mount `SagepilotChatProvider` once near the top of your app so the SDK can render the hosted chat WebView when you open it:
69
+
70
+ ```tsx
71
+ import { useEffect, type ReactNode } from "react";
72
+ import * as Keychain from "react-native-keychain";
73
+ import {
74
+ SagepilotChat,
75
+ SagepilotChatProvider,
76
+ createKeychainTokenStorage
77
+ } from "@sagepilot-ai/react-native-sdk";
78
+
79
+ function SagepilotProvider({ children }: { children: ReactNode }) {
80
+ useEffect(() => {
81
+ void SagepilotChat.configure({
82
+ key: "workspace_id:channel_id",
83
+ tokenStorage: createKeychainTokenStorage(Keychain)
84
+ });
85
+
86
+ return () => {
87
+ SagepilotChat.destroy();
88
+ };
89
+ }, []);
90
+
91
+ return <SagepilotChatProvider>{children}</SagepilotChatProvider>;
92
+ }
93
+
94
+ export function App() {
95
+ return (
96
+ <SagepilotProvider>
97
+ <RootNavigator />
98
+ </SagepilotProvider>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ### 4. Optional: Add Sagepilot Android CameraX
104
+
105
+ Install this addon only when you want Sagepilot's Android in-app CameraX picker. Apps that skip this step keep the smaller base SDK install.
106
+
107
+ ```bash
108
+ npm install @sagepilot-ai/react-native-camera-addon@latest
109
+ ```
110
+
111
+ The addon declares `@sagepilot-ai/react-native-sdk` as a peer dependency. Keep the base SDK installed directly in your app; the addon should not be treated as a replacement SDK.
112
+
113
+ After installing the addon, run a fresh native Android build. A Metro reload is not enough for React Native autolinking to register `NativeModules.SagepilotInAppCamera`.
114
+
115
+ Then pass the addon picker into `SagepilotChat.configure`:
116
+
117
+ ```ts
118
+ import { SagepilotChat } from "@sagepilot-ai/react-native-sdk";
119
+ import { createSagepilotCameraXFilePicker } from "@sagepilot-ai/react-native-camera-addon";
120
+
121
+ await SagepilotChat.configure({
122
+ key: "workspace_id:channel_id",
123
+ filePicker: createSagepilotCameraXFilePicker()
124
+ });
125
+ ```
126
+
127
+ With this setup, the base SDK still owns chat configuration, hosted WebView rendering, auth/session, and the bridge. The camera addon owns CameraX dependencies, native module registration, camera activity, overlay, and image processing.
128
+
33
129
  ## Configuration Values
34
130
 
35
131
  You configure the SDK with:
@@ -69,9 +165,6 @@ These options are part of the supported React Native SDK configuration contract.
69
165
  | `headers` | Additional headers for SDK service requests. Do not pass server API keys or long-lived secrets from a mobile app. |
70
166
  | `fetch` | Custom fetch implementation for runtimes that need one. |
71
167
  | `tokenStorage` | Secure storage adapter for SDK-created customer session tokens. |
72
- | `cacheStorage` | Optional durable cache adapter used by SDK features such as native file-picker batch recovery. |
73
- | `filePicker` | Optional native picker adapter created with `createSagepilotFilePicker(...)` for camera, gallery, and document attachments. |
74
- | `fileStore` | Optional native file-store adapter created with `createSagepilotFileStore(...)` for durable picked-file bytes. |
75
168
  | `anonymousId` | Optional stable anonymous identifier sent when the SDK creates a customer session. |
76
169
  | `metadata` | Optional app metadata merged into the session creation payload. |
77
170
  | `deviceInfo` | Optional adapter that returns device metadata to include during session creation. |
@@ -181,50 +274,68 @@ export function ChatLauncher() {
181
274
  }
182
275
  ```
183
276
 
184
- ## Push Notifications With Webhooks
277
+ ## Native File Picker
185
278
 
186
- The React Native SDK does not register APNs/FCM device tokens and does not send operating-system push notifications directly. Use Sagepilot webhooks to connect new support messages to your own push-notification system.
279
+ By default, the hosted chat widget's attach button uses the WebView's `<input type="file">`. On low-RAM Android devices this is unreliable: while the system camera is in the foreground, Android can kill the WebView render process, leaving a dead white screen and losing the captured photo.
280
+
281
+ Configure `filePicker` so the attach button uses native pickers instead. Captured files are delivered to the widget over a hardened bridge (protocol v2): they are held in app memory **and** mirrored to `cacheStorage`, then **re-delivered until the widget acknowledges them** — surviving the WebView hydration race, renderer crashes, and (for small batches) full app-process death. Pending batches are pinned to the hosted chat route that requested them, so a recovered photo is re-delivered to the originating thread. On Android the SDK also runs a liveness watchdog on app-resume that repaints and, if the widget is confirmed dead, remounts the WebView.
187
282
 
188
- Recommended flow:
283
+ The base package does not include CameraX. Apps that want the smallest install can use only:
189
284
 
190
- 1. Register and store the app user's push token in your backend.
191
- 2. Configure a Sagepilot webhook subscription for `support.message.created`.
192
- 3. When your webhook receives a new customer-visible support message, map the webhook's customer/conversation data to your app user.
193
- 4. Send the push notification from your backend through APNs, FCM, or your notification provider.
285
+ ```bash
286
+ npm install @sagepilot-ai/react-native-sdk@latest react-native-webview
287
+ ```
194
288
 
195
- The webhook event includes conversation, customer, message, and SDK context so your backend can decide whether to notify the user. Keep push-token storage, notification preferences, and "user is currently viewing chat" suppression logic in your app/backend.
289
+ ### Optional Android CameraX Addon
196
290
 
197
- ## Native File Picker (Strongly Recommended)
291
+ Install the CameraX addon only when you want Sagepilot's Android in-app camera implementation:
198
292
 
199
- By default, the hosted chat widget's attach button uses the WebView's `<input type="file">`. On low-RAM Android devices this is unreliable: while the system camera is in the foreground, Android can kill the WebView render process, leaving a dead white screen and losing the captured photo.
293
+ ```bash
294
+ npm install @sagepilot-ai/react-native-camera-addon@latest
295
+ ```
296
+
297
+ The addon declares `@sagepilot-ai/react-native-sdk` as a peer dependency. Keep the base SDK installed directly in your app; the addon should not be treated as a replacement SDK.
298
+
299
+ Run a clean native Android rebuild after installing the addon. If you only Metro-reload, the native module is not linked yet and `createSagepilotCameraXFilePicker()` returns `undefined`, so the hosted widget falls back to the WebView file input.
300
+
301
+ Then configure the addon adapter:
302
+
303
+ ```ts
304
+ import { SagepilotChat } from "@sagepilot-ai/react-native-sdk";
305
+ import { createSagepilotCameraXFilePicker } from "@sagepilot-ai/react-native-camera-addon";
306
+
307
+ await SagepilotChat.configure({
308
+ key: "workspace_id:channel_id",
309
+ filePicker: createSagepilotCameraXFilePicker()
310
+ });
311
+ ```
200
312
 
201
- Configure `filePicker` so the attach button uses native pickers instead. Captured files are delivered to the widget over a hardened bridge (protocol v2): they are held in app memory **and** mirrored to `cacheStorage`, then **re-delivered until the widget acknowledges them** — surviving the WebView hydration race, renderer crashes, and (for small batches) full app-process death. On Android the SDK also runs a liveness watchdog on app-resume that repaints and, if the widget is confirmed dead, remounts the WebView — healing the blank-white-screen-after-camera case that no reload-on-crash handler can catch.
313
+ The addon owns the Android CameraX Gradle dependencies, `CAMERA` permission merge, native module, camera activity, overlay, and image processing. The base SDK continues to own the hosted chat WebView, auth/session, bridge, attachment protocol, and `SagepilotFilePickerAdapter` type.
202
314
 
203
- For the strongest resilience (a photo captured right before the OS kills the app survives the restart, within the widget's upload limits), pass BOTH `cacheStorage` (stores the tiny manifest) and `fileStore` (stores the bytes on disk). `cacheStorage` alone keeps only small (≤~2MB) batches durable, because Android's AsyncStorage caps a single row near 2MB; `fileStore` removes that durability bottleneck by writing bytes to an app-private file and persisting only file keys in the manifest. The hosted widget still enforces the final upload limits listed below.
315
+ For the strongest resilience (a photo captured right before the OS kills the app survives the restart, at any size), pass BOTH `cacheStorage` (stores the tiny manifest) and `fileStore` (stores the bytes on disk). `cacheStorage` alone keeps only small (≤~2MB) batches durable, because Android's AsyncStorage caps a single row near 2MB; `fileStore` lifts that limit by writing bytes to an app-private file and persisting only file keys in the manifest:
204
316
 
205
317
  ```ts
206
318
  import AsyncStorage from "@react-native-async-storage/async-storage";
207
- import * as imagePicker from "react-native-image-picker";
208
- import * as documentsPicker from "@react-native-documents/picker";
209
319
  import ReactNativeBlobUtil from "react-native-blob-util";
210
- import {
211
- createAsyncStorageCacheStorage,
212
- createSagepilotFilePicker,
213
- createSagepilotFileStore
214
- } from "@sagepilot-ai/react-native-sdk";
320
+ import { createAsyncStorageCacheStorage, createSagepilotFileStore } from "@sagepilot-ai/react-native-sdk";
321
+ import { createSagepilotCameraXFilePicker } from "@sagepilot-ai/react-native-camera-addon";
215
322
 
216
323
  await SagepilotChat.configure({
217
324
  key: "workspace_id:channel_id",
218
325
  cacheStorage: createAsyncStorageCacheStorage(AsyncStorage),
219
326
  fileStore: createSagepilotFileStore(ReactNativeBlobUtil),
220
- filePicker: createSagepilotFilePicker({ imagePicker, documentsPicker, fileReader: ReactNativeBlobUtil })
327
+ filePicker: createSagepilotCameraXFilePicker()
221
328
  });
222
329
  ```
223
330
 
331
+ ### Host-Provided Pickers
332
+
333
+ You can also provide your own picker modules to the base SDK. Modules are passed in rather than imported by the SDK, so Metro never resolves packages your app did not install.
334
+
224
335
  Install the optional dependencies your app needs:
225
336
 
226
337
  ```sh
227
- # Camera and photo gallery (required for the native picker)
338
+ # Camera/gallery through react-native-image-picker
228
339
  npm install react-native-image-picker
229
340
 
230
341
  # Document/file picking (optional)
@@ -257,11 +368,11 @@ await SagepilotChat.configure({
257
368
 
258
369
  Notes:
259
370
 
260
- - Camera and gallery images are downscaled natively (1920px longest edge, JPEG quality 0.8 by default; override with `imageMaxDimension` / `imageQuality`). This keeps memory and upload sizes low on constrained devices.
261
- - The hosted widget enforces the final attachment limits for both WebView and native picker flows: up to 5 files at a time, total previewable images up to 5MB, and total non-image files up to 10MB.
262
- - The native picker also has earlier safety guards before files are read into memory: gallery multi-select is capped at 5 by default (`imageSelectionLimit`), documents are rejected above 20MB (`documentMaxFileSizeBytes`), and images above 15MB (`imageMaxFileSizeBytes`). These higher guards prevent out-of-memory crashes; they do not raise the hosted widget's final upload limits.
371
+ - Android CameraX images are captured by `@sagepilot-ai/react-native-camera-addon` and downscaled natively (1920px longest edge, JPEG quality 0.8 by default; override with `imageMaxDimension` / `imageQuality`). This keeps memory and upload sizes low on constrained devices.
372
+ - The CameraX addon can also expose Android Photo Picker and document sources from its native module. The base `createSagepilotFilePicker(...)` helper uses only the host-provided modules you pass into it.
373
+ - Gallery multi-select is capped at 5 by default (`imageSelectionLimit`); documents are rejected above 20MB (`documentMaxFileSizeBytes`) and images above 15MB (`imageMaxFileSizeBytes`) **before** they are read into memory, preventing out-of-memory crashes on large files.
263
374
  - Failures are never silent: the picker distinguishes user-cancel from real errors and surfaces a typed `code` (`permission_denied`, `camera_unavailable`, `encode_failed`, `file_too_large`, `read_failed`) to the widget.
264
- - Android apps that declare the `CAMERA` permission in their manifest must grant it at runtime; the SDK requests it automatically before opening the camera and surfaces a clear message (with a Settings hint) when it is denied. iOS apps need `NSCameraUsageDescription` and `NSPhotoLibraryUsageDescription` in `Info.plist`.
375
+ - Android apps that install the CameraX addon must grant `CAMERA` at runtime; the addon requests it automatically before opening the camera and surfaces a clear message (with a Settings hint) when it is denied. iOS apps using `react-native-image-picker` need `NSCameraUsageDescription` and `NSPhotoLibraryUsageDescription` in `Info.plist`.
265
376
  - Apps that skip `filePicker` keep the WebView file input. The SDK still recovers from WebView renderer crashes by reloading the widget, but a photo captured at crash time cannot be restored in that mode.
266
377
 
267
378
  ## Secure Session Storage
@@ -311,7 +422,7 @@ Configure the SDK after your app knows the current workspace/channel and, if ava
311
422
 
312
423
  ```tsx
313
424
  import * as Keychain from "react-native-keychain";
314
- import { useEffect } from "react";
425
+ import { useEffect, type ReactNode } from "react";
315
426
  import {
316
427
  SagepilotChat,
317
428
  SagepilotChatProvider,
@@ -320,7 +431,7 @@ import {
320
431
 
321
432
  const tokenStorage = createKeychainTokenStorage(Keychain);
322
433
 
323
- export function SagepilotProvider({ children }: { children: React.ReactNode }) {
434
+ export function SagepilotProvider({ children }: { children: ReactNode }) {
324
435
  useEffect(() => {
325
436
  let cancelled = false;
326
437
 
@@ -3,18 +3,23 @@ buildscript {
3
3
  google()
4
4
  mavenCentral()
5
5
  }
6
+ dependencies {
7
+ classpath "com.android.tools.build:gradle:8.5.0"
8
+ }
6
9
  }
7
10
 
8
- plugins {
9
- id "com.android.library"
10
- }
11
+ apply plugin: "com.android.library"
11
12
 
12
13
  def isNewArchitectureEnabled() {
13
14
  return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
14
15
  }
15
16
 
16
17
  if (isNewArchitectureEnabled()) {
17
- apply plugin: "com.facebook.react"
18
+ try {
19
+ apply plugin: "com.facebook.react"
20
+ } catch (e) {
21
+ logger.warn("Could not apply 'com.facebook.react' plugin. This is expected if syncing the library standalone.")
22
+ }
18
23
  }
19
24
 
20
25
  def safeExtGet(prop, fallback) {
@@ -31,6 +36,10 @@ android {
31
36
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
32
37
  }
33
38
 
39
+ buildFeatures {
40
+ buildConfig true
41
+ }
42
+
34
43
  compileOptions {
35
44
  sourceCompatibility JavaVersion.VERSION_17
36
45
  targetCompatibility JavaVersion.VERSION_17
@@ -8,11 +8,13 @@ import java.util.Collections;
8
8
  import java.util.List;
9
9
 
10
10
  public class SagepilotReactNativeSdkPackage implements ReactPackage {
11
+ /** Registers SDK-owned native modules exposed to JavaScript. */
11
12
  @Override
12
13
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
13
14
  return Collections.emptyList();
14
15
  }
15
16
 
17
+ /** Registers the existing SDK view managers without changing their surface. */
16
18
  @Override
17
19
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
18
20
  return Collections.singletonList(new SagepilotInsetsViewManager());
package/dist/index.d.mts CHANGED
@@ -11,6 +11,21 @@ type SagepilotPickedFile = {
11
11
  size: number;
12
12
  data_base64: string;
13
13
  };
14
+ type SagepilotFilePickerErrorCode = "permission_denied" | "camera_unavailable" | "encode_failed" | "file_too_large" | "read_failed" | "unknown";
15
+ /**
16
+ * Structured picker failure. The provider maps `.code` to a user-facing message
17
+ * and surfaces it via sagepilot:file_picker_error so a failed capture is never
18
+ * silently swallowed.
19
+ */
20
+ declare class SagepilotFilePickerError extends Error {
21
+ readonly code: SagepilotFilePickerErrorCode;
22
+ /** Creates a typed picker error that can be surfaced over the widget bridge. */
23
+ constructor(code: SagepilotFilePickerErrorCode, message: string);
24
+ }
25
+ declare const SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION = 1920;
26
+ declare const SAGEPILOT_DEFAULT_IMAGE_QUALITY = 0.8;
27
+ declare const SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES: number;
28
+
14
29
  type SagepilotFilePickerSource = "camera" | "library" | "documents";
15
30
  type SagepilotFilePickerRequest = {
16
31
  source: SagepilotFilePickerSource;
@@ -97,18 +112,15 @@ type SagepilotCreateFilePickerOptions = {
97
112
  /** Reject camera/library images larger than this (bytes). */
98
113
  imageMaxFileSizeBytes?: number;
99
114
  };
100
- type SagepilotFilePickerErrorCode = "permission_denied" | "camera_unavailable" | "encode_failed" | "file_too_large" | "read_failed" | "unknown";
115
+ /** Shows an actionable Android settings prompt after permanent camera denial. */
116
+ declare function promptOpenSagepilotCameraSettings(): Promise<void>;
101
117
  /**
102
- * Structured picker failure. The provider maps `.code` to a user-facing message
103
- * and surfaces it via sagepilot:file_picker_error so a failed capture is never
104
- * silently swallowed (the historical bug: cancel, OEM cancel-misreport, and
105
- * base64-encode failure all collapsed into an empty array indistinguishable
106
- * from a real user cancel).
118
+ * Android apps that declare the CAMERA permission must also hold it at runtime
119
+ * before the SDK-owned camera screen can open. Request it defensively; apps
120
+ * that never declared it are unaffected and native launch can surface the real
121
+ * failure.
107
122
  */
108
- declare class SagepilotFilePickerError extends Error {
109
- readonly code: SagepilotFilePickerErrorCode;
110
- constructor(code: SagepilotFilePickerErrorCode, message: string);
111
- }
123
+ declare function ensureSagepilotAndroidCameraPermission(): Promise<void>;
112
124
  /**
113
125
  * Builds the SDK file picker adapter from host-app-installed native modules.
114
126
  * Modules are passed in (rather than required by the SDK) so Metro never
@@ -300,7 +312,8 @@ type SagepilotMobileConfig = {
300
312
  * Native file picker used by the hosted chat widget's attach button.
301
313
  * Strongly recommended: in-WebView camera capture is unreliable on
302
314
  * low-RAM Android devices (the OS can kill the WebView render process
303
- * while the camera is open). Build with createSagepilotFilePicker().
315
+ * while the camera is open). Build with createSagepilotFilePicker() or
316
+ * the optional @sagepilot-ai/react-native-camera-addon package.
304
317
  */
305
318
  filePicker?: SagepilotFilePickerAdapter;
306
319
  /**
@@ -543,4 +556,4 @@ declare function SagepilotChatProvider({ children, closeLabel, loadingLabel }: S
543
556
 
544
557
  declare function useSagepilotChat(): SagepilotChatHookState;
545
558
 
546
- export { type SagepilotBiometricsAdapter, type SagepilotBlobUtilLike, type SagepilotBootstrapChannelResponse, type SagepilotCacheStorage, SagepilotChat, SagepilotChatError, type SagepilotChatErrorCode, type SagepilotChatHookState, SagepilotChatProvider, type SagepilotChatProviderProps, type SagepilotConversationCreatedEvent, type SagepilotCreateFilePickerOptions, type SagepilotCreateFileStoreOptions, type SagepilotDeviceInfo, type SagepilotDeviceInfoAdapter, type SagepilotDocumentsPickerModule, type SagepilotFilePickerAdapter, SagepilotFilePickerError, type SagepilotFilePickerErrorCode, type SagepilotFilePickerRequest, type SagepilotFilePickerSource, type SagepilotFileReaderModule, type SagepilotIdentifyResult, type SagepilotIdentity, type SagepilotIdentityState, type SagepilotImagePickerModule, type SagepilotImagePickerResult, type SagepilotLifecycleState, type SagepilotLogoutResponse, type SagepilotMessageComposerOptions, type SagepilotMobileBehaviorConfig, type SagepilotMobileColorScheme, type SagepilotMobileConfig, type SagepilotMobileConfigureResult, type SagepilotMobileLauncherConfig, type SagepilotMobilePresentationConfig, type SagepilotMobilePresentationStyle, type SagepilotMobileResolvedLauncherConfig, type SagepilotMobileState, type SagepilotMobileSubscription, type SagepilotMobileThemeConfig, type SagepilotMobileThemeState, type SagepilotPersistedFileStore, type SagepilotPickedFile, type SagepilotSessionState, type SagepilotTokenStorage, type SagepilotUnreadState, createAsyncStorageCacheStorage, createKeychainTokenStorage, createSagepilotFilePicker, createSagepilotFileStore, useSagepilotChat };
559
+ export { SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION, SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES, SAGEPILOT_DEFAULT_IMAGE_QUALITY, type SagepilotBiometricsAdapter, type SagepilotBlobUtilLike, type SagepilotBootstrapChannelResponse, type SagepilotCacheStorage, SagepilotChat, SagepilotChatError, type SagepilotChatErrorCode, type SagepilotChatHookState, SagepilotChatProvider, type SagepilotChatProviderProps, type SagepilotConversationCreatedEvent, type SagepilotCreateFilePickerOptions, type SagepilotCreateFileStoreOptions, type SagepilotDeviceInfo, type SagepilotDeviceInfoAdapter, type SagepilotDocumentsPickerModule, type SagepilotFilePickerAdapter, SagepilotFilePickerError, type SagepilotFilePickerErrorCode, type SagepilotFilePickerRequest, type SagepilotFilePickerSource, type SagepilotFileReaderModule, type SagepilotIdentifyResult, type SagepilotIdentity, type SagepilotIdentityState, type SagepilotImagePickerModule, type SagepilotImagePickerResult, type SagepilotLifecycleState, type SagepilotLogoutResponse, type SagepilotMessageComposerOptions, type SagepilotMobileBehaviorConfig, type SagepilotMobileColorScheme, type SagepilotMobileConfig, type SagepilotMobileConfigureResult, type SagepilotMobileLauncherConfig, type SagepilotMobilePresentationConfig, type SagepilotMobilePresentationStyle, type SagepilotMobileResolvedLauncherConfig, type SagepilotMobileState, type SagepilotMobileSubscription, type SagepilotMobileThemeConfig, type SagepilotMobileThemeState, type SagepilotPersistedFileStore, type SagepilotPickedFile, type SagepilotSessionState, type SagepilotTokenStorage, type SagepilotUnreadState, createAsyncStorageCacheStorage, createKeychainTokenStorage, createSagepilotFilePicker, createSagepilotFileStore, ensureSagepilotAndroidCameraPermission, promptOpenSagepilotCameraSettings, useSagepilotChat };
package/dist/index.d.ts CHANGED
@@ -11,6 +11,21 @@ type SagepilotPickedFile = {
11
11
  size: number;
12
12
  data_base64: string;
13
13
  };
14
+ type SagepilotFilePickerErrorCode = "permission_denied" | "camera_unavailable" | "encode_failed" | "file_too_large" | "read_failed" | "unknown";
15
+ /**
16
+ * Structured picker failure. The provider maps `.code` to a user-facing message
17
+ * and surfaces it via sagepilot:file_picker_error so a failed capture is never
18
+ * silently swallowed.
19
+ */
20
+ declare class SagepilotFilePickerError extends Error {
21
+ readonly code: SagepilotFilePickerErrorCode;
22
+ /** Creates a typed picker error that can be surfaced over the widget bridge. */
23
+ constructor(code: SagepilotFilePickerErrorCode, message: string);
24
+ }
25
+ declare const SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION = 1920;
26
+ declare const SAGEPILOT_DEFAULT_IMAGE_QUALITY = 0.8;
27
+ declare const SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES: number;
28
+
14
29
  type SagepilotFilePickerSource = "camera" | "library" | "documents";
15
30
  type SagepilotFilePickerRequest = {
16
31
  source: SagepilotFilePickerSource;
@@ -97,18 +112,15 @@ type SagepilotCreateFilePickerOptions = {
97
112
  /** Reject camera/library images larger than this (bytes). */
98
113
  imageMaxFileSizeBytes?: number;
99
114
  };
100
- type SagepilotFilePickerErrorCode = "permission_denied" | "camera_unavailable" | "encode_failed" | "file_too_large" | "read_failed" | "unknown";
115
+ /** Shows an actionable Android settings prompt after permanent camera denial. */
116
+ declare function promptOpenSagepilotCameraSettings(): Promise<void>;
101
117
  /**
102
- * Structured picker failure. The provider maps `.code` to a user-facing message
103
- * and surfaces it via sagepilot:file_picker_error so a failed capture is never
104
- * silently swallowed (the historical bug: cancel, OEM cancel-misreport, and
105
- * base64-encode failure all collapsed into an empty array indistinguishable
106
- * from a real user cancel).
118
+ * Android apps that declare the CAMERA permission must also hold it at runtime
119
+ * before the SDK-owned camera screen can open. Request it defensively; apps
120
+ * that never declared it are unaffected and native launch can surface the real
121
+ * failure.
107
122
  */
108
- declare class SagepilotFilePickerError extends Error {
109
- readonly code: SagepilotFilePickerErrorCode;
110
- constructor(code: SagepilotFilePickerErrorCode, message: string);
111
- }
123
+ declare function ensureSagepilotAndroidCameraPermission(): Promise<void>;
112
124
  /**
113
125
  * Builds the SDK file picker adapter from host-app-installed native modules.
114
126
  * Modules are passed in (rather than required by the SDK) so Metro never
@@ -300,7 +312,8 @@ type SagepilotMobileConfig = {
300
312
  * Native file picker used by the hosted chat widget's attach button.
301
313
  * Strongly recommended: in-WebView camera capture is unreliable on
302
314
  * low-RAM Android devices (the OS can kill the WebView render process
303
- * while the camera is open). Build with createSagepilotFilePicker().
315
+ * while the camera is open). Build with createSagepilotFilePicker() or
316
+ * the optional @sagepilot-ai/react-native-camera-addon package.
304
317
  */
305
318
  filePicker?: SagepilotFilePickerAdapter;
306
319
  /**
@@ -543,4 +556,4 @@ declare function SagepilotChatProvider({ children, closeLabel, loadingLabel }: S
543
556
 
544
557
  declare function useSagepilotChat(): SagepilotChatHookState;
545
558
 
546
- export { type SagepilotBiometricsAdapter, type SagepilotBlobUtilLike, type SagepilotBootstrapChannelResponse, type SagepilotCacheStorage, SagepilotChat, SagepilotChatError, type SagepilotChatErrorCode, type SagepilotChatHookState, SagepilotChatProvider, type SagepilotChatProviderProps, type SagepilotConversationCreatedEvent, type SagepilotCreateFilePickerOptions, type SagepilotCreateFileStoreOptions, type SagepilotDeviceInfo, type SagepilotDeviceInfoAdapter, type SagepilotDocumentsPickerModule, type SagepilotFilePickerAdapter, SagepilotFilePickerError, type SagepilotFilePickerErrorCode, type SagepilotFilePickerRequest, type SagepilotFilePickerSource, type SagepilotFileReaderModule, type SagepilotIdentifyResult, type SagepilotIdentity, type SagepilotIdentityState, type SagepilotImagePickerModule, type SagepilotImagePickerResult, type SagepilotLifecycleState, type SagepilotLogoutResponse, type SagepilotMessageComposerOptions, type SagepilotMobileBehaviorConfig, type SagepilotMobileColorScheme, type SagepilotMobileConfig, type SagepilotMobileConfigureResult, type SagepilotMobileLauncherConfig, type SagepilotMobilePresentationConfig, type SagepilotMobilePresentationStyle, type SagepilotMobileResolvedLauncherConfig, type SagepilotMobileState, type SagepilotMobileSubscription, type SagepilotMobileThemeConfig, type SagepilotMobileThemeState, type SagepilotPersistedFileStore, type SagepilotPickedFile, type SagepilotSessionState, type SagepilotTokenStorage, type SagepilotUnreadState, createAsyncStorageCacheStorage, createKeychainTokenStorage, createSagepilotFilePicker, createSagepilotFileStore, useSagepilotChat };
559
+ export { SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION, SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES, SAGEPILOT_DEFAULT_IMAGE_QUALITY, type SagepilotBiometricsAdapter, type SagepilotBlobUtilLike, type SagepilotBootstrapChannelResponse, type SagepilotCacheStorage, SagepilotChat, SagepilotChatError, type SagepilotChatErrorCode, type SagepilotChatHookState, SagepilotChatProvider, type SagepilotChatProviderProps, type SagepilotConversationCreatedEvent, type SagepilotCreateFilePickerOptions, type SagepilotCreateFileStoreOptions, type SagepilotDeviceInfo, type SagepilotDeviceInfoAdapter, type SagepilotDocumentsPickerModule, type SagepilotFilePickerAdapter, SagepilotFilePickerError, type SagepilotFilePickerErrorCode, type SagepilotFilePickerRequest, type SagepilotFilePickerSource, type SagepilotFileReaderModule, type SagepilotIdentifyResult, type SagepilotIdentity, type SagepilotIdentityState, type SagepilotImagePickerModule, type SagepilotImagePickerResult, type SagepilotLifecycleState, type SagepilotLogoutResponse, type SagepilotMessageComposerOptions, type SagepilotMobileBehaviorConfig, type SagepilotMobileColorScheme, type SagepilotMobileConfig, type SagepilotMobileConfigureResult, type SagepilotMobileLauncherConfig, type SagepilotMobilePresentationConfig, type SagepilotMobilePresentationStyle, type SagepilotMobileResolvedLauncherConfig, type SagepilotMobileState, type SagepilotMobileSubscription, type SagepilotMobileThemeConfig, type SagepilotMobileThemeState, type SagepilotPersistedFileStore, type SagepilotPickedFile, type SagepilotSessionState, type SagepilotTokenStorage, type SagepilotUnreadState, createAsyncStorageCacheStorage, createKeychainTokenStorage, createSagepilotFilePicker, createSagepilotFileStore, ensureSagepilotAndroidCameraPermission, promptOpenSagepilotCameraSettings, useSagepilotChat };