@independo/capacitor-inderun 1.0.1-dev.1 → 1.1.0-dev.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.
Files changed (29) hide show
  1. package/Package.swift +6 -2
  2. package/README.md +97 -9
  3. package/android/build.gradle.kts +5 -5
  4. package/android/src/main/kotlin/app/independo/inderun/capacitor/IndeRunCapacitorPlugin.kt +119 -0
  5. package/android/src/main/kotlin/app/independo/inderun/capacitor/IndeRunSerializer.kt +106 -1
  6. package/android/src/main/kotlin/app/independo/inderun/capacitor/IndeRunStreamRegistry.kt +118 -0
  7. package/android/src/test/kotlin/app/independo/inderun/capacitor/IndeRunSerializerStreamTest.kt +226 -0
  8. package/android/src/test/kotlin/app/independo/inderun/capacitor/IndeRunStreamRegistryTest.kt +163 -0
  9. package/dist/definitions.d.ts +54 -1
  10. package/dist/definitions.d.ts.map +1 -1
  11. package/dist/errors.d.ts +19 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +36 -0
  14. package/dist/index.d.ts +14 -4
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +30 -13
  17. package/dist/streaming.d.ts +17 -0
  18. package/dist/streaming.d.ts.map +1 -0
  19. package/dist/streaming.js +303 -0
  20. package/dist/web.d.ts +17 -2
  21. package/dist/web.d.ts.map +1 -1
  22. package/dist/web.js +70 -0
  23. package/ios/Sources/IndeRunCapacitorPlugin/IndeRunCapacitorBridge.swift +121 -0
  24. package/ios/Sources/IndeRunCapacitorPlugin/IndeRunCapacitorPlugin.swift +70 -1
  25. package/ios/Sources/IndeRunCapacitorPlugin/IndeRunCapacitorStreamRegistry.swift +121 -0
  26. package/ios/Tests/IndeRunCapacitorTests/IndeRunCapacitorStreamCodecTests.swift +178 -0
  27. package/ios/Tests/IndeRunCapacitorTests/IndeRunCapacitorStreamPumpTests.swift +215 -0
  28. package/ios/Tests/IndeRunCapacitorTests/IndeRunCapacitorStreamRegistryTests.swift +154 -0
  29. package/package.json +18 -6
@@ -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
+ }
@@ -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, StreamEvent, StreamRunHandle, TaskRequest, TaskResult } from "@independo/inderun-contracts";
2
3
  export interface OpenAIProviderBootstrapOptions {
3
4
  model: string;
4
5
  endpointUrl?: string;
@@ -10,8 +11,60 @@ export interface ConfigureOptions {
10
11
  openAI?: OpenAIProviderBootstrapOptions;
11
12
  allowDirectOpenAIEndpoint?: boolean;
12
13
  }
14
+ /**
15
+ * Bridge-local correlation id for one streaming run.
16
+ *
17
+ * Not a contract field, and never placed inside the `TaskRequest`: `runId` stays the
18
+ * engine's to mint. `startStream` resolves only *after* the engine has minted one, so
19
+ * keying the JS-side event sink on `runId` would leave a window in which native can
20
+ * emit events for a run JS has not yet heard of. The `streamId` is generated before
21
+ * the call instead, which lets the sink be bound first and makes `cancel()`
22
+ * well-defined even before a handle exists.
23
+ */
24
+ export interface StartStreamOptions {
25
+ streamId: string;
26
+ request: TaskRequest;
27
+ }
28
+ export interface CancelStreamOptions {
29
+ streamId: string;
30
+ reason?: string;
31
+ }
32
+ /** Payload of `"indeRunStreamEvent"`. `event` is the canonical, unmodified `StreamEvent`. */
33
+ export interface StreamEventNotification {
34
+ streamId: string;
35
+ event: StreamEvent;
36
+ }
37
+ /**
38
+ * Payload of `"indeRunStreamError"`: a failure of the *bridge*, not an outcome of the
39
+ * run. A run that fails ends in a terminal event with `payload.outcome === "error"`,
40
+ * delivered through `"indeRunStreamEvent"` like any other event.
41
+ */
42
+ export interface StreamErrorNotification {
43
+ streamId: string;
44
+ error: IndeRunError;
45
+ }
46
+ /**
47
+ * Structurally identical to `StreamRun` in `@independo/inderun-web`, so bridge and
48
+ * direct-SDK consumers read the same. `events` terminates in exactly one terminal
49
+ * event and is single-use; `cancel()` is idempotent.
50
+ */
51
+ export interface StreamRun {
52
+ handle: StreamRunHandle;
53
+ events: AsyncIterable<StreamEvent>;
54
+ cancel(reason?: string): void;
55
+ }
13
56
  export interface IndeRunCapacitorPlugin {
14
57
  configure(options?: ConfigureOptions): Promise<void>;
15
58
  run(request: TaskRequest): Promise<TaskResult>;
59
+ /**
60
+ * Unlike `run(request)`, which passes the request at the options root, this nests it
61
+ * under `request` so the envelope can also carry `streamId`. Both native decode sites
62
+ * carry a matching comment.
63
+ */
64
+ startStream(options: StartStreamOptions): Promise<StreamRunHandle>;
65
+ cancelStream(options: CancelStreamOptions): Promise<void>;
66
+ addListener(eventName: "indeRunStreamEvent", listenerFunc: (notification: StreamEventNotification) => void): Promise<PluginListenerHandle>;
67
+ addListener(eventName: "indeRunStreamError", listenerFunc: (notification: StreamErrorNotification) => void): Promise<PluginListenerHandle>;
68
+ removeAllListeners(): Promise<void>;
16
69
  }
17
70
  //# 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":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,eAAe,EACf,WAAW,EACX,UAAU,EACX,MAAM,8BAA8B,CAAC;AAEtC,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;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,6FAA6F;AAC7F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACnC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;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;IAC/C;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACnE,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,WAAW,CACT,SAAS,EAAE,oBAAoB,EAC/B,YAAY,EAAE,CAAC,YAAY,EAAE,uBAAuB,KAAK,IAAI,GAC5D,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjC,WAAW,CACT,SAAS,EAAE,oBAAoB,EAC/B,YAAY,EAAE,CAAC,YAAY,EAAE,uBAAuB,KAAK,IAAI,GAC5D,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC"}
@@ -0,0 +1,19 @@
1
+ import { type IndeRunError } from "@independo/inderun-contracts";
2
+ /**
3
+ * Unwraps whatever the Capacitor boundary hands back into the canonical
4
+ * `IndeRunError` the native side rejected with.
5
+ *
6
+ * Capacitor wraps a native rejection's structured data under `error.data`, so a
7
+ * thrown value is either already an `IndeRunError` (the web implementation throws
8
+ * one directly) or carries one there. Anything else is passed through untouched
9
+ * rather than guessed at.
10
+ */
11
+ export declare function normalizePluginError(error: unknown): unknown;
12
+ /**
13
+ * Mints an `IndeRunError` for a failure of the bridge itself — event loss across
14
+ * the JS boundary, a malformed handle — as opposed to one reported by a provider
15
+ * or the engine. `Internal` is the correct class: nothing about the request or the
16
+ * provider is at fault, the transport is.
17
+ */
18
+ export declare function createBridgeError(message: string, runId?: string): IndeRunError;
19
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEvF;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAe5D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,CAO/E"}
package/dist/errors.js ADDED
@@ -0,0 +1,36 @@
1
+ import { validateIndeRunError } from "@independo/inderun-contracts";
2
+ /**
3
+ * Unwraps whatever the Capacitor boundary hands back into the canonical
4
+ * `IndeRunError` the native side rejected with.
5
+ *
6
+ * Capacitor wraps a native rejection's structured data under `error.data`, so a
7
+ * thrown value is either already an `IndeRunError` (the web implementation throws
8
+ * one directly) or carries one there. Anything else is passed through untouched
9
+ * rather than guessed at.
10
+ */
11
+ export function normalizePluginError(error) {
12
+ if (validateIndeRunError(error)) {
13
+ return error;
14
+ }
15
+ if (typeof error === "object" &&
16
+ error !== null &&
17
+ "data" in error &&
18
+ validateIndeRunError(error.data)) {
19
+ return error.data;
20
+ }
21
+ return error;
22
+ }
23
+ /**
24
+ * Mints an `IndeRunError` for a failure of the bridge itself — event loss across
25
+ * the JS boundary, a malformed handle — as opposed to one reported by a provider
26
+ * or the engine. `Internal` is the correct class: nothing about the request or the
27
+ * provider is at fault, the transport is.
28
+ */
29
+ export function createBridgeError(message, runId) {
30
+ return {
31
+ schemaVersion: "1.0",
32
+ errorClass: "Internal",
33
+ message,
34
+ ...(runId !== undefined ? { runId } : {})
35
+ };
36
+ }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,20 @@
1
- import { type TaskRequest, type TaskResult } from "@independo/inderun-contracts";
2
- import type { ConfigureOptions, IndeRunCapacitorPlugin as IndeRunCapacitorPluginContract, OpenAIProviderBootstrapOptions } from "./definitions.js";
3
- export declare const IndeRunCapacitor: IndeRunCapacitorPluginContract;
1
+ import type { TaskRequest, TaskResult } from "@independo/inderun-contracts";
2
+ import type { CancelStreamOptions, ConfigureOptions, IndeRunCapacitorPlugin as IndeRunCapacitorPluginContract, OpenAIProviderBootstrapOptions, StartStreamOptions, StreamErrorNotification, StreamEventNotification, StreamRun } from "./definitions.js";
3
+ /**
4
+ * The plugin contract plus the ergonomic Mode 2 entry point. `stream()` is not a
5
+ * plugin method — it is `startStream`, the event listeners, and `cancelStream`
6
+ * reassembled into the same shape the platform SDKs return from `stream()`.
7
+ */
8
+ export interface IndeRunCapacitorApi extends IndeRunCapacitorPluginContract {
9
+ stream(request: TaskRequest): Promise<StreamRun>;
10
+ }
11
+ export declare const IndeRunCapacitor: IndeRunCapacitorApi;
4
12
  export interface IndeRunCapacitorInstance {
5
13
  run(request: TaskRequest): Promise<TaskResult>;
14
+ stream(request: TaskRequest): Promise<StreamRun>;
6
15
  }
7
16
  export declare function createIndeRunCapacitor(options?: ConfigureOptions): IndeRunCapacitorInstance;
8
- export type { ConfigureOptions, IndeRunCapacitorPluginContract as IndeRunCapacitorPlugin, OpenAIProviderBootstrapOptions, TaskRequest, TaskResult };
17
+ export { STREAM_ERROR_NAME, STREAM_EVENT_NAME } from "./streaming.js";
18
+ export type { CancelStreamOptions, ConfigureOptions, IndeRunCapacitorPluginContract as IndeRunCapacitorPlugin, OpenAIProviderBootstrapOptions, StartStreamOptions, StreamErrorNotification, StreamEventNotification, StreamRun, TaskRequest, TaskResult };
9
19
  export type * from "@independo/inderun-contracts";
10
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,IAAI,8BAA8B,EACxD,8BAA8B,EAC/B,MAAM,kBAAkB,CAAC;AAuB1B,eAAO,MAAM,gBAAgB,EAAE,8BAgB9B,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACvC,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAChD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,wBAAwB,CAiB3F;AAED,YAAY,EACV,gBAAgB,EAChB,8BAA8B,IAAI,sBAAsB,EACxD,8BAA8B,EAC9B,WAAW,EACX,UAAU,EACX,CAAC;AAEF,mBAAmB,8BAA8B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE7F,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,IAAI,8BAA8B,EACxD,8BAA8B,EAC9B,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,SAAS,EACV,MAAM,kBAAkB,CAAC;AAQ1B;;;;GAIG;AACH,MAAM,WAAW,mBAAoB,SAAQ,8BAA8B;IACzE,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CAClD;AAED,eAAO,MAAM,gBAAgB,EAAE,mBA4C9B,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACvC,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CAClD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,wBAAwB,CAsB3F;AAED,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEtE,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,8BAA8B,IAAI,sBAAsB,EACxD,8BAA8B,EAC9B,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,SAAS,EACT,WAAW,EACX,UAAU,EACX,CAAC;AAEF,mBAAmB,8BAA8B,CAAC"}
package/dist/index.js CHANGED
@@ -1,20 +1,9 @@
1
- import { validateIndeRunError } from "@independo/inderun-contracts";
2
1
  import { registerPlugin } from "@capacitor/core";
2
+ import { normalizePluginError } from "./errors.js";
3
+ import { startCapacitorStream } from "./streaming.js";
3
4
  const IndeRunCapacitorNative = registerPlugin("IndeRunCapacitor", {
4
5
  web: () => import("./web.js").then((module) => new module.IndeRunWeb())
5
6
  });
6
- function normalizePluginError(error) {
7
- if (validateIndeRunError(error)) {
8
- return error;
9
- }
10
- if (typeof error === "object" &&
11
- error !== null &&
12
- "data" in error &&
13
- validateIndeRunError(error.data)) {
14
- return error.data;
15
- }
16
- return error;
17
- }
18
7
  export const IndeRunCapacitor = {
19
8
  async configure(options) {
20
9
  try {
@@ -31,6 +20,29 @@ export const IndeRunCapacitor = {
31
20
  catch (error) {
32
21
  throw normalizePluginError(error);
33
22
  }
23
+ },
24
+ async startStream(options) {
25
+ try {
26
+ return await IndeRunCapacitorNative.startStream(options);
27
+ }
28
+ catch (error) {
29
+ throw normalizePluginError(error);
30
+ }
31
+ },
32
+ async cancelStream(options) {
33
+ try {
34
+ await IndeRunCapacitorNative.cancelStream(options);
35
+ }
36
+ catch (error) {
37
+ throw normalizePluginError(error);
38
+ }
39
+ },
40
+ addListener: IndeRunCapacitorNative.addListener.bind(IndeRunCapacitorNative),
41
+ async removeAllListeners() {
42
+ await IndeRunCapacitorNative.removeAllListeners();
43
+ },
44
+ stream(request) {
45
+ return startCapacitorStream(IndeRunCapacitorNative, request);
34
46
  }
35
47
  };
36
48
  export function createIndeRunCapacitor(options) {
@@ -46,6 +58,11 @@ export function createIndeRunCapacitor(options) {
46
58
  async run(request) {
47
59
  await ensureConfigured();
48
60
  return IndeRunCapacitor.run(request);
61
+ },
62
+ async stream(request) {
63
+ await ensureConfigured();
64
+ return IndeRunCapacitor.stream(request);
49
65
  }
50
66
  };
51
67
  }
68
+ export { STREAM_ERROR_NAME, STREAM_EVENT_NAME } from "./streaming.js";
@@ -0,0 +1,17 @@
1
+ import { type TaskRequest } from "@independo/inderun-contracts";
2
+ import type { IndeRunCapacitorPlugin, StreamRun } from "./definitions.js";
3
+ /**
4
+ * Listener event names. These are **public contract**: an app may attach its own
5
+ * listener, and native code emits exactly these. Renaming one is a breaking change.
6
+ */
7
+ export declare const STREAM_EVENT_NAME = "indeRunStreamEvent";
8
+ export declare const STREAM_ERROR_NAME = "indeRunStreamError";
9
+ /**
10
+ * Starts a Mode 2 run over the bridge and reassembles it into the same `StreamRun`
11
+ * shape `@independo/inderun-web` returns directly.
12
+ *
13
+ * The listeners are registered *before* `startStream` is called, so no event can be
14
+ * emitted for a run whose sink does not exist yet.
15
+ */
16
+ export declare function startCapacitorStream(plugin: IndeRunCapacitorPlugin, request: TaskRequest): Promise<StreamRun>;
17
+ //# sourceMappingURL=streaming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,sBAAsB,EAGtB,SAAS,EACV,MAAM,kBAAkB,CAAC;AAG1B;;;GAGG;AACH,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AACtD,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AA6QtD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,SAAS,CAAC,CAwDpB"}