@dvai-bridge/android 4.0.0 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,165 +1,252 @@
1
- // Phase 3D umbrella module — co.deepvoiceai:dvai-bridge AAR.
2
- //
3
- // Depends on the four Android cores (shared / llama / mediapipe / litert)
4
- // via Maven coordinates (mavenLocal in dev, GitHub Packages in prod). The
5
- // public DVAIBridge Kotlin object wires those backends behind a single
6
- // 8-method API surface that mirrors the iOS DVAIBridge SDK.
7
-
8
- buildscript {
9
- ext {
10
- kotlinVersion = '2.3.21'
11
- }
12
- repositories {
13
- google()
14
- mavenCentral()
15
- }
16
- dependencies {
17
- classpath 'com.android.tools.build:gradle:9.2.0'
18
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
19
- }
20
- }
21
-
22
- ext {
23
- junitVersion = '4.13.2'
24
- androidxAppCompatVersion = '1.7.1'
25
- coroutinesVersion = '1.10.2'
26
- ktorVersion = '2.3.13'
27
- }
28
-
29
- allprojects {
30
- repositories {
31
- google()
32
- mavenCentral()
33
- // Phase 3D: cores resolve here in dev (after `bash scripts/android-publish-local.sh`).
34
- // Production publishes resolve from GitHub Packages.
35
- mavenLocal()
36
- }
37
- }
38
-
39
- apply plugin: 'com.android.library'
40
- apply plugin: 'maven-publish'
41
-
42
- android {
43
- namespace 'co.deepvoiceai.bridge'
44
- // Phase 2 Task 3 (examples) library fix: see shared-core/android/build.gradle
45
- // for the rationale — `-PcompileSdkOverride=35` lets Windows hosts sidestep
46
- // an AGP 9.2.0 parseLocalResources bug against android-36's public-final.xml.
47
- compileSdk(project.findProperty('compileSdkOverride')?.toInteger() ?: 36)
48
-
49
- defaultConfig {
50
- minSdk 24
51
- targetSdk(project.findProperty('compileSdkOverride')?.toInteger() ?: 36)
52
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
53
-
54
- // Phase 3D Task 17: integration tests read SMOKE_* env vars via
55
- // BuildConfig fields. Empty defaults make the tests self-skip via
56
- // `Assume.assumeFalse(... isEmpty())` when the env vars aren't set.
57
- // CI workflows export the env vars from repo secrets; local dev
58
- // can `export SMOKE_MODEL_URL=... && ./gradlew connectedAndroidTest`.
59
- buildConfigField "String", "SMOKE_MODEL_URL",
60
- "\"${System.getenv('SMOKE_MODEL_URL') ?: ''}\""
61
- buildConfigField "String", "SMOKE_MODEL_SHA256",
62
- "\"${System.getenv('SMOKE_MODEL_SHA256') ?: ''}\""
63
- buildConfigField "String", "SMOKE_MEDIAPIPE_MODEL_URL",
64
- "\"${System.getenv('SMOKE_MEDIAPIPE_MODEL_URL') ?: ''}\""
65
- buildConfigField "String", "SMOKE_MEDIAPIPE_MODEL_SHA256",
66
- "\"${System.getenv('SMOKE_MEDIAPIPE_MODEL_SHA256') ?: ''}\""
67
- buildConfigField "String", "SMOKE_LITERT_MODEL_URL",
68
- "\"${System.getenv('SMOKE_LITERT_MODEL_URL') ?: ''}\""
69
- buildConfigField "String", "SMOKE_LITERT_MODEL_SHA256",
70
- "\"${System.getenv('SMOKE_LITERT_MODEL_SHA256') ?: ''}\""
71
- buildConfigField "String", "SMOKE_LITERT_TOKENIZER_URL",
72
- "\"${System.getenv('SMOKE_LITERT_TOKENIZER_URL') ?: ''}\""
73
- }
74
-
75
- buildFeatures {
76
- buildConfig = true
77
- }
78
-
79
- compileOptions {
80
- sourceCompatibility JavaVersion.VERSION_17
81
- targetCompatibility JavaVersion.VERSION_17
82
- }
83
- testOptions {
84
- unitTests.includeAndroidResources = true
85
- }
86
-
87
- publishing {
88
- singleVariant('release') {
89
- withSourcesJar()
90
- }
91
- }
92
- }
93
-
94
- kotlin {
95
- compilerOptions {
96
- jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
97
- }
98
- }
99
-
100
- dependencies {
101
- // Cores — each declared as `api` so consumer apps that pull just the
102
- // umbrella see the underlying types (BackendKind, HandlerContext, etc.)
103
- // without re-declaring the deps.
104
- api "co.deepvoiceai:android-shared-core:$dvaiBridgeVersion"
105
- api "co.deepvoiceai:android-llama-core:$dvaiBridgeVersion"
106
- api "co.deepvoiceai:android-mediapipe-core:$dvaiBridgeVersion"
107
- api "co.deepvoiceai:android-litert-core:$dvaiBridgeVersion"
108
-
109
- implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
110
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
111
- implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0'
112
-
113
- // v3.3 — offline JWT license validator. nimbus-jose-jwt is the
114
- // widely-vetted JOSE implementation for the JVM; we use ES256 only
115
- // (refuse alg=none / HS256, classic algorithm-confusion defense).
116
- // See android/src/main/java/co/deepvoiceai/bridge/license/.
117
- implementation 'com.nimbusds:nimbus-jose-jwt:10.5'
118
-
119
- // Phase 5 (v3.2)outgoing-offload pre-routing proxy. The proxy
120
- // binds the public port; the underlying native backend binds an
121
- // internal loopback port. Per-request the proxy decides local-vs-
122
- // forward and either streams from local or HMAC-signs + forwards
123
- // to a paired peer. CIO engine is the smallest of the Ktor engine
124
- // options (~500 KB AAR contribution vs Netty's ~3 MB).
125
- implementation "io.ktor:ktor-server-core:$ktorVersion"
126
- implementation "io.ktor:ktor-server-cio:$ktorVersion"
127
- implementation "io.ktor:ktor-server-status-pages:$ktorVersion"
128
- implementation "io.ktor:ktor-client-core:$ktorVersion"
129
- implementation "io.ktor:ktor-client-cio:$ktorVersion"
130
-
131
- testImplementation "junit:junit:$junitVersion"
132
- testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
133
- testImplementation 'org.robolectric:robolectric:4.16.1'
134
- testImplementation "io.ktor:ktor-server-test-host:$ktorVersion"
135
- testImplementation 'com.squareup.okhttp3:mockwebserver:5.3.2'
136
-
137
- androidTestImplementation 'androidx.test:runner:1.7.0'
138
- androidTestImplementation 'androidx.test.ext:junit:1.3.0'
139
- androidTestImplementation 'com.squareup.okhttp3:okhttp:5.3.2'
140
- }
141
-
142
- // Phase 3D Task 4: copy-paste publishing block (see llama-core for the
143
- // rationale on why each Gradle root has its own copy).
144
- afterEvaluate {
145
- publishing {
146
- publications {
147
- release(MavenPublication) {
148
- groupId = 'co.deepvoiceai'
149
- artifactId = 'dvai-bridge'
150
- version = (project.findProperty('dvaiBridgeVersion') ?: '4.0.0').toString()
151
- from components.release
152
- }
153
- }
154
- repositories {
155
- maven {
156
- name = 'GitHubPackages'
157
- url = uri('https://maven.pkg.github.com/dvai-global/dvai-bridge')
158
- credentials {
159
- username = project.findProperty('gpr.user') ?: System.getenv('GITHUB_ACTOR')
160
- password = project.findProperty('gpr.key') ?: System.getenv('GITHUB_TOKEN')
161
- }
162
- }
163
- }
164
- }
165
- }
1
+ // Phase 3D umbrella module — co.deepvoiceai:dvai-bridge AAR.
2
+ //
3
+ // Depends on the four Android cores (shared / llama / mediapipe / litert)
4
+ // via Maven coordinates (mavenLocal in dev, GitHub Packages in prod). The
5
+ // public DVAIBridge Kotlin object wires those backends behind a single
6
+ // 8-method API surface that mirrors the iOS DVAIBridge SDK.
7
+
8
+ buildscript {
9
+ ext {
10
+ kotlinVersion = '2.3.21'
11
+ }
12
+ repositories {
13
+ google()
14
+ mavenCentral()
15
+ gradlePluginPortal()
16
+ }
17
+ dependencies {
18
+ classpath 'com.android.tools.build:gradle:9.2.0'
19
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
20
+ // Maven Central publishing — wraps maven-publish + signing + the
21
+ // Central Portal upload API into one config block. See the
22
+ // mavenPublishing { ... } block below.
23
+ classpath 'com.vanniktech:gradle-maven-publish-plugin:0.36.0'
24
+ }
25
+ }
26
+
27
+ ext {
28
+ junitVersion = '4.13.2'
29
+ androidxAppCompatVersion = '1.7.1'
30
+ coroutinesVersion = '1.10.2'
31
+ ktorVersion = '2.3.13'
32
+ }
33
+
34
+ allprojects {
35
+ repositories {
36
+ google()
37
+ mavenCentral()
38
+ // Phase 3D: cores resolve here in dev (after `bash scripts/android-publish-local.sh`).
39
+ // Production publishes resolve from GitHub Packages.
40
+ mavenLocal()
41
+ }
42
+ }
43
+
44
+ apply plugin: 'com.android.library'
45
+ apply plugin: 'com.vanniktech.maven.publish'
46
+
47
+ android {
48
+ namespace 'co.deepvoiceai.bridge'
49
+ // Phase 2 Task 3 (examples) library fix: see shared-core/android/build.gradle
50
+ // for the rationale — `-PcompileSdkOverride=35` lets Windows hosts sidestep
51
+ // an AGP 9.2.0 parseLocalResources bug against android-36's public-final.xml.
52
+ compileSdk(project.findProperty('compileSdkOverride')?.toInteger() ?: 36)
53
+
54
+ defaultConfig {
55
+ minSdk 24
56
+ targetSdk(project.findProperty('compileSdkOverride')?.toInteger() ?: 36)
57
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
58
+
59
+ // Phase 3D Task 17: integration tests read SMOKE_* env vars via
60
+ // BuildConfig fields. Empty defaults make the tests self-skip via
61
+ // `Assume.assumeFalse(... isEmpty())` when the env vars aren't set.
62
+ // CI workflows export the env vars from repo secrets; local dev
63
+ // can `export SMOKE_MODEL_URL=... && ./gradlew connectedAndroidTest`.
64
+ buildConfigField "String", "SMOKE_MODEL_URL",
65
+ "\"${System.getenv('SMOKE_MODEL_URL') ?: ''}\""
66
+ buildConfigField "String", "SMOKE_MODEL_SHA256",
67
+ "\"${System.getenv('SMOKE_MODEL_SHA256') ?: ''}\""
68
+ buildConfigField "String", "SMOKE_MEDIAPIPE_MODEL_URL",
69
+ "\"${System.getenv('SMOKE_MEDIAPIPE_MODEL_URL') ?: ''}\""
70
+ buildConfigField "String", "SMOKE_MEDIAPIPE_MODEL_SHA256",
71
+ "\"${System.getenv('SMOKE_MEDIAPIPE_MODEL_SHA256') ?: ''}\""
72
+ buildConfigField "String", "SMOKE_LITERT_MODEL_URL",
73
+ "\"${System.getenv('SMOKE_LITERT_MODEL_URL') ?: ''}\""
74
+ buildConfigField "String", "SMOKE_LITERT_MODEL_SHA256",
75
+ "\"${System.getenv('SMOKE_LITERT_MODEL_SHA256') ?: ''}\""
76
+ buildConfigField "String", "SMOKE_LITERT_TOKENIZER_URL",
77
+ "\"${System.getenv('SMOKE_LITERT_TOKENIZER_URL') ?: ''}\""
78
+ }
79
+
80
+ buildFeatures {
81
+ buildConfig = true
82
+ }
83
+
84
+ compileOptions {
85
+ sourceCompatibility JavaVersion.VERSION_17
86
+ targetCompatibility JavaVersion.VERSION_17
87
+ }
88
+ testOptions {
89
+ unitTests.includeAndroidResources = true
90
+ }
91
+
92
+ // NB: don't add `publishing { singleVariant('release') { withSourcesJar() } }`
93
+ // here. The vanniktech.maven.publish plugin auto-configures Android
94
+ // single-variant publication + sources + javadoc jars on its own; a
95
+ // manual singleVariant block collides with "Using singleVariant publishing
96
+ // DSL multiple times to publish variant 'release' to component 'release'
97
+ // is not allowed."
98
+ }
99
+
100
+ kotlin {
101
+ compilerOptions {
102
+ jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
103
+ }
104
+ }
105
+
106
+ dependencies {
107
+ // Cores — each declared as `api` so consumer apps that pull just the
108
+ // umbrella see the underlying types (BackendKind, HandlerContext, etc.)
109
+ // without re-declaring the deps.
110
+ api "co.deepvoiceai:android-shared-core:$dvaiBridgeVersion"
111
+ api "co.deepvoiceai:android-llama-core:$dvaiBridgeVersion"
112
+ api "co.deepvoiceai:android-mediapipe-core:$dvaiBridgeVersion"
113
+ api "co.deepvoiceai:android-litert-core:$dvaiBridgeVersion"
114
+
115
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
116
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
117
+ implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0'
118
+
119
+ // v3.3offline JWT license validator. nimbus-jose-jwt is the
120
+ // widely-vetted JOSE implementation for the JVM; we use ES256 only
121
+ // (refuse alg=none / HS256, classic algorithm-confusion defense).
122
+ // See android/src/main/java/co/deepvoiceai/bridge/license/.
123
+ implementation 'com.nimbusds:nimbus-jose-jwt:10.5'
124
+
125
+ // Phase 5 (v3.2) — outgoing-offload pre-routing proxy. The proxy
126
+ // binds the public port; the underlying native backend binds an
127
+ // internal loopback port. Per-request the proxy decides local-vs-
128
+ // forward and either streams from local or HMAC-signs + forwards
129
+ // to a paired peer. CIO engine is the smallest of the Ktor engine
130
+ // options (~500 KB AAR contribution vs Netty's ~3 MB).
131
+ implementation "io.ktor:ktor-server-core:$ktorVersion"
132
+ implementation "io.ktor:ktor-server-cio:$ktorVersion"
133
+ implementation "io.ktor:ktor-server-status-pages:$ktorVersion"
134
+ implementation "io.ktor:ktor-client-core:$ktorVersion"
135
+ implementation "io.ktor:ktor-client-cio:$ktorVersion"
136
+
137
+ testImplementation "junit:junit:$junitVersion"
138
+ testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
139
+ testImplementation 'org.robolectric:robolectric:4.16.1'
140
+ testImplementation "io.ktor:ktor-server-test-host:$ktorVersion"
141
+ testImplementation 'com.squareup.okhttp3:mockwebserver:5.3.2'
142
+
143
+ androidTestImplementation 'androidx.test:runner:1.7.0'
144
+ androidTestImplementation 'androidx.test.ext:junit:1.3.0'
145
+ androidTestImplementation 'com.squareup.okhttp3:okhttp:5.3.2'
146
+ }
147
+
148
+ // Maven Central publishing via Sonatype Central Portal. See the
149
+ // PUBLISHING-MAVEN-CENTRAL.md note at the repo root (gitignored — your
150
+ // operator playbook) for credentials + the per-release sequence.
151
+ //
152
+ // Coordinates and POM metadata live here; the actual publish is driven
153
+ // by `./gradlew publishAndReleaseToMavenCentral` (auto-release on
154
+ // validation passing) or `publishToMavenLocal` for a dry-run that
155
+ // writes the artifacts + signatures to ~/.m2/repository/.
156
+ mavenPublishing {
157
+ // AGP 9.2.0 bundles a Dokka build whose ASM can't read Kotlin 2.3.21's
158
+ // sealed-class bytecode (`PermittedSubclasses requires ASM9`) when it
159
+ // tries to resolve the api jars of the cores during the
160
+ // `javaDocReleaseGeneration` task. We bypass AGP's Dokka javadoc and
161
+ // ship an empty javadoc.jar instead — Sonatype Central Portal validates
162
+ // the artifact's presence, not that it is populated. Re-enable once
163
+ // AGP upgrades its bundled ASM.
164
+ configure(new com.vanniktech.maven.publish.AndroidSingleVariantLibrary(
165
+ /* variant = */ 'release',
166
+ /* sourcesJar = */ true,
167
+ /* publishJavadoc = */ false,
168
+ ))
169
+
170
+ publishToMavenCentral(true)
171
+ // Sign only when the PGP key is configured — see shared-core for rationale.
172
+ if (project.findProperty('signingInMemoryKey') ||
173
+ System.getenv('ORG_GRADLE_PROJECT_signingInMemoryKey')) {
174
+ signAllPublications()
175
+ }
176
+
177
+ coordinates(
178
+ 'co.deepvoiceai',
179
+ 'dvai-bridge',
180
+ (project.findProperty('dvaiBridgeVersion') ?: '4.0.2').toString(),
181
+ )
182
+
183
+ pom {
184
+ name = 'DVAI Bridge Android'
185
+ description = 'DVAI Bridge Android SDK — embeds an OpenAI-compatible HTTP server ' +
186
+ 'inside your Android app process (127.0.0.1:38883, /v1/chat/completions, ' +
187
+ '/v1/embeddings, /v1/models, SSE streaming). Any OpenAI client speaks to it ' +
188
+ '(LangChain, autogen, crewai, the OpenAI SDK). ' +
189
+ 'Single 8-method Kotlin API surface fronting llama.cpp, MediaPipe / LiteRT-LM ' +
190
+ 'and bare-LiteRT backends; engine selected at runtime by device capability. ' +
191
+ 'Same OpenAI wire as the dvai-bridge family on iOS (CocoaPods), Flutter (pub.dev), ' +
192
+ 'React Native + Capacitor + browser + Node (npm), and .NET MAUI (NuGet) — ' +
193
+ 'cross-platform agentic apps stop being a per-platform porting exercise. ' +
194
+ 'Mirrors the iOS DVAIBridge SDK; pulls in the four cores ' +
195
+ '(android-shared-core / android-llama-core / android-mediapipe-core / ' +
196
+ 'android-litert-core) transitively. Unlike Ollama / LM Studio / llama-server ' +
197
+ 'which the end user installs, this library ships INSIDE your app. ' +
198
+ 'Docs + architecture: https://bridge.deepvoiceai.co'
199
+ inceptionYear = '2026'
200
+ url = 'https://github.com/dvai-global/dvai-bridge'
201
+
202
+ licenses {
203
+ license {
204
+ name = 'DVAI Bridge Community Licence v1.0'
205
+ url = 'https://bridge.deepvoiceai.co/licensing'
206
+ distribution = 'repo'
207
+ }
208
+ }
209
+ developers {
210
+ developer {
211
+ id = 'deepvoiceai'
212
+ name = 'Deep Voice AI Limited'
213
+ email = 'info@deepvoiceai.co'
214
+ organization = 'Deep Voice AI Limited'
215
+ organizationUrl = 'https://deepvoiceai.co'
216
+ }
217
+ }
218
+ scm {
219
+ url = 'https://github.com/dvai-global/dvai-bridge'
220
+ connection = 'scm:git:git://github.com/dvai-global/dvai-bridge.git'
221
+ developerConnection = 'scm:git:ssh://github.com:dvai-global/dvai-bridge.git'
222
+ }
223
+ }
224
+ }
225
+
226
+ // Switch Gradle signing from BouncyCastle-based in-memory mode to
227
+ // shell-out-to-gpg mode. The in-memory path can't parse GnuPG 2.4+
228
+ // keys that carry pref-aead-algos (subpkt 34) in their user-ID
229
+ // self-signature — `gpg --list-packets` of our v4.0.1 signing key
230
+ // confirms that subpacket is present. useGpgCmd() shells out to the
231
+ // local gpg binary (imported into the runner's keyring by the
232
+ // publish.yml workflow), which can parse it. Passphrase is supplied
233
+ // via ~/.gradle/gradle.properties (signing.gnupg.passphrase).
234
+ if (project.findProperty('signing.gnupg.keyName') ||
235
+ System.getenv('ORG_GRADLE_PROJECT_USE_GPG_CMD_SIGNING') == 'true') {
236
+ signing {
237
+ useGpgCmd()
238
+ }
239
+ }
240
+
241
+ // Empty javadoc.jar to satisfy Sonatype Central Portal validation while
242
+ // AGP 9.2.0's bundled Dokka is incompatible with our sealed-class bytecode.
243
+ // See the `configure(AndroidSingleVariantLibrary ...)` comment above.
244
+ tasks.register('emptyJavadocJar', Jar) {
245
+ archiveClassifier.set('javadoc')
246
+ }
247
+
248
+ afterEvaluate {
249
+ publishing.publications.withType(MavenPublication).configureEach { pub ->
250
+ pub.artifact(tasks.named('emptyJavadocJar'))
251
+ }
252
+ }
@@ -2,4 +2,4 @@ android.useAndroidX=true
2
2
  kotlin.code.style=official
3
3
  android.nonTransitiveRClass=true
4
4
  org.gradle.jvmargs=-Xmx4096m
5
- dvaiBridgeVersion=4.0.0
5
+ dvaiBridgeVersion=4.0.2
@@ -1,39 +1,39 @@
1
- package co.deepvoiceai.bridge
2
-
3
- import co.deepvoiceai.bridge.license.LicenseStatus
4
-
5
- /**
6
- * Result of a successful [DVAIBridge.start] call. Mirrors the iOS DVAIBridge
7
- * `BoundServer` struct + the Capacitor JS shim's StartResult.
8
- *
9
- * @param baseUrl Full base URL of the embedded OpenAI-compatible server,
10
- * including the `/v1` suffix. Example:
11
- * `"http://127.0.0.1:38883/v1"`.
12
- * @param port Port the HTTP server actually bound to (port-fallback may
13
- * have moved it past [StartOptions.httpBasePort]).
14
- * @param backend The backend that actually loaded — useful when [StartOptions]
15
- * set [BackendKind.Auto] and the consumer wants to know what
16
- * was picked.
17
- * @param modelId Stable identifier for the loaded model. Surfaced in the
18
- * `model` field of every OpenAI response.
19
- * @param licenseStatus v3.3 — outcome of the offline JWT license check that
20
- * ran during [DVAIBridge.start]. Null when the validator
21
- * didn't run (legacy callers, missing context). Production
22
- * starts always carry a non-null status here; failure modes
23
- * throw before this struct is produced.
24
- */
25
- data class BoundServer(
26
- val baseUrl: String,
27
- val port: Int,
28
- val backend: BackendKind,
29
- val modelId: String,
30
- val licenseStatus: LicenseStatus? = null,
31
- )
32
-
33
- /** Read-only status snapshot returned by [DVAIBridge.status]. */
34
- data class StatusInfo(
35
- val running: Boolean,
36
- val baseUrl: String? = null,
37
- val backend: BackendKind? = null,
38
- val modelId: String? = null,
39
- )
1
+ package co.deepvoiceai.bridge
2
+
3
+ import co.deepvoiceai.bridge.license.LicenseStatus
4
+
5
+ /**
6
+ * Result of a successful [DVAIBridge.start] call. Mirrors the iOS DVAIBridge
7
+ * `BoundServer` struct + the Capacitor JS shim's StartResult.
8
+ *
9
+ * @param baseUrl Full base URL of the embedded OpenAI-compatible server,
10
+ * including the `/v1` suffix. Example:
11
+ * `"http://127.0.0.1:38883/v1"`.
12
+ * @param port Port the HTTP server actually bound to (port-fallback may
13
+ * have moved it past [StartOptions.httpBasePort]).
14
+ * @param backend The backend that actually loaded — useful when [StartOptions]
15
+ * set [BackendKind.Auto] and the consumer wants to know what
16
+ * was picked.
17
+ * @param modelId Stable identifier for the loaded model. Surfaced in the
18
+ * `model` field of every OpenAI response.
19
+ * @param licenseStatus v3.3 — outcome of the offline JWT license check that
20
+ * ran during [DVAIBridge.start]. Null when the validator
21
+ * didn't run (legacy callers, missing context). Production
22
+ * starts always carry a non-null status here; failure modes
23
+ * throw before this struct is produced.
24
+ */
25
+ data class BoundServer(
26
+ val baseUrl: String,
27
+ val port: Int,
28
+ val backend: BackendKind,
29
+ val modelId: String,
30
+ val licenseStatus: LicenseStatus? = null,
31
+ )
32
+
33
+ /** Read-only status snapshot returned by [DVAIBridge.status]. */
34
+ data class StatusInfo(
35
+ val running: Boolean,
36
+ val baseUrl: String? = null,
37
+ val backend: BackendKind? = null,
38
+ val modelId: String? = null,
39
+ )