@independo/capacitor-inderun 1.0.1-dev.1 → 1.1.0-dev.10

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 (33) hide show
  1. package/Package.swift +19 -2
  2. package/README.md +190 -13
  3. package/android/build.gradle.kts +12 -6
  4. package/android/settings.gradle +7 -0
  5. package/android/src/main/kotlin/app/independo/inderun/capacitor/IndeRunCapacitorPlugin.kt +154 -0
  6. package/android/src/main/kotlin/app/independo/inderun/capacitor/IndeRunSerializer.kt +203 -1
  7. package/android/src/main/kotlin/app/independo/inderun/capacitor/IndeRunStreamRegistry.kt +118 -0
  8. package/android/src/test/kotlin/app/independo/inderun/capacitor/IndeRunSerializerCapabilitiesTest.kt +253 -0
  9. package/android/src/test/kotlin/app/independo/inderun/capacitor/IndeRunSerializerStreamTest.kt +226 -0
  10. package/android/src/test/kotlin/app/independo/inderun/capacitor/IndeRunSerializerTest.kt +79 -0
  11. package/android/src/test/kotlin/app/independo/inderun/capacitor/IndeRunStreamRegistryTest.kt +163 -0
  12. package/dist/definitions.d.ts +175 -1
  13. package/dist/definitions.d.ts.map +1 -1
  14. package/dist/errors.d.ts +19 -0
  15. package/dist/errors.d.ts.map +1 -0
  16. package/dist/errors.js +36 -0
  17. package/dist/index.d.ts +17 -4
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +44 -13
  20. package/dist/streaming.d.ts +17 -0
  21. package/dist/streaming.d.ts.map +1 -0
  22. package/dist/streaming.js +303 -0
  23. package/dist/web.d.ts +18 -2
  24. package/dist/web.d.ts.map +1 -1
  25. package/dist/web.js +136 -7
  26. package/ios/Sources/IndeRunCapacitorPlugin/IndeRunCapacitorBridge.swift +181 -0
  27. package/ios/Sources/IndeRunCapacitorPlugin/IndeRunCapacitorPlugin.swift +93 -3
  28. package/ios/Sources/IndeRunCapacitorPlugin/IndeRunCapacitorStreamRegistry.swift +121 -0
  29. package/ios/Tests/IndeRunCapacitorTests/IndeRunCapacitorBridgeTests.swift +234 -0
  30. package/ios/Tests/IndeRunCapacitorTests/IndeRunCapacitorStreamCodecTests.swift +178 -0
  31. package/ios/Tests/IndeRunCapacitorTests/IndeRunCapacitorStreamPumpTests.swift +215 -0
  32. package/ios/Tests/IndeRunCapacitorTests/IndeRunCapacitorStreamRegistryTests.swift +154 -0
  33. package/package.json +21 -9
@@ -0,0 +1,226 @@
1
+ package app.independo.inderun.capacitor
2
+
3
+ import app.independo.inderun.contracts.FinishReason
4
+ import app.independo.inderun.contracts.IndeRunErrorClass
5
+ import app.independo.inderun.contracts.Outcome
6
+ import app.independo.inderun.contracts.Payload
7
+ import app.independo.inderun.contracts.PayloadError
8
+ import app.independo.inderun.contracts.PayloadTelemetry
9
+ import app.independo.inderun.contracts.PayloadUsage
10
+ import app.independo.inderun.contracts.Phase
11
+ import app.independo.inderun.contracts.SchemaVersion
12
+ import app.independo.inderun.contracts.StreamEvent
13
+ import app.independo.inderun.contracts.StreamRunHandle
14
+ import org.junit.Assert.assertEquals
15
+ import org.junit.Assert.assertFalse
16
+ import org.junit.Assert.assertTrue
17
+ import org.junit.Test
18
+
19
+ /**
20
+ * Encoding is what the JS side actually consumes, so these assert exact key sets: a
21
+ * field that encodes as null instead of being absent changes which union branch the
22
+ * consumer reconstitutes.
23
+ */
24
+ class IndeRunSerializerStreamTest {
25
+
26
+ private fun event(
27
+ type: String,
28
+ payload: Payload?,
29
+ sequence: Long = 0,
30
+ runId: String = "run_1"
31
+ ): StreamEvent = StreamEvent(
32
+ payload = payload,
33
+ runId = runId,
34
+ schemaVersion = SchemaVersion.V1_0,
35
+ sequence = sequence,
36
+ timestamp = 1_700_000_000_000.0,
37
+ type = type
38
+ )
39
+
40
+ private fun keysOf(json: org.json.JSONObject): Set<String> =
41
+ json.keys().asSequence().toSet()
42
+
43
+ @Test
44
+ fun `encodes a content delta with only the text field`() {
45
+ val encoded = IndeRunSerializer.encodeStreamEvent(
46
+ event(type = "content_delta", payload = Payload(text = "hello"))
47
+ )
48
+
49
+ assertEquals("1.0", encoded.getString("schemaVersion"))
50
+ assertEquals("run_1", encoded.getString("runId"))
51
+ assertEquals(0L, encoded.getLong("sequence"))
52
+ assertEquals("content_delta", encoded.getString("type"))
53
+ assertEquals(setOf("text"), keysOf(encoded.getJSONObject("payload")))
54
+ assertEquals("hello", encoded.getJSONObject("payload").getString("text"))
55
+ }
56
+
57
+ @Test
58
+ fun `encodes a completed terminal outcome`() {
59
+ val payload = Payload(
60
+ finalText = "all done",
61
+ finishReason = FinishReason.STOP,
62
+ outcome = Outcome.Completed,
63
+ runId = "run_1",
64
+ schemaVersion = SchemaVersion.V1_0,
65
+ telemetry = PayloadTelemetry(providerUsed = "openai", totalMs = 42.0),
66
+ usage = PayloadUsage(inputTokens = 3, outputTokens = 5, totalTokens = 8)
67
+ )
68
+
69
+ val encoded = IndeRunSerializer.encodeStreamEvent(
70
+ event(type = "terminal", payload = payload, sequence = 4)
71
+ ).getJSONObject("payload")
72
+
73
+ assertEquals("completed", encoded.getString("outcome"))
74
+ assertEquals("all done", encoded.getString("finalText"))
75
+ assertEquals("stop", encoded.getString("finishReason"))
76
+ assertEquals(8L, encoded.getJSONObject("usage").getLong("totalTokens"))
77
+ assertEquals("openai", encoded.getJSONObject("telemetry").getString("providerUsed"))
78
+ // The other branches' fields must not leak into a completion.
79
+ assertFalse(encoded.has("error"))
80
+ assertFalse(encoded.has("partialText"))
81
+ assertFalse(encoded.has("reason"))
82
+ }
83
+
84
+ @Test
85
+ fun `encodes an error terminal outcome with its nested error`() {
86
+ val payload = Payload(
87
+ outcome = Outcome.Error,
88
+ runId = "run_1",
89
+ schemaVersion = SchemaVersion.V1_0,
90
+ error = PayloadError(
91
+ details = mapOf("endpoint" to "https://example.test"),
92
+ errorClass = IndeRunErrorClass.RateLimited,
93
+ message = "Too many requests.",
94
+ providerId = "openai",
95
+ retryable = true,
96
+ retryAfterMs = 2_000
97
+ ),
98
+ partialText = "half "
99
+ )
100
+
101
+ val encoded = IndeRunSerializer.encodeStreamEvent(
102
+ event(type = "terminal", payload = payload, sequence = 7)
103
+ ).getJSONObject("payload")
104
+
105
+ assertEquals("error", encoded.getString("outcome"))
106
+ assertEquals("half ", encoded.getString("partialText"))
107
+
108
+ val error = encoded.getJSONObject("error")
109
+ assertEquals("RateLimited", error.getString("errorClass"))
110
+ assertEquals(2_000L, error.getLong("retryAfterMs"))
111
+ assertTrue(error.getBoolean("retryable"))
112
+ assertEquals(
113
+ "https://example.test",
114
+ error.getJSONObject("details").getString("endpoint")
115
+ )
116
+ }
117
+
118
+ @Test
119
+ fun `omits the cancellation reason entirely when there is none`() {
120
+ val withReason = IndeRunSerializer.encodeStreamEvent(
121
+ event(
122
+ type = "terminal",
123
+ payload = Payload(
124
+ outcome = Outcome.Cancelled,
125
+ partialText = "part",
126
+ reason = "user left"
127
+ )
128
+ )
129
+ ).getJSONObject("payload")
130
+ assertEquals("user left", withReason.getString("reason"))
131
+
132
+ val withoutReason = IndeRunSerializer.encodeStreamEvent(
133
+ event(
134
+ type = "terminal",
135
+ payload = Payload(outcome = Outcome.Cancelled, partialText = "part")
136
+ )
137
+ ).getJSONObject("payload")
138
+
139
+ assertEquals("cancelled", withoutReason.getString("outcome"))
140
+ assertEquals("part", withoutReason.getString("partialText"))
141
+ // Absent, not null: a null would decode as a present-but-empty reason.
142
+ assertFalse(withoutReason.has("reason"))
143
+ }
144
+
145
+ @Test
146
+ fun `encodes a lifecycle phase using its snake case wire value`() {
147
+ val encoded = IndeRunSerializer.encodeStreamEvent(
148
+ event(type = "lifecycle", payload = Payload(phase = Phase.ProviderSelected))
149
+ ).getJSONObject("payload")
150
+
151
+ assertEquals("provider_selected", encoded.getString("phase"))
152
+ }
153
+
154
+ /**
155
+ * The schema closes its union with an open catch-all branch, so an unrecognized
156
+ * type must cross the bridge untouched rather than be rejected — unlike the
157
+ * inbound request enums, which throw.
158
+ */
159
+ @Test
160
+ fun `passes an unknown event type through verbatim without a payload`() {
161
+ val encoded = IndeRunSerializer.encodeStreamEvent(
162
+ event(type = "some_future_type", payload = null, sequence = 2)
163
+ )
164
+
165
+ assertEquals("some_future_type", encoded.getString("type"))
166
+ assertFalse(encoded.has("payload"))
167
+ }
168
+
169
+ @Test
170
+ fun `encodes a diagnostic event with an empty payload object`() {
171
+ val encoded = IndeRunSerializer.encodeStreamEvent(
172
+ event(type = "diagnostic", payload = Payload())
173
+ )
174
+
175
+ assertTrue(encoded.has("payload"))
176
+ assertEquals(emptySet<String>(), keysOf(encoded.getJSONObject("payload")))
177
+ }
178
+
179
+ @Test
180
+ fun `encodes a stream run handle with and without a provider id`() {
181
+ val withProvider = IndeRunSerializer.encodeStreamRunHandle(
182
+ StreamRunHandle(
183
+ providerId = "openai",
184
+ runId = "run_1",
185
+ schemaVersion = SchemaVersion.V1_0,
186
+ startedAt = 1_700_000_000_000.0
187
+ )
188
+ )
189
+ assertEquals("openai", withProvider.getString("providerId"))
190
+ assertEquals("run_1", withProvider.getString("runId"))
191
+ assertEquals(1_700_000_000_000.0, withProvider.getDouble("startedAt"), 0.0)
192
+
193
+ val withoutProvider = IndeRunSerializer.encodeStreamRunHandle(
194
+ StreamRunHandle(
195
+ runId = "run_1",
196
+ schemaVersion = SchemaVersion.V1_0,
197
+ startedAt = 1_700_000_000_000.0
198
+ )
199
+ )
200
+ assertFalse(withoutProvider.has("providerId"))
201
+ }
202
+
203
+ /**
204
+ * Outcome and Phase are generated without a rawValue, so their wire strings are
205
+ * hand-written. If a regeneration adds a constant, the `when` stops compiling —
206
+ * and if one is ever mapped to the wrong casing, this catches it.
207
+ */
208
+ @Test
209
+ fun `every outcome and phase constant maps to a snake case wire value`() {
210
+ val outcomes = Outcome.entries.map { outcome ->
211
+ IndeRunSerializer.encodeStreamEvent(
212
+ event(type = "terminal", payload = Payload(outcome = outcome))
213
+ ).getJSONObject("payload").getString("outcome")
214
+ }
215
+ assertEquals(listOf("cancelled", "completed", "error"), outcomes)
216
+
217
+ val phases = Phase.entries.map { phase ->
218
+ IndeRunSerializer.encodeStreamEvent(
219
+ event(type = "lifecycle", payload = Payload(phase = phase))
220
+ ).getJSONObject("payload").getString("phase")
221
+ }
222
+ assertEquals(listOf("provider_selected", "started"), phases)
223
+
224
+ assertTrue((outcomes + phases).all { it.isNotEmpty() && it == it.lowercase() })
225
+ }
226
+ }
@@ -142,6 +142,29 @@ class IndeRunSerializerTest {
142
142
 
143
143
  // --- encodeError ---
144
144
 
145
+ /**
146
+ * `systemModel` and `onnx` are web-only bootstrap keys, like
147
+ * `allowDirectOpenAIEndpoint`. Android registers its own on-device provider, so it must
148
+ * ignore them rather than fail on the options object that carries them.
149
+ */
150
+ @Test
151
+ fun `parseConfigureOptions ignores the web-only provider bootstrap keys`() {
152
+ val options = IndeRunSerializer.parseConfigureOptions(
153
+ JSONObject(
154
+ """
155
+ {
156
+ "openAI": { "model": "gpt-5.2", "auth": "none" },
157
+ "systemModel": { "id": "local.system-model.web", "timeoutMs": 30000 },
158
+ "onnx": { "modelPackage": { "id": "demo", "format": "onnx" } }
159
+ }
160
+ """.trimIndent()
161
+ )
162
+ )
163
+
164
+ assertEquals("gpt-5.2", options.openAI?.model)
165
+ assertEquals("none", options.openAI?.auth)
166
+ }
167
+
145
168
  @Test
146
169
  fun `encodeError produces correct shape with required fields only`() {
147
170
  val encoded = IndeRunSerializer.encodeError(
@@ -177,4 +200,60 @@ class IndeRunSerializerTest {
177
200
  assertEquals(2000L, encoded.getLong("retryAfterMs"))
178
201
  assertEquals("run_abc", encoded.getString("runId"))
179
202
  }
203
+
204
+ /**
205
+ * A routing refusal carries its plan diagnostics on the error's `details`, and since
206
+ * inderun 0.3.0 it does so on iOS and Android too, not just on the web SDK. The shape
207
+ * is nested — a `rejectedProviders` array of objects, each with its own `reasons`
208
+ * array — and `encodeError` hands the whole map to `JSONObject(Map)`, which deep-wraps
209
+ * via `wrap()`. Nothing asserted that before; this is the data a provider-refusal UI
210
+ * actually reads, so a regression here would be invisible until a demo showed an empty
211
+ * table.
212
+ */
213
+ @Test
214
+ fun `encodeError preserves nested route-plan diagnostics in details`() {
215
+ val encoded = IndeRunSerializer.encodeError(
216
+ IndeRunError(
217
+ errorClass = IndeRunErrorClass.CapabilityMismatch,
218
+ message = "No eligible provider can stream this request.",
219
+ details = mapOf(
220
+ "failureCode" to "CapabilityMismatch",
221
+ "rejectedProviders" to listOf(
222
+ mapOf(
223
+ "providerId" to "android.mlkit.genai",
224
+ "reasons" to listOf(
225
+ mapOf(
226
+ "code" to "streaming_not_supported",
227
+ "message" to "Provider does not declare streaming."
228
+ )
229
+ )
230
+ ),
231
+ mapOf(
232
+ "providerId" to "openai",
233
+ "reasons" to listOf(
234
+ mapOf("code" to "privacy_constraint", "message" to "local_required forbids cloud.")
235
+ )
236
+ )
237
+ )
238
+ )
239
+ )
240
+ )
241
+
242
+ val details = encoded.getJSONObject("details")
243
+ assertEquals("CapabilityMismatch", details.getString("failureCode"))
244
+
245
+ val rejected = details.getJSONArray("rejectedProviders")
246
+ assertEquals(2, rejected.length())
247
+ assertEquals("android.mlkit.genai", rejected.getJSONObject(0).getString("providerId"))
248
+ assertEquals(
249
+ "streaming_not_supported",
250
+ rejected.getJSONObject(0).getJSONArray("reasons").getJSONObject(0).getString("code")
251
+ )
252
+ assertEquals("openai", rejected.getJSONObject(1).getString("providerId"))
253
+ assertEquals(
254
+ "privacy_constraint",
255
+ rejected.getJSONObject(1).getJSONArray("reasons").getJSONObject(0).getString("code")
256
+ )
257
+ }
258
+
180
259
  }
@@ -0,0 +1,163 @@
1
+ package app.independo.inderun.capacitor
2
+
3
+ import app.independo.inderun.contracts.SchemaVersion
4
+ import app.independo.inderun.contracts.StreamRunHandle
5
+ import app.independo.inderun.core.StreamRun
6
+ import kotlinx.coroutines.Job
7
+ import kotlinx.coroutines.flow.emptyFlow
8
+ import org.junit.Assert.assertEquals
9
+ import org.junit.Assert.assertFalse
10
+ import org.junit.Assert.assertTrue
11
+ import org.junit.Test
12
+
13
+ class IndeRunStreamRegistryTest {
14
+
15
+ private fun run(record: (String?) -> Unit): StreamRun = StreamRun(
16
+ handle = StreamRunHandle(
17
+ runId = "run_1",
18
+ schemaVersion = SchemaVersion.V1_0,
19
+ startedAt = 1_700_000_000_000.0
20
+ ),
21
+ events = emptyFlow(),
22
+ onCancel = record
23
+ )
24
+
25
+ @Test
26
+ fun `attaches and closes by stream id`() {
27
+ val registry = IndeRunStreamRegistry()
28
+
29
+ registry.open("s1")
30
+ assertEquals(AttachOutcome.Attached, registry.attach("s1", run {}))
31
+ assertEquals(1, registry.activeCount)
32
+
33
+ registry.close("s1")
34
+ assertEquals(0, registry.activeCount)
35
+ }
36
+
37
+ /**
38
+ * A cancel can beat the engine's route selection. It must be held and applied when
39
+ * the run finally attaches, not dropped.
40
+ */
41
+ @Test
42
+ fun `cancel before attach is applied on attach with its reason`() {
43
+ val registry = IndeRunStreamRegistry()
44
+ val reasons = mutableListOf<String?>()
45
+
46
+ registry.open("s1")
47
+ registry.requestCancel("s1", "user left")
48
+ // Nothing to cancel yet, so nothing was called.
49
+ assertTrue(reasons.isEmpty())
50
+
51
+ val outcome = registry.attach("s1", run { reasons.add(it) })
52
+ assertEquals(AttachOutcome.CancelRequested("user left"), outcome)
53
+ }
54
+
55
+ @Test
56
+ fun `cancel after close is a no-op`() {
57
+ val registry = IndeRunStreamRegistry()
58
+ val reasons = mutableListOf<String?>()
59
+
60
+ registry.open("s1")
61
+ registry.attach("s1", run { reasons.add(it) })
62
+ registry.close("s1")
63
+
64
+ registry.requestCancel("s1", "too late")
65
+ assertTrue(reasons.isEmpty())
66
+ }
67
+
68
+ @Test
69
+ fun `cancel for an unknown stream id is a no-op`() {
70
+ val registry = IndeRunStreamRegistry()
71
+
72
+ registry.requestCancel("never_opened", null)
73
+ assertEquals(0, registry.activeCount)
74
+ }
75
+
76
+ @Test
77
+ fun `repeated cancel cancels the run exactly once`() {
78
+ val registry = IndeRunStreamRegistry()
79
+ val reasons = mutableListOf<String?>()
80
+
81
+ registry.open("s1")
82
+ registry.attach("s1", run { reasons.add(it) })
83
+
84
+ registry.requestCancel("s1", "first")
85
+ registry.requestCancel("s1", "second")
86
+
87
+ assertEquals(listOf("first"), reasons)
88
+ }
89
+
90
+ @Test
91
+ fun `concurrent streams are isolated`() {
92
+ val registry = IndeRunStreamRegistry()
93
+ val first = mutableListOf<String?>()
94
+ val second = mutableListOf<String?>()
95
+
96
+ registry.open("s1")
97
+ registry.open("s2")
98
+ registry.attach("s1", run { first.add(it) })
99
+ registry.attach("s2", run { second.add(it) })
100
+
101
+ registry.requestCancel("s1", "only the first")
102
+
103
+ assertEquals(listOf("only the first"), first)
104
+ assertTrue(second.isEmpty())
105
+ assertEquals(2, registry.activeCount)
106
+ }
107
+
108
+ @Test
109
+ fun `cancelAll cancels every run once and empties the registry`() {
110
+ val registry = IndeRunStreamRegistry()
111
+ val first = mutableListOf<String?>()
112
+ val second = mutableListOf<String?>()
113
+
114
+ registry.open("s1")
115
+ registry.open("s2")
116
+ registry.attach("s1", run { first.add(it) })
117
+ registry.attach("s2", run { second.add(it) })
118
+
119
+ registry.cancelAll("torn down")
120
+
121
+ assertEquals(listOf("torn down"), first)
122
+ assertEquals(listOf("torn down"), second)
123
+ assertEquals(0, registry.activeCount)
124
+ }
125
+
126
+ /**
127
+ * Teardown also cancels the collecting jobs — at that point nobody is left to
128
+ * receive a terminal event.
129
+ */
130
+ @Test
131
+ fun `cancelAll cancels the collecting job`() {
132
+ val registry = IndeRunStreamRegistry()
133
+ val job = Job()
134
+
135
+ registry.open("s1")
136
+ registry.attach("s1", run {})
137
+ registry.attachJob("s1", job)
138
+
139
+ assertFalse(job.isCancelled)
140
+ registry.cancelAll(null)
141
+ assertTrue(job.isCancelled)
142
+ }
143
+
144
+ @Test
145
+ fun `attach after teardown reports cancel requested`() {
146
+ val registry = IndeRunStreamRegistry()
147
+
148
+ registry.open("s1")
149
+ registry.cancelAll("torn down")
150
+
151
+ // The entry is gone, so the run that arrives late must be cancelled by its caller.
152
+ assertEquals(AttachOutcome.CancelRequested(null), registry.attach("s1", run {}))
153
+ }
154
+
155
+ @Test
156
+ fun `a job attached to a closed stream is cancelled immediately`() {
157
+ val registry = IndeRunStreamRegistry()
158
+ val job = Job()
159
+
160
+ registry.attachJob("never_opened", job)
161
+ assertTrue(job.isCancelled)
162
+ }
163
+ }
@@ -1,4 +1,5 @@
1
- import type { TaskRequest, TaskResult } from "@independo/inderun-contracts";
1
+ import type { PluginListenerHandle } from "@capacitor/core";
2
+ import type { IndeRunError, ModelPackage, StreamEvent, StreamRunHandle, TaskRequest, TaskResult } from "@independo/inderun-contracts";
2
3
  export interface OpenAIProviderBootstrapOptions {
3
4
  model: string;
4
5
  endpointUrl?: string;
@@ -6,12 +7,185 @@ export interface OpenAIProviderBootstrapOptions {
6
7
  authContextRef?: string;
7
8
  timeoutMs?: number;
8
9
  }
10
+ /**
11
+ * Bootstrap options for the browser-managed on-device provider (Chrome's Prompt API).
12
+ *
13
+ * **Web only**, and ignored on iOS and Android, which register their own on-device
14
+ * providers — Apple Foundation Models and ML Kit GenAI — from `configure()` regardless.
15
+ *
16
+ * The browser owns model availability, download and execution, so there is nothing to
17
+ * configure: registering it is the whole point, and it is what makes
18
+ * `constraints.privacy = "local_required"` routable in a browser. Mode 1 only — this
19
+ * provider does not stream, so a `local_required` *stream* on the web is still refused at
20
+ * routing time.
21
+ *
22
+ * The web SDK's `runtime` injection seam is deliberately not exposed: it is a function,
23
+ * and `configure()` has to survive a JSON bridge hop on native.
24
+ */
25
+ export interface SystemModelProviderBootstrapOptions {
26
+ id?: string;
27
+ timeoutMs?: number;
28
+ }
29
+ /**
30
+ * Bootstrap options for the Web ONNX Runtime provider, for a developer-supplied local
31
+ * model.
32
+ *
33
+ * **Web only**, like `systemModel`, and Mode 1 only.
34
+ *
35
+ * Two caveats worth knowing before reaching for this. The consumer has to install the
36
+ * optional `@huggingface/transformers` peer dependency — this bridge does not declare it —
37
+ * and supply real model weights, because the web SDK's `runtime` seam is a function and so
38
+ * cannot cross the JSON bridge hop: only the default Transformers.js runtime is reachable
39
+ * from here, never the fixture runtime the upstream demos use offline. A registered
40
+ * provider that cannot load turns a clean routing refusal into a provider error, so
41
+ * register it only when the weights are actually there.
42
+ */
43
+ export interface OnnxProviderBootstrapOptions {
44
+ id?: string;
45
+ modelPackage: ModelPackage;
46
+ timeoutMs?: number;
47
+ }
9
48
  export interface ConfigureOptions {
10
49
  openAI?: OpenAIProviderBootstrapOptions;
50
+ /** Web only; native registers its own on-device provider. */
51
+ systemModel?: SystemModelProviderBootstrapOptions;
52
+ /** Web only; native registers its own on-device provider. */
53
+ onnx?: OnnxProviderBootstrapOptions;
11
54
  allowDirectOpenAIEndpoint?: boolean;
12
55
  }
56
+ /**
57
+ * Bridge-local correlation id for one streaming run.
58
+ *
59
+ * Not a contract field, and never placed inside the `TaskRequest`: `runId` stays the
60
+ * engine's to mint. `startStream` resolves only *after* the engine has minted one, so
61
+ * keying the JS-side event sink on `runId` would leave a window in which native can
62
+ * emit events for a run JS has not yet heard of. The `streamId` is generated before
63
+ * the call instead, which lets the sink be bound first and makes `cancel()`
64
+ * well-defined even before a handle exists.
65
+ */
66
+ export interface StartStreamOptions {
67
+ streamId: string;
68
+ request: TaskRequest;
69
+ }
70
+ export interface CancelStreamOptions {
71
+ streamId: string;
72
+ reason?: string;
73
+ }
74
+ /** Payload of `"indeRunStreamEvent"`. `event` is the canonical, unmodified `StreamEvent`. */
75
+ export interface StreamEventNotification {
76
+ streamId: string;
77
+ event: StreamEvent;
78
+ }
79
+ /**
80
+ * Payload of `"indeRunStreamError"`: a failure of the *bridge*, not an outcome of the
81
+ * run. A run that fails ends in a terminal event with `payload.outcome === "error"`,
82
+ * delivered through `"indeRunStreamEvent"` like any other event.
83
+ */
84
+ export interface StreamErrorNotification {
85
+ streamId: string;
86
+ error: IndeRunError;
87
+ }
88
+ /**
89
+ * Structurally identical to `StreamRun` in `@independo/inderun-web`, so bridge and
90
+ * direct-SDK consumers read the same. `events` terminates in exactly one terminal
91
+ * event and is single-use; `cancel()` is idempotent.
92
+ */
93
+ export interface StreamRun {
94
+ handle: StreamRunHandle;
95
+ events: AsyncIterable<StreamEvent>;
96
+ cancel(reason?: string): void;
97
+ }
98
+ /**
99
+ * A provider's static capability declaration.
100
+ *
101
+ * Declared here rather than imported, because — unlike `TaskRequest`, `StreamEvent` and
102
+ * the rest — this shape is *not* a generated contract. There is no schema for it in
103
+ * `@independo/inderun-contracts` and no validator; each SDK declares it independently
104
+ * (`core/provider.d.ts` in `@independo/inderun-web`, `IndeRunCore/Provider.swift`,
105
+ * `core/Provider.kt`), and this is the fourth copy. Importing it from
106
+ * `@independo/inderun-web` is not an option either: that would make the native
107
+ * platforms' plugin contract depend on the web SDK.
108
+ *
109
+ * The drift guard is in `web.ts`, which returns the web SDK's value into this type
110
+ * uncast — so `tsc` proves the shapes still match on every build, the same way the
111
+ * `StreamRun` mirror below is checked. See the upstream follow-up asking for a schema.
112
+ */
113
+ export interface ProviderDescriptor {
114
+ id: string;
115
+ type: "local" | "edge" | "cloud";
116
+ transport: "in_process" | "system_service" | "http" | "sse" | "realtime";
117
+ streamingStyle?: "tokens" | "chunks" | "snapshots";
118
+ supports: {
119
+ run: boolean;
120
+ streaming: boolean;
121
+ realtime: boolean;
122
+ tools: boolean;
123
+ reasoningEvents: boolean;
124
+ structuredOutput: boolean;
125
+ multimodal: boolean;
126
+ };
127
+ cancel: "hard" | "soft" | "none";
128
+ tasks: string[];
129
+ limits?: {
130
+ maxInputTokens?: number;
131
+ maxOutputTokens?: number;
132
+ maxImageBytes?: number;
133
+ maxAudioSeconds?: number;
134
+ };
135
+ privacy?: {
136
+ dataLeavesDevice: boolean;
137
+ regions?: string[];
138
+ };
139
+ }
140
+ /**
141
+ * A provider's live availability, as of the moment `checkCapabilities()` was called.
142
+ *
143
+ * `streamingAvailable` and `cancellationAvailable` are **absent**, not `null`, when the
144
+ * runtime has nothing to say: absence means *inherit the static declaration*
145
+ * (`descriptor.supports.streaming`, and `descriptor.cancel !== "none"`). Both native
146
+ * encoders omit the key rather than emitting null for exactly that reason.
147
+ */
148
+ export interface ProviderDynamicCapabilities {
149
+ available: boolean;
150
+ reason?: string;
151
+ streamingAvailable?: boolean;
152
+ streamingUnavailableReason?: string;
153
+ cancellationAvailable?: boolean;
154
+ }
155
+ /** One registered provider's identity, static declaration, and live availability. */
156
+ export interface ProviderCapabilitySnapshot {
157
+ providerId: string;
158
+ descriptor: ProviderDescriptor;
159
+ capabilities: ProviderDynamicCapabilities;
160
+ }
161
+ /**
162
+ * Envelope for `checkCapabilities()`. The array is wrapped because a Capacitor plugin
163
+ * method must resolve an object on both native platforms — `PluginCall.resolve(JSObject)`
164
+ * and `call.resolve([String: Any])` cannot carry a top-level array. `IndeRunCapacitor`
165
+ * and `createIndeRunCapacitor()` unwrap it, so app code sees the same
166
+ * `ProviderCapabilitySnapshot[]` the three platform SDKs return.
167
+ */
168
+ export interface CheckCapabilitiesResult {
169
+ providers: ProviderCapabilitySnapshot[];
170
+ }
13
171
  export interface IndeRunCapacitorPlugin {
14
172
  configure(options?: ConfigureOptions): Promise<void>;
15
173
  run(request: TaskRequest): Promise<TaskResult>;
174
+ /**
175
+ * Live snapshot of every registered provider, without executing a task. Availability
176
+ * changes between calls — a local model can unload, cloud credentials can expire — so
177
+ * do not cache it across a `run()` or `stream()`.
178
+ */
179
+ checkCapabilities(): Promise<CheckCapabilitiesResult>;
180
+ /**
181
+ * Unlike `run(request)`, which passes the request at the options root, this nests it
182
+ * under `request` so the envelope can also carry `streamId`. Both native decode sites
183
+ * carry a matching comment.
184
+ */
185
+ startStream(options: StartStreamOptions): Promise<StreamRunHandle>;
186
+ cancelStream(options: CancelStreamOptions): Promise<void>;
187
+ addListener(eventName: "indeRunStreamEvent", listenerFunc: (notification: StreamEventNotification) => void): Promise<PluginListenerHandle>;
188
+ addListener(eventName: "indeRunStreamError", listenerFunc: (notification: StreamErrorNotification) => void): Promise<PluginListenerHandle>;
189
+ removeAllListeners(): Promise<void>;
16
190
  }
17
191
  //# sourceMappingURL=definitions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../src/definitions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE5E,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,8BAA8B,CAAC;IACxC,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAChD"}
1
+ {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../src/definitions.ts"],"names":[],"mappings":"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"}