@dvai-bridge/android 4.0.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.
Files changed (30) hide show
  1. package/LICENSE +51 -0
  2. package/README.md +199 -0
  3. package/android/build.gradle +165 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/settings.gradle +1 -0
  6. package/android/src/androidTest/java/co/deepvoiceai/bridge/RealModelIntegrationTest.kt +162 -0
  7. package/android/src/main/AndroidManifest.xml +3 -0
  8. package/android/src/main/java/co/deepvoiceai/bridge/BackendKind.kt +21 -0
  9. package/android/src/main/java/co/deepvoiceai/bridge/BackendSelector.kt +28 -0
  10. package/android/src/main/java/co/deepvoiceai/bridge/BoundServer.kt +39 -0
  11. package/android/src/main/java/co/deepvoiceai/bridge/DVAIBridge.kt +642 -0
  12. package/android/src/main/java/co/deepvoiceai/bridge/DVAIBridgeConfig.kt +119 -0
  13. package/android/src/main/java/co/deepvoiceai/bridge/DVAIBridgeError.kt +51 -0
  14. package/android/src/main/java/co/deepvoiceai/bridge/OffloadProxy.kt +574 -0
  15. package/android/src/main/java/co/deepvoiceai/bridge/ProgressBroadcaster.kt +55 -0
  16. package/android/src/main/java/co/deepvoiceai/bridge/ProgressEvent.kt +25 -0
  17. package/android/src/main/java/co/deepvoiceai/bridge/ReactiveState.kt +60 -0
  18. package/android/src/main/java/co/deepvoiceai/bridge/license/Audience.kt +134 -0
  19. package/android/src/main/java/co/deepvoiceai/bridge/license/Discovery.kt +146 -0
  20. package/android/src/main/java/co/deepvoiceai/bridge/license/LicenseTypes.kt +158 -0
  21. package/android/src/main/java/co/deepvoiceai/bridge/license/LicenseValidator.kt +400 -0
  22. package/android/src/main/java/co/deepvoiceai/bridge/license/PublicKeys.kt +91 -0
  23. package/android/src/test/java/co/deepvoiceai/bridge/BackendSelectorTest.kt +76 -0
  24. package/android/src/test/java/co/deepvoiceai/bridge/CapabilityPrecheckTest.kt +117 -0
  25. package/android/src/test/java/co/deepvoiceai/bridge/DVAIBridgeAPIShapeTest.kt +86 -0
  26. package/android/src/test/java/co/deepvoiceai/bridge/OffloadProxyDecisionTest.kt +144 -0
  27. package/android/src/test/java/co/deepvoiceai/bridge/OffloadProxyForwardingTest.kt +327 -0
  28. package/android/src/test/java/co/deepvoiceai/bridge/ProgressBroadcasterTest.kt +56 -0
  29. package/android/src/test/java/co/deepvoiceai/bridge/license/LicenseValidatorTest.kt +539 -0
  30. package/package.json +19 -0
@@ -0,0 +1,119 @@
1
+ package co.deepvoiceai.bridge
2
+
3
+ import co.deepvoiceai.bridge.shared.core.CorsConfig
4
+ import co.deepvoiceai.bridge.shared.core.offload.OffloadConfig
5
+
6
+ /**
7
+ * Options accepted by [DVAIBridge.start]. Mirrors the iOS DVAIBridge
8
+ * `StartOptions` struct + the Capacitor JS shim's StartOptions.
9
+ *
10
+ * @param backend Which backend to use. [BackendKind.Auto] resolves
11
+ * via [BackendSelector] at start-time.
12
+ * @param modelPath Filesystem path to the model checkpoint. Required
13
+ * for Llama (.gguf), MediaPipe (.task), and LiteRT
14
+ * (.tflite / .litertlm) backends.
15
+ * @param tokenizerPath Filesystem path to a directory containing
16
+ * tokenizer.json (and optional tokenizer_config.json).
17
+ * Required for the LiteRT backend; ignored otherwise.
18
+ * @param mmprojPath Optional multimodal projector path for Llama
19
+ * (vision/audio LLMs).
20
+ * @param chatTemplate Optional Jinja chat template override (Llama backend
21
+ * only). Falls back to the model's bundled template.
22
+ * @param gpuLayers Llama backend: number of transformer layers to
23
+ * offload to GPU (Vulkan / OpenCL on supported
24
+ * devices). 99 = all layers, 0 = CPU only. Ignored
25
+ * by other backends.
26
+ * @param contextSize Context window in tokens. Defaults to 2048.
27
+ * @param threads CPU thread count for the inference loop. Llama
28
+ * uses this directly; LiteRT/MediaPipe pick their
29
+ * own threading by default.
30
+ * @param embeddingMode Llama backend: open the model in
31
+ * embedding-extraction mode rather than completion
32
+ * mode. Mutually exclusive with chat completion.
33
+ * @param visionEnabled MediaPipe backend: open the LiteRT-LM EngineConfig
34
+ * with `visionBackend` enabled.
35
+ * @param temperature LiteRT backend: sampling temperature (0 = greedy).
36
+ * @param topP LiteRT backend: nucleus sampling cutoff (1 = disabled).
37
+ * @param topK LiteRT backend: top-K truncation (0 = disabled).
38
+ * @param maxNewTokens LiteRT backend: hard cap on tokens generated per request.
39
+ * @param httpBasePort First port the HTTP server tries to bind. Defaults
40
+ * to 38883 (matches the rest of the dvai-bridge family).
41
+ * @param httpMaxPortAttempts Number of consecutive ports to try before giving
42
+ * up. Defaults to 16.
43
+ * @param corsOrigin CORS allow-origin policy. See [CorsConfig].
44
+ * Defaults to wildcard.
45
+ * @param modelId Optional override for the model id surfaced via
46
+ * `/v1/models`. Defaults to the file name minus
47
+ * extension when null.
48
+ */
49
+ data class StartOptions(
50
+ val backend: BackendKind = BackendKind.Auto,
51
+ val modelPath: String? = null,
52
+ val tokenizerPath: String? = null,
53
+ val mmprojPath: String? = null,
54
+ val chatTemplate: String? = null,
55
+ val gpuLayers: Int = 99,
56
+ val contextSize: Int = 2048,
57
+ val threads: Int = 4,
58
+ val embeddingMode: Boolean = false,
59
+ val visionEnabled: Boolean = false,
60
+ val temperature: Float = 0f,
61
+ val topP: Float = 1f,
62
+ val topK: Int = 0,
63
+ val maxNewTokens: Int = 512,
64
+ val httpBasePort: Int = 38883,
65
+ val httpMaxPortAttempts: Int = 16,
66
+ val corsOrigin: CorsConfig = CorsConfig.Wildcard,
67
+ val modelId: String? = null,
68
+ /**
69
+ * Phase 3 — opt-in distributed inference / device offload. When
70
+ * `enabled = true`, [DVAIBridge] spins up an [NsdDiscovery] +
71
+ * [NsdAdvertiser], a [CapabilityCache], and a [PairingPolicy]
72
+ * whose `requests` Flow is exposed via `DVAIBridge.pairingRequests`
73
+ * for the host UI. Default null = behave exactly like v2.x.
74
+ */
75
+ val offload: OffloadConfig? = null,
76
+ /**
77
+ * v3.3 — offline JWT license validator config. When non-null, the
78
+ * SDK loads the JWT from this filesystem path and verifies it at
79
+ * startup. Auto-discovery (assets/, res/raw/, filesDir/) runs when
80
+ * BOTH this AND [licenseToken] are null.
81
+ *
82
+ * Production Android builds without a valid license throw
83
+ * [co.deepvoiceai.bridge.license.LicenseRequiredError] from
84
+ * [DVAIBridge.start]. Debug builds (`hostBuildConfigDebug = true`
85
+ * or `ApplicationInfo.FLAG_DEBUGGABLE`) skip validation entirely.
86
+ */
87
+ val licenseKeyPath: String? = null,
88
+ /**
89
+ * v3.3 — inline JWT license token. Overrides every other discovery
90
+ * source. Useful for CI / test contexts where reading a file isn't
91
+ * practical and operators inject via env var or build config.
92
+ */
93
+ val licenseToken: String? = null,
94
+ /**
95
+ * v3.3 — the host app's `BuildConfig.DEBUG` value, passed through to
96
+ * the license validator's dev-mode bypass. When true the validator
97
+ * returns `FreeDev` without trying to verify anything; when false (or
98
+ * null) the validator falls back to `ApplicationInfo.FLAG_DEBUGGABLE`.
99
+ *
100
+ * Pass `BuildConfig.DEBUG` from your app module here — the validator
101
+ * lives in this library module whose own `BuildConfig.DEBUG` never
102
+ * reflects the host app's state.
103
+ */
104
+ val hostBuildConfigDebug: Boolean? = null,
105
+ )
106
+
107
+ /** Options for [DVAIBridge.downloadModel]. */
108
+ data class DownloadOptions(
109
+ val url: String,
110
+ val sha256: String,
111
+ val destFilename: String,
112
+ )
113
+
114
+ /** Result of a successful [DVAIBridge.downloadModel] call. */
115
+ data class DownloadResult(
116
+ val path: String,
117
+ val sha256: String,
118
+ val sizeBytes: Long,
119
+ )
@@ -0,0 +1,51 @@
1
+ package co.deepvoiceai.bridge
2
+
3
+ /**
4
+ * Public error surface for the DVAIBridge Android SDK. Mirrors iOS
5
+ * `DVAIBridgeError` 1:1 in case shape so cross-platform consumers see
6
+ * identical failure modes.
7
+ *
8
+ * Uses a sealed Exception hierarchy rather than a sealed-class +
9
+ * data-classes pair so consumers can `try { ... } catch (e:
10
+ * DVAIBridgeError.ModelLoadFailed) { ... }`.
11
+ */
12
+ sealed class DVAIBridgeError(message: String, cause: Throwable? = null) : Exception(message, cause) {
13
+ /** [DVAIBridge.start] called twice without a [DVAIBridge.stop] in between. */
14
+ class AlreadyStarted(
15
+ val currentBackend: BackendKind,
16
+ val baseUrl: String,
17
+ ) : DVAIBridgeError(
18
+ "DVAIBridge is already running ($currentBackend at $baseUrl). Call stop() first.",
19
+ )
20
+
21
+ /** [StartOptions] is malformed (e.g. unsupported modelPath extension under Auto). */
22
+ class ConfigurationInvalid(reason: String) : DVAIBridgeError("Configuration invalid: $reason")
23
+
24
+ /** Backend rejected the model file or tokenizer at load time. */
25
+ class ModelLoadFailed(reason: String, cause: Throwable? = null) : DVAIBridgeError("Model load failed: $reason", cause)
26
+
27
+ /**
28
+ * Backend can't run in the current environment (e.g. asking for
29
+ * MediaPipe on a device too old for LiteRT-LM, or asking for the
30
+ * upcoming `.foundation` Apple-Models backend on Android — it doesn't
31
+ * exist). The umbrella throws this when [BackendSelector] resolves to
32
+ * something the runtime doesn't support.
33
+ */
34
+ class BackendUnavailable(val backend: BackendKind, reason: String) : DVAIBridgeError(
35
+ "Backend $backend is not available: $reason",
36
+ )
37
+
38
+ /** Generic backend failure (HTTP server bind, model inference exception). */
39
+ class BackendError(underlying: Throwable) : DVAIBridgeError(
40
+ "Backend error: ${underlying.message ?: underlying::class.qualifiedName}",
41
+ underlying,
42
+ )
43
+
44
+ /** [DVAIBridge.downloadModel] downloaded the file but the sha256 didn't match. */
45
+ class ChecksumMismatch(expected: String, actual: String) : DVAIBridgeError(
46
+ "Downloaded file sha256 mismatch: expected $expected, got $actual",
47
+ )
48
+
49
+ /** [DVAIBridge.downloadModel] failed before completion (network, disk, HTTP error). */
50
+ class DownloadFailed(reason: String, cause: Throwable? = null) : DVAIBridgeError("Download failed: $reason", cause)
51
+ }