@sagepilot-ai/react-native-sdk 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sagepilot AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
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
22
  npm install @sagepilot-ai/react-native-sdk 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,92 @@ 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
109
+ ```
110
+
111
+ After installing the addon, run a fresh native Android build. A Metro reload is not enough for React Native autolinking to register `NativeModules.SagepilotInAppCamera`.
112
+
113
+ Then pass the addon picker into `SagepilotChat.configure`:
114
+
115
+ ```ts
116
+ import { SagepilotChat } from "@sagepilot-ai/react-native-sdk";
117
+ import { createSagepilotCameraXFilePicker } from "@sagepilot-ai/react-native-camera-addon";
118
+
119
+ await SagepilotChat.configure({
120
+ key: "workspace_id:channel_id",
121
+ filePicker: createSagepilotCameraXFilePicker()
122
+ });
123
+ ```
124
+
125
+ 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.
126
+
33
127
  ## Configuration Values
34
128
 
35
129
  You configure the SDK with:
@@ -178,6 +272,105 @@ export function ChatLauncher() {
178
272
  }
179
273
  ```
180
274
 
275
+ ## Native File Picker
276
+
277
+ 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.
278
+
279
+ 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.
280
+
281
+ The base package does not include CameraX. Apps that want the smallest install can use only:
282
+
283
+ ```bash
284
+ npm install @sagepilot-ai/react-native-sdk react-native-webview
285
+ ```
286
+
287
+ ### Optional Android CameraX Addon
288
+
289
+ Install the CameraX addon only when you want Sagepilot's Android in-app camera implementation:
290
+
291
+ ```bash
292
+ npm install @sagepilot-ai/react-native-camera-addon
293
+ ```
294
+
295
+ 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.
296
+
297
+ Then configure the addon adapter:
298
+
299
+ ```ts
300
+ import { SagepilotChat } from "@sagepilot-ai/react-native-sdk";
301
+ import { createSagepilotCameraXFilePicker } from "@sagepilot-ai/react-native-camera-addon";
302
+
303
+ await SagepilotChat.configure({
304
+ key: "workspace_id:channel_id",
305
+ filePicker: createSagepilotCameraXFilePicker()
306
+ });
307
+ ```
308
+
309
+ 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.
310
+
311
+ 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:
312
+
313
+ ```ts
314
+ import AsyncStorage from "@react-native-async-storage/async-storage";
315
+ import ReactNativeBlobUtil from "react-native-blob-util";
316
+ import { createAsyncStorageCacheStorage, createSagepilotFileStore } from "@sagepilot-ai/react-native-sdk";
317
+ import { createSagepilotCameraXFilePicker } from "@sagepilot-ai/react-native-camera-addon";
318
+
319
+ await SagepilotChat.configure({
320
+ key: "workspace_id:channel_id",
321
+ cacheStorage: createAsyncStorageCacheStorage(AsyncStorage),
322
+ fileStore: createSagepilotFileStore(ReactNativeBlobUtil),
323
+ filePicker: createSagepilotCameraXFilePicker()
324
+ });
325
+ ```
326
+
327
+ ### Host-Provided Pickers
328
+
329
+ 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.
330
+
331
+ Install the optional dependencies your app needs:
332
+
333
+ ```sh
334
+ # Camera/gallery through react-native-image-picker
335
+ npm install react-native-image-picker
336
+
337
+ # Document/file picking (optional)
338
+ npm install @react-native-documents/picker
339
+
340
+ # Reliable document reads (optional, recommended with the documents picker)
341
+ npm install react-native-blob-util
342
+ ```
343
+
344
+ Then pass the modules to `createSagepilotFilePicker`:
345
+
346
+ ```ts
347
+ import * as imagePicker from "react-native-image-picker";
348
+ import * as documentsPicker from "@react-native-documents/picker";
349
+ import ReactNativeBlobUtil from "react-native-blob-util";
350
+ import {
351
+ SagepilotChat,
352
+ createSagepilotFilePicker
353
+ } from "@sagepilot-ai/react-native-sdk";
354
+
355
+ await SagepilotChat.configure({
356
+ key: "workspace_id:channel_id",
357
+ filePicker: createSagepilotFilePicker({
358
+ imagePicker,
359
+ documentsPicker,
360
+ fileReader: ReactNativeBlobUtil
361
+ })
362
+ });
363
+ ```
364
+
365
+ Notes:
366
+
367
+ - 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.
368
+ - 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.
369
+ - 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.
370
+ - 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.
371
+ - 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`.
372
+ - 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.
373
+
181
374
  ## Secure Session Storage
182
375
 
183
376
  The SDK creates opaque customer session tokens at runtime. These tokens are not exposed through hooks or public session APIs.
@@ -225,7 +418,7 @@ Configure the SDK after your app knows the current workspace/channel and, if ava
225
418
 
226
419
  ```tsx
227
420
  import * as Keychain from "react-native-keychain";
228
- import { useEffect } from "react";
421
+ import { useEffect, type ReactNode } from "react";
229
422
  import {
230
423
  SagepilotChat,
231
424
  SagepilotChatProvider,
@@ -234,7 +427,7 @@ import {
234
427
 
235
428
  const tokenStorage = createKeychainTokenStorage(Keychain);
236
429
 
237
- export function SagepilotProvider({ children }: { children: React.ReactNode }) {
430
+ export function SagepilotProvider({ children }: { children: ReactNode }) {
238
431
  useEffect(() => {
239
432
  let cancelled = false;
240
433
 
@@ -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
@@ -1,5 +1,203 @@
1
1
  import { ReactNode } from 'react';
2
2
 
3
+ /**
4
+ * Wire shape delivered to the hosted widget. Matches the Customer API
5
+ * attachment input (`data_base64`) so the widget can reuse its existing
6
+ * validation and send path without backend changes.
7
+ */
8
+ type SagepilotPickedFile = {
9
+ file_name: string;
10
+ mime_type: string;
11
+ size: number;
12
+ data_base64: string;
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
+
29
+ type SagepilotFilePickerSource = "camera" | "library" | "documents";
30
+ type SagepilotFilePickerRequest = {
31
+ source: SagepilotFilePickerSource;
32
+ multiple: boolean;
33
+ };
34
+ /**
35
+ * Host-app-provided native file picker. When configured, the hosted widget's
36
+ * attach button routes through native pickers instead of the WebView's
37
+ * `<input type="file">`. This is required for reliable camera capture on
38
+ * low-RAM Android devices: the OS can kill the WebView render process while
39
+ * the camera activity is foregrounded, which destroys any in-WebView file
40
+ * chooser state. Natively captured files survive that kill and are
41
+ * re-delivered after the WebView reloads.
42
+ */
43
+ type SagepilotFilePickerAdapter = {
44
+ /** Sources offered to the customer in the attachment chooser. */
45
+ sources: SagepilotFilePickerSource[];
46
+ /** Resolves picked files, or an empty array when the customer cancels. */
47
+ pickFiles: (request: SagepilotFilePickerRequest) => Promise<SagepilotPickedFile[]>;
48
+ };
49
+ /** Structural subset of `react-native-image-picker` used by the SDK. */
50
+ type SagepilotImagePickerModule = {
51
+ launchCamera: (options: Record<string, unknown>) => Promise<SagepilotImagePickerResult>;
52
+ launchImageLibrary: (options: Record<string, unknown>) => Promise<SagepilotImagePickerResult>;
53
+ };
54
+ type SagepilotImagePickerResult = {
55
+ didCancel?: boolean;
56
+ errorCode?: string;
57
+ errorMessage?: string;
58
+ assets?: Array<{
59
+ fileName?: string | null;
60
+ type?: string | null;
61
+ fileSize?: number | null;
62
+ base64?: string | null;
63
+ uri?: string | null;
64
+ }>;
65
+ };
66
+ /** Structural subset of `@react-native-documents/picker` used by the SDK. */
67
+ type SagepilotDocumentsPickerModule = {
68
+ pick: (options?: Record<string, unknown>) => Promise<Array<{
69
+ uri: string;
70
+ name: string | null;
71
+ size: number | null;
72
+ type: string | null;
73
+ }>>;
74
+ keepLocalCopy?: (options: {
75
+ files: Array<{
76
+ uri: string;
77
+ fileName: string;
78
+ }>;
79
+ destination: "cachesDirectory" | "documentDirectory";
80
+ }) => Promise<Array<{
81
+ status: string;
82
+ sourceUri: string;
83
+ localUri?: string;
84
+ copyError?: string;
85
+ }>>;
86
+ };
87
+ /** Structural subset of `react-native-blob-util` used for document reads. */
88
+ type SagepilotFileReaderModule = {
89
+ fs: {
90
+ readFile: (path: string, encoding: string) => Promise<unknown>;
91
+ };
92
+ };
93
+ type SagepilotCreateFilePickerOptions = {
94
+ imagePicker?: SagepilotImagePickerModule;
95
+ documentsPicker?: SagepilotDocumentsPickerModule;
96
+ /** Optional `react-native-blob-util` for reliable document reads. */
97
+ fileReader?: SagepilotFileReaderModule;
98
+ /**
99
+ * Longest image edge in pixels for camera/library picks. Downscaling
100
+ * natively keeps memory and upload size low on low-RAM devices.
101
+ */
102
+ imageMaxDimension?: number;
103
+ /** JPEG quality (0-1) applied to camera/library picks. */
104
+ imageQuality?: number;
105
+ /**
106
+ * Max images per gallery selection. Defaults to 5. Set 0 for unlimited
107
+ * (not recommended: a large batch produces a multi-megabyte bridge payload).
108
+ */
109
+ imageSelectionLimit?: number;
110
+ /** Reject documents larger than this (bytes) before reading them. */
111
+ documentMaxFileSizeBytes?: number;
112
+ /** Reject camera/library images larger than this (bytes). */
113
+ imageMaxFileSizeBytes?: number;
114
+ };
115
+ /** Shows an actionable Android settings prompt after permanent camera denial. */
116
+ declare function promptOpenSagepilotCameraSettings(): Promise<void>;
117
+ /**
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.
122
+ */
123
+ declare function ensureSagepilotAndroidCameraPermission(): Promise<void>;
124
+ /**
125
+ * Builds the SDK file picker adapter from host-app-installed native modules.
126
+ * Modules are passed in (rather than required by the SDK) so Metro never
127
+ * tries to resolve packages the host app has not installed.
128
+ *
129
+ * @example
130
+ * import * as imagePicker from "react-native-image-picker";
131
+ * import * as documentsPicker from "@react-native-documents/picker";
132
+ *
133
+ * SagepilotChat.configure({
134
+ * key: "...",
135
+ * filePicker: createSagepilotFilePicker({ imagePicker, documentsPicker })
136
+ * });
137
+ */
138
+ declare function createSagepilotFilePicker(options: SagepilotCreateFilePickerOptions): SagepilotFilePickerAdapter | undefined;
139
+
140
+ /**
141
+ * Durable file outbox for natively-picked attachments.
142
+ *
143
+ * Persisting attachment BYTES in `cacheStorage` (AsyncStorage) does not scale:
144
+ * Android's SQLite-backed store caps a single row at ~2MB (CursorWindow), so a
145
+ * multi-megabyte capture cannot be stored there at all. Instead we write the
146
+ * bytes to a stable app-private file and persist only a tiny manifest of file
147
+ * keys + metadata in `cacheStorage`. This makes durability size-independent and
148
+ * lets a capture survive a full app-process death during the camera round-trip.
149
+ *
150
+ * The store is built from the host app's `react-native-blob-util` so the SDK
151
+ * itself depends on no filesystem package (same injection pattern as the
152
+ * native file picker).
153
+ */
154
+ /** Structural subset of `react-native-blob-util` used by the file store. */
155
+ type SagepilotBlobUtilLike = {
156
+ fs: {
157
+ dirs: {
158
+ DocumentDir: string;
159
+ };
160
+ writeFile: (path: string, data: string, encoding: string) => Promise<unknown>;
161
+ readFile: (path: string, encoding: string) => Promise<unknown>;
162
+ unlink: (path: string) => Promise<unknown>;
163
+ exists: (path: string) => Promise<boolean>;
164
+ ls: (path: string) => Promise<string[]>;
165
+ mkdir: (path: string) => Promise<unknown>;
166
+ };
167
+ };
168
+ /**
169
+ * Persists attachment bytes to disk under stable, app-private keys. Keys (not
170
+ * absolute paths) are stored in the manifest so persistence survives the
171
+ * platform relocating the app container between launches (notably iOS).
172
+ */
173
+ type SagepilotPersistedFileStore = {
174
+ /** Writes base64 content under `key`; resolves once durably on disk. */
175
+ write: (key: string, base64: string) => Promise<void>;
176
+ /** Reads previously-written content back as base64. */
177
+ read: (key: string) => Promise<string>;
178
+ /** Best-effort delete of a single key. */
179
+ remove: (key: string) => Promise<void>;
180
+ /** Best-effort delete of every stored file whose key is not in `keepKeys`. */
181
+ prune: (keepKeys: string[]) => Promise<void>;
182
+ };
183
+ type SagepilotCreateFileStoreOptions = {
184
+ /** Sub-directory under DocumentDir. Defaults to "sagepilot-attachments". */
185
+ directoryName?: string;
186
+ };
187
+ /**
188
+ * Builds a {@link SagepilotPersistedFileStore} backed by react-native-blob-util.
189
+ *
190
+ * @example
191
+ * import ReactNativeBlobUtil from "react-native-blob-util";
192
+ * SagepilotChat.configure({
193
+ * key: "...",
194
+ * cacheStorage: createAsyncStorageCacheStorage(AsyncStorage),
195
+ * fileStore: createSagepilotFileStore(ReactNativeBlobUtil),
196
+ * filePicker: createSagepilotFilePicker({ imagePicker, documentsPicker, fileReader: ReactNativeBlobUtil })
197
+ * });
198
+ */
199
+ declare function createSagepilotFileStore(blobUtil: SagepilotBlobUtilLike, options?: SagepilotCreateFileStoreOptions): SagepilotPersistedFileStore;
200
+
3
201
  type SagepilotMobilePresentationStyle = "sheet" | "fullScreen" | "push";
4
202
  type SagepilotMobileColorScheme = "system" | "light" | "dark";
5
203
  type SagepilotChatErrorCode = "invalid_config" | "not_initialized" | "already_initialized" | "network_error" | "invalid_response" | "invalid_request" | "unauthorized" | "forbidden" | "session_not_found" | "session_expired" | "channel_not_found" | "conversation_not_found" | "message_not_found" | "contact_required" | "otp_required" | "otp_invalid" | "otp_expired" | "identity_verification_failed" | "rate_limited" | "idempotency_conflict" | "not_implemented" | "internal_error";
@@ -110,6 +308,22 @@ type SagepilotMobileConfig = {
110
308
  cacheStorage?: SagepilotCacheStorage;
111
309
  deviceInfo?: SagepilotDeviceInfoAdapter;
112
310
  biometrics?: SagepilotBiometricsAdapter;
311
+ /**
312
+ * Native file picker used by the hosted chat widget's attach button.
313
+ * Strongly recommended: in-WebView camera capture is unreliable on
314
+ * low-RAM Android devices (the OS can kill the WebView render process
315
+ * while the camera is open). Build with createSagepilotFilePicker() or
316
+ * the optional @sagepilot-ai/react-native-camera-addon package.
317
+ */
318
+ filePicker?: SagepilotFilePickerAdapter;
319
+ /**
320
+ * Durable on-disk store for picked attachments. When provided, a captured
321
+ * photo/document survives a full app-process death during the camera
322
+ * round-trip (the SDK rehydrates and re-delivers it on relaunch), with no
323
+ * size limit. Without it, durability falls back to small batches stored in
324
+ * cacheStorage. Build with createSagepilotFileStore(ReactNativeBlobUtil).
325
+ */
326
+ fileStore?: SagepilotPersistedFileStore;
113
327
  presentation?: SagepilotMobilePresentationConfig;
114
328
  theme?: SagepilotMobileThemeConfig;
115
329
  behavior?: SagepilotMobileBehaviorConfig;
@@ -342,4 +556,4 @@ declare function SagepilotChatProvider({ children, closeLabel, loadingLabel }: S
342
556
 
343
557
  declare function useSagepilotChat(): SagepilotChatHookState;
344
558
 
345
- export { type SagepilotBiometricsAdapter, type SagepilotBootstrapChannelResponse, type SagepilotCacheStorage, SagepilotChat, SagepilotChatError, type SagepilotChatErrorCode, type SagepilotChatHookState, SagepilotChatProvider, type SagepilotChatProviderProps, type SagepilotConversationCreatedEvent, type SagepilotDeviceInfo, type SagepilotDeviceInfoAdapter, type SagepilotIdentifyResult, type SagepilotIdentity, type SagepilotIdentityState, 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 SagepilotSessionState, type SagepilotTokenStorage, type SagepilotUnreadState, createAsyncStorageCacheStorage, createKeychainTokenStorage, 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 };