@independo/capacitor-inderun 0.2.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/Package.swift +48 -0
- package/README.md +115 -0
- package/android/build.gradle.kts +38 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +3 -0
- package/android/gradlew +248 -0
- package/android/gradlew.bat +82 -0
- package/android/settings.gradle +21 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/kotlin/app/independo/inderun/capacitor/IndeRunCapacitorPlugin.kt +111 -0
- package/android/src/main/kotlin/app/independo/inderun/capacitor/IndeRunSerializer.kt +263 -0
- package/android/src/test/kotlin/app/independo/inderun/capacitor/IndeRunSerializerTest.kt +180 -0
- package/dist/definitions.d.ts +17 -0
- package/dist/definitions.d.ts.map +1 -0
- package/dist/definitions.js +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/web.d.ts +10 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +52 -0
- package/ios/Sources/IndeRunCapacitorPlugin/IndeRunCapacitorBridge.swift +105 -0
- package/ios/Sources/IndeRunCapacitorPlugin/IndeRunCapacitorPlugin.swift +51 -0
- package/ios/Tests/IndeRunCapacitorTests/IndeRunCapacitorBridgeTests.swift +123 -0
- package/package.json +77 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package app.independo.inderun.capacitor
|
|
2
|
+
|
|
3
|
+
import app.independo.inderun.core.IndeRunException
|
|
4
|
+
import app.independo.inderun.core.ProviderRegistry
|
|
5
|
+
import app.independo.inderun.core.toIndeRunException
|
|
6
|
+
import app.independo.inderun.providers.mlkit.AndroidProviderRegistryFactory
|
|
7
|
+
import app.independo.inderun.providers.openai.DEFAULT_OPENAI_RESPONSES_ENDPOINT
|
|
8
|
+
import app.independo.inderun.providers.openai.OpenAIAuthMode
|
|
9
|
+
import app.independo.inderun.providers.openai.OpenAIProvider
|
|
10
|
+
import app.independo.inderun.providers.openai.OpenAIProviderOptions
|
|
11
|
+
import app.independo.inderun.sdk.IndeRun
|
|
12
|
+
import com.getcapacitor.Plugin
|
|
13
|
+
import com.getcapacitor.PluginCall
|
|
14
|
+
import com.getcapacitor.PluginMethod
|
|
15
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
16
|
+
import kotlinx.coroutines.CoroutineScope
|
|
17
|
+
import kotlinx.coroutines.Dispatchers
|
|
18
|
+
import kotlinx.coroutines.SupervisorJob
|
|
19
|
+
import kotlinx.coroutines.cancel
|
|
20
|
+
import kotlinx.coroutines.launch
|
|
21
|
+
|
|
22
|
+
@CapacitorPlugin(name = "IndeRunCapacitor")
|
|
23
|
+
class IndeRunCapacitorPlugin : Plugin() {
|
|
24
|
+
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
|
25
|
+
private var configuredRegistry: ProviderRegistry? = null
|
|
26
|
+
|
|
27
|
+
@PluginMethod
|
|
28
|
+
fun configure(call: PluginCall) {
|
|
29
|
+
try {
|
|
30
|
+
val options = IndeRunSerializer.parseConfigureOptions(call.data)
|
|
31
|
+
configuredRegistry = createRegistry(options.openAI)
|
|
32
|
+
call.resolve()
|
|
33
|
+
} catch (error: IndeRunException) {
|
|
34
|
+
val contractError = error.toContractError()
|
|
35
|
+
call.reject(
|
|
36
|
+
contractError.message,
|
|
37
|
+
contractError.errorClass.rawValue,
|
|
38
|
+
null,
|
|
39
|
+
runCatching { IndeRunSerializer.encodeError(contractError) }.getOrNull()
|
|
40
|
+
)
|
|
41
|
+
} catch (error: Throwable) {
|
|
42
|
+
val normalized = toIndeRunException(error)
|
|
43
|
+
val contractError = normalized.toContractError()
|
|
44
|
+
call.reject(
|
|
45
|
+
contractError.message,
|
|
46
|
+
contractError.errorClass.rawValue,
|
|
47
|
+
null,
|
|
48
|
+
runCatching { IndeRunSerializer.encodeError(contractError) }.getOrNull()
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@PluginMethod
|
|
54
|
+
fun run(call: PluginCall) {
|
|
55
|
+
val requestJson = call.data
|
|
56
|
+
|
|
57
|
+
scope.launch {
|
|
58
|
+
try {
|
|
59
|
+
val registry = configuredRegistry
|
|
60
|
+
?: throw toIndeRunException(IllegalStateException("Capacitor IndeRun has not been configured. Configure providers before calling run(request)."))
|
|
61
|
+
val request = IndeRunSerializer.parseTaskRequest(requestJson)
|
|
62
|
+
// IndeRun is stateless; new per call is intentional — registry is cached after configure().
|
|
63
|
+
val result = IndeRun.initialize(context.applicationContext, registry).run(request)
|
|
64
|
+
call.resolve(IndeRunSerializer.encodeTaskResult(result))
|
|
65
|
+
} catch (error: IndeRunException) {
|
|
66
|
+
val contractError = error.toContractError()
|
|
67
|
+
call.reject(
|
|
68
|
+
contractError.message,
|
|
69
|
+
contractError.errorClass.rawValue,
|
|
70
|
+
null,
|
|
71
|
+
runCatching { IndeRunSerializer.encodeError(contractError) }.getOrNull()
|
|
72
|
+
)
|
|
73
|
+
} catch (error: Throwable) {
|
|
74
|
+
val normalized = toIndeRunException(error)
|
|
75
|
+
val contractError = normalized.toContractError()
|
|
76
|
+
call.reject(
|
|
77
|
+
contractError.message,
|
|
78
|
+
contractError.errorClass.rawValue,
|
|
79
|
+
null,
|
|
80
|
+
runCatching { IndeRunSerializer.encodeError(contractError) }.getOrNull()
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
override fun handleOnDestroy() {
|
|
87
|
+
scope.cancel()
|
|
88
|
+
super.handleOnDestroy()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private fun createRegistry(openAI: OpenAIProviderBootstrapOptions?): ProviderRegistry {
|
|
92
|
+
val registry = AndroidProviderRegistryFactory.makeDefaultRegistry(context.applicationContext)
|
|
93
|
+
|
|
94
|
+
if (openAI != null) {
|
|
95
|
+
registry.register(
|
|
96
|
+
OpenAIProvider(
|
|
97
|
+
OpenAIProviderOptions(
|
|
98
|
+
id = "openai",
|
|
99
|
+
model = openAI.model,
|
|
100
|
+
endpointUrl = openAI.endpointUrl ?: DEFAULT_OPENAI_RESPONSES_ENDPOINT,
|
|
101
|
+
auth = if (openAI.auth == "none") OpenAIAuthMode.none else OpenAIAuthMode.authContextRef,
|
|
102
|
+
authContextRef = openAI.authContextRef,
|
|
103
|
+
timeoutMs = openAI.timeoutMs
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return registry
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
package app.independo.inderun.capacitor
|
|
2
|
+
|
|
3
|
+
import app.independo.inderun.contracts.Cloud
|
|
4
|
+
import app.independo.inderun.contracts.FinishReason
|
|
5
|
+
import app.independo.inderun.contracts.Generation
|
|
6
|
+
import app.independo.inderun.contracts.IndeRunError
|
|
7
|
+
import app.independo.inderun.contracts.IndeRunErrorClass
|
|
8
|
+
import app.independo.inderun.contracts.Message
|
|
9
|
+
import app.independo.inderun.contracts.MessageRole
|
|
10
|
+
import app.independo.inderun.contracts.OptimizeFor
|
|
11
|
+
import app.independo.inderun.contracts.OutputType
|
|
12
|
+
import app.independo.inderun.contracts.PrivacyEnum
|
|
13
|
+
import app.independo.inderun.contracts.SchemaVersion
|
|
14
|
+
import app.independo.inderun.contracts.TaskKind
|
|
15
|
+
import app.independo.inderun.contracts.TaskRequest
|
|
16
|
+
import app.independo.inderun.contracts.TaskRequestConstraints
|
|
17
|
+
import app.independo.inderun.contracts.TaskRequestPreferences
|
|
18
|
+
import app.independo.inderun.contracts.TaskRequestTask
|
|
19
|
+
import app.independo.inderun.contracts.TaskRequestTelemetry
|
|
20
|
+
import app.independo.inderun.contracts.TaskResult
|
|
21
|
+
import app.independo.inderun.contracts.TaskResultTelemetry
|
|
22
|
+
import app.independo.inderun.contracts.TelemetryLevel
|
|
23
|
+
import app.independo.inderun.contracts.Usage
|
|
24
|
+
import com.getcapacitor.JSArray
|
|
25
|
+
import com.getcapacitor.JSObject
|
|
26
|
+
import org.json.JSONArray
|
|
27
|
+
import org.json.JSONObject
|
|
28
|
+
|
|
29
|
+
data class OpenAIProviderBootstrapOptions(
|
|
30
|
+
val model: String,
|
|
31
|
+
val endpointUrl: String? = null,
|
|
32
|
+
val auth: String? = null,
|
|
33
|
+
val authContextRef: String? = null,
|
|
34
|
+
val timeoutMs: Long? = null
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
data class RunOptions(
|
|
38
|
+
val openAI: OpenAIProviderBootstrapOptions? = null
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
object IndeRunSerializer {
|
|
42
|
+
fun parseConfigureOptions(json: JSONObject): RunOptions {
|
|
43
|
+
return RunOptions(
|
|
44
|
+
openAI = json.optJSONObject("openAI")?.let(::parseOpenAIOptions)
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun parseTaskRequest(json: JSONObject): TaskRequest {
|
|
49
|
+
return TaskRequest(
|
|
50
|
+
authContextRef = json.optStringOrNull("authContextRef"),
|
|
51
|
+
constraints = json.optJSONObject("constraints")?.let(::parseConstraints),
|
|
52
|
+
generation = json.optJSONObject("generation")?.let(::parseGeneration),
|
|
53
|
+
messages = json.optJSONArray("messages")?.let(::parseMessages),
|
|
54
|
+
preferences = json.optJSONObject("preferences")?.let(::parsePreferences),
|
|
55
|
+
prompt = json.optStringOrNull("prompt"),
|
|
56
|
+
requestId = json.optStringOrNull("requestId"),
|
|
57
|
+
schemaVersion = parseSchemaVersion(json.getString("schemaVersion")),
|
|
58
|
+
task = parseTask(json.getJSONObject("task")),
|
|
59
|
+
telemetry = json.optJSONObject("telemetry")?.let(::parseTelemetry)
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fun encodeTaskResult(result: TaskResult): JSObject {
|
|
64
|
+
return JSObject().apply {
|
|
65
|
+
put("schemaVersion", result.schemaVersion.rawValue)
|
|
66
|
+
put("runId", result.runId)
|
|
67
|
+
put("finishReason", result.finishReason.rawValue)
|
|
68
|
+
put(
|
|
69
|
+
"output",
|
|
70
|
+
JSObject()
|
|
71
|
+
.put("type", result.output.type.rawValue)
|
|
72
|
+
.put("text", result.output.text)
|
|
73
|
+
)
|
|
74
|
+
put(
|
|
75
|
+
"telemetry",
|
|
76
|
+
JSObject()
|
|
77
|
+
.put("providerUsed", result.telemetry.providerUsed)
|
|
78
|
+
.put("totalMs", result.telemetry.totalMs)
|
|
79
|
+
.apply {
|
|
80
|
+
result.telemetry.errorClass?.let { put("errorClass", it.rawValue) }
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
result.usage?.let { usage ->
|
|
84
|
+
put(
|
|
85
|
+
"usage",
|
|
86
|
+
JSObject().apply {
|
|
87
|
+
usage.inputTokens?.let { put("inputTokens", it) }
|
|
88
|
+
usage.outputTokens?.let { put("outputTokens", it) }
|
|
89
|
+
usage.totalTokens?.let { put("totalTokens", it) }
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fun encodeError(error: IndeRunError): JSObject {
|
|
97
|
+
return JSObject().apply {
|
|
98
|
+
put("schemaVersion", error.schemaVersion.rawValue)
|
|
99
|
+
put("errorClass", error.errorClass.rawValue)
|
|
100
|
+
put("message", error.message)
|
|
101
|
+
error.providerId?.let { put("providerId", it) }
|
|
102
|
+
error.retryable?.let { put("retryable", it) }
|
|
103
|
+
error.retryAfterMs?.let { put("retryAfterMs", it) }
|
|
104
|
+
error.runId?.let { put("runId", it) }
|
|
105
|
+
error.details?.let { put("details", JSONObject(it)) }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private fun parseOpenAIOptions(json: JSONObject): OpenAIProviderBootstrapOptions {
|
|
110
|
+
return OpenAIProviderBootstrapOptions(
|
|
111
|
+
model = json.getString("model"),
|
|
112
|
+
endpointUrl = json.optStringOrNull("endpointUrl"),
|
|
113
|
+
auth = json.optStringOrNull("auth"),
|
|
114
|
+
authContextRef = json.optStringOrNull("authContextRef"),
|
|
115
|
+
timeoutMs = json.optLongOrNull("timeoutMs")
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private fun parseConstraints(json: JSONObject): TaskRequestConstraints {
|
|
120
|
+
return TaskRequestConstraints(
|
|
121
|
+
cloud = json.optStringOrNull("cloud")?.let(::parseCloud),
|
|
122
|
+
privacy = json.optStringOrNull("privacy")?.let(::parsePrivacy),
|
|
123
|
+
timeoutMs = json.optLongOrNull("timeoutMs")
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private fun parseGeneration(json: JSONObject): Generation {
|
|
128
|
+
return Generation(
|
|
129
|
+
maxOutputTokens = json.optLongOrNull("maxOutputTokens"),
|
|
130
|
+
seed = json.optLongOrNull("seed"),
|
|
131
|
+
stop = json.optJSONArray("stop")?.toStringList(),
|
|
132
|
+
temperature = json.optDoubleOrNull("temperature"),
|
|
133
|
+
topP = json.optDoubleOrNull("topP")
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private fun parseMessages(json: JSONArray): List<Message> {
|
|
138
|
+
return buildList {
|
|
139
|
+
for (index in 0 until json.length()) {
|
|
140
|
+
val message = json.getJSONObject(index)
|
|
141
|
+
add(
|
|
142
|
+
Message(
|
|
143
|
+
role = parseRole(message.getString("role")),
|
|
144
|
+
content = message.getString("content")
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private fun parsePreferences(json: JSONObject): TaskRequestPreferences {
|
|
152
|
+
return TaskRequestPreferences(
|
|
153
|
+
optimizeFor = json.optStringOrNull("optimizeFor")?.let(::parseOptimizeFor)
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private fun parseTask(json: JSONObject): TaskRequestTask {
|
|
158
|
+
return TaskRequestTask(kind = parseTaskKind(json.getString("kind")))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun parseTelemetry(json: JSONObject): TaskRequestTelemetry {
|
|
162
|
+
return TaskRequestTelemetry(
|
|
163
|
+
consent = json.optBooleanOrNull("consent"),
|
|
164
|
+
level = json.optStringOrNull("level")?.let(::parseTelemetryLevel),
|
|
165
|
+
tags = json.optJSONObject("tags")?.toStringMap()
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private fun parseSchemaVersion(value: String): SchemaVersion {
|
|
170
|
+
return when (value) {
|
|
171
|
+
"1.0" -> SchemaVersion.V1_0
|
|
172
|
+
else -> throw IllegalArgumentException("Unsupported schemaVersion '$value'.")
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private fun parseCloud(value: String): Cloud {
|
|
177
|
+
return when (value) {
|
|
178
|
+
"allowed" -> Cloud.Allowed
|
|
179
|
+
"forbidden" -> Cloud.Forbidden
|
|
180
|
+
"required" -> Cloud.Required
|
|
181
|
+
else -> throw IllegalArgumentException("Unsupported cloud constraint '$value'.")
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private fun parsePrivacy(value: String): PrivacyEnum {
|
|
186
|
+
return when (value) {
|
|
187
|
+
"local_required" -> PrivacyEnum.LocalRequired
|
|
188
|
+
"local_preferred" -> PrivacyEnum.LocalPreferred
|
|
189
|
+
"cloud_allowed" -> PrivacyEnum.CloudAllowed
|
|
190
|
+
"cloud_required" -> PrivacyEnum.CloudRequired
|
|
191
|
+
else -> throw IllegalArgumentException("Unsupported privacy constraint '$value'.")
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private fun parseRole(value: String): MessageRole {
|
|
196
|
+
return when (value) {
|
|
197
|
+
"assistant" -> MessageRole.ASSISTANT
|
|
198
|
+
"system" -> MessageRole.SYSTEM
|
|
199
|
+
"user" -> MessageRole.USER
|
|
200
|
+
else -> throw IllegalArgumentException("Unsupported message role '$value'.")
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private fun parseOptimizeFor(value: String): OptimizeFor {
|
|
205
|
+
return when (value) {
|
|
206
|
+
"balanced" -> OptimizeFor.Balanced
|
|
207
|
+
"cost" -> OptimizeFor.Cost
|
|
208
|
+
"latency" -> OptimizeFor.Latency
|
|
209
|
+
"privacy" -> OptimizeFor.Privacy
|
|
210
|
+
else -> throw IllegalArgumentException("Unsupported optimizeFor '$value'.")
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private fun parseTaskKind(value: String): TaskKind {
|
|
215
|
+
return when (value) {
|
|
216
|
+
"text_to_text" -> TaskKind.TEXT_TO_TEXT
|
|
217
|
+
else -> throw IllegalArgumentException("Unsupported task kind '$value'.")
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private fun parseTelemetryLevel(value: String): TelemetryLevel {
|
|
222
|
+
return when (value) {
|
|
223
|
+
"debug" -> TelemetryLevel.DEBUG
|
|
224
|
+
"minimal" -> TelemetryLevel.MINIMAL
|
|
225
|
+
"off" -> TelemetryLevel.OFF
|
|
226
|
+
else -> throw IllegalArgumentException("Unsupported telemetry level '$value'.")
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private fun JSONObject.optStringOrNull(key: String): String? {
|
|
232
|
+
return if (has(key) && !isNull(key)) getString(key) else null
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private fun JSONObject.optLongOrNull(key: String): Long? {
|
|
236
|
+
return if (has(key) && !isNull(key)) getLong(key) else null
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private fun JSONObject.optDoubleOrNull(key: String): Double? {
|
|
240
|
+
return if (has(key) && !isNull(key)) getDouble(key) else null
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private fun JSONObject.optBooleanOrNull(key: String): Boolean? {
|
|
244
|
+
return if (has(key) && !isNull(key)) getBoolean(key) else null
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private fun JSONObject.toStringMap(): Map<String, String> {
|
|
248
|
+
val result = linkedMapOf<String, String>()
|
|
249
|
+
val keys = keys()
|
|
250
|
+
while (keys.hasNext()) {
|
|
251
|
+
val key = keys.next()
|
|
252
|
+
result[key] = getString(key)
|
|
253
|
+
}
|
|
254
|
+
return result
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private fun JSONArray.toStringList(): List<String> {
|
|
258
|
+
return buildList {
|
|
259
|
+
for (index in 0 until length()) {
|
|
260
|
+
add(getString(index))
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
package app.independo.inderun.capacitor
|
|
2
|
+
|
|
3
|
+
import app.independo.inderun.contracts.FinishReason
|
|
4
|
+
import app.independo.inderun.contracts.IndeRunError
|
|
5
|
+
import app.independo.inderun.contracts.IndeRunErrorClass
|
|
6
|
+
import app.independo.inderun.contracts.Output
|
|
7
|
+
import app.independo.inderun.contracts.SchemaVersion
|
|
8
|
+
import app.independo.inderun.contracts.TaskRequest
|
|
9
|
+
import app.independo.inderun.contracts.TaskRequestTask
|
|
10
|
+
import app.independo.inderun.contracts.TaskResult
|
|
11
|
+
import app.independo.inderun.contracts.TaskResultTelemetry
|
|
12
|
+
import org.json.JSONObject
|
|
13
|
+
import org.junit.Assert.assertEquals
|
|
14
|
+
import org.junit.Assert.assertFalse
|
|
15
|
+
import org.junit.Assert.assertNull
|
|
16
|
+
import org.junit.Test
|
|
17
|
+
|
|
18
|
+
class IndeRunSerializerTest {
|
|
19
|
+
@Test
|
|
20
|
+
fun `parseRunOptions decodes canonical request fields`() {
|
|
21
|
+
val request = IndeRunSerializer.parseTaskRequest(
|
|
22
|
+
JSONObject(
|
|
23
|
+
"""
|
|
24
|
+
{
|
|
25
|
+
"schemaVersion": "1.0",
|
|
26
|
+
"task": { "kind": "text_to_text" },
|
|
27
|
+
"prompt": "Hello",
|
|
28
|
+
"constraints": { "privacy": "cloud_required" }
|
|
29
|
+
}
|
|
30
|
+
""".trimIndent()
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
val options = IndeRunSerializer.parseConfigureOptions(
|
|
34
|
+
JSONObject(
|
|
35
|
+
"""
|
|
36
|
+
{
|
|
37
|
+
"openAI": {
|
|
38
|
+
"model": "gpt-5.2",
|
|
39
|
+
"endpointUrl": "/api/inderun/openai-responses",
|
|
40
|
+
"auth": "none"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
""".trimIndent()
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
assertEquals("Hello", request.prompt)
|
|
48
|
+
assertEquals("gpt-5.2", options.openAI?.model)
|
|
49
|
+
assertEquals("/api/inderun/openai-responses", options.openAI?.endpointUrl)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Test
|
|
53
|
+
fun `encodeTaskResult preserves canonical task result shape`() {
|
|
54
|
+
val encoded = IndeRunSerializer.encodeTaskResult(
|
|
55
|
+
TaskResult(
|
|
56
|
+
finishReason = FinishReason.STOP,
|
|
57
|
+
output = Output(text = "Hi"),
|
|
58
|
+
runId = "run_123",
|
|
59
|
+
schemaVersion = SchemaVersion.V1_0,
|
|
60
|
+
telemetry = TaskResultTelemetry(providerUsed = "openai", totalMs = 12.0)
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
assertEquals("1.0", encoded.getString("schemaVersion"))
|
|
65
|
+
assertEquals("stop", encoded.getString("finishReason"))
|
|
66
|
+
assertEquals("Hi", encoded.getJSONObject("output").getString("text"))
|
|
67
|
+
assertEquals("openai", encoded.getJSONObject("telemetry").getString("providerUsed"))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Unknown enum values throw ---
|
|
71
|
+
|
|
72
|
+
@Test(expected = IllegalArgumentException::class)
|
|
73
|
+
fun `parseTaskRequest throws on unknown schemaVersion`() {
|
|
74
|
+
IndeRunSerializer.parseTaskRequest(
|
|
75
|
+
JSONObject("""{"schemaVersion":"9.9","task":{"kind":"text_to_text"},"prompt":"Hello"}""")
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Test(expected = IllegalArgumentException::class)
|
|
80
|
+
fun `parseTaskRequest throws on unknown task kind`() {
|
|
81
|
+
IndeRunSerializer.parseTaskRequest(
|
|
82
|
+
JSONObject("""{"schemaVersion":"1.0","task":{"kind":"image_to_text"},"prompt":"Hello"}""")
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Test(expected = IllegalArgumentException::class)
|
|
87
|
+
fun `parseTaskRequest throws on unknown cloud constraint`() {
|
|
88
|
+
IndeRunSerializer.parseTaskRequest(
|
|
89
|
+
JSONObject("""{"schemaVersion":"1.0","task":{"kind":"text_to_text"},"prompt":"Hi","constraints":{"cloud":"maybe"}}""")
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Test(expected = IllegalArgumentException::class)
|
|
94
|
+
fun `parseTaskRequest throws on unknown privacy constraint`() {
|
|
95
|
+
IndeRunSerializer.parseTaskRequest(
|
|
96
|
+
JSONObject("""{"schemaVersion":"1.0","task":{"kind":"text_to_text"},"prompt":"Hi","constraints":{"privacy":"some_value"}}""")
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Test(expected = IllegalArgumentException::class)
|
|
101
|
+
fun `parseTaskRequest throws on unknown message role`() {
|
|
102
|
+
IndeRunSerializer.parseTaskRequest(
|
|
103
|
+
JSONObject("""{"schemaVersion":"1.0","task":{"kind":"text_to_text"},"prompt":"Hi","messages":[{"role":"moderator","content":"Hi"}]}""")
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@Test(expected = IllegalArgumentException::class)
|
|
108
|
+
fun `parseTaskRequest throws on unknown optimizeFor`() {
|
|
109
|
+
IndeRunSerializer.parseTaskRequest(
|
|
110
|
+
JSONObject("""{"schemaVersion":"1.0","task":{"kind":"text_to_text"},"prompt":"Hi","preferences":{"optimizeFor":"turbo"}}""")
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@Test(expected = IllegalArgumentException::class)
|
|
115
|
+
fun `parseTaskRequest throws on unknown telemetry level`() {
|
|
116
|
+
IndeRunSerializer.parseTaskRequest(
|
|
117
|
+
JSONObject("""{"schemaVersion":"1.0","task":{"kind":"text_to_text"},"prompt":"Hi","telemetry":{"level":"verbose"}}""")
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Absent optional fields ---
|
|
122
|
+
|
|
123
|
+
@Test
|
|
124
|
+
fun `parseTaskRequest accepts minimal request with all optionals absent`() {
|
|
125
|
+
val request = IndeRunSerializer.parseTaskRequest(
|
|
126
|
+
JSONObject("""{"schemaVersion":"1.0","task":{"kind":"text_to_text"},"prompt":"Hi"}""")
|
|
127
|
+
)
|
|
128
|
+
assertNull(request.authContextRef)
|
|
129
|
+
assertNull(request.constraints)
|
|
130
|
+
assertNull(request.generation)
|
|
131
|
+
assertNull(request.messages)
|
|
132
|
+
assertNull(request.preferences)
|
|
133
|
+
assertNull(request.requestId)
|
|
134
|
+
assertNull(request.telemetry)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@Test
|
|
138
|
+
fun `parseConfigureOptions accepts empty object`() {
|
|
139
|
+
val options = IndeRunSerializer.parseConfigureOptions(JSONObject("{}"))
|
|
140
|
+
assertNull(options.openAI)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// --- encodeError ---
|
|
144
|
+
|
|
145
|
+
@Test
|
|
146
|
+
fun `encodeError produces correct shape with required fields only`() {
|
|
147
|
+
val encoded = IndeRunSerializer.encodeError(
|
|
148
|
+
IndeRunError(
|
|
149
|
+
errorClass = IndeRunErrorClass.Unavailable,
|
|
150
|
+
message = "Not configured."
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
assertEquals("1.0", encoded.getString("schemaVersion"))
|
|
154
|
+
assertEquals("Unavailable", encoded.getString("errorClass"))
|
|
155
|
+
assertEquals("Not configured.", encoded.getString("message"))
|
|
156
|
+
assertFalse(encoded.has("providerId"))
|
|
157
|
+
assertFalse(encoded.has("retryable"))
|
|
158
|
+
assertFalse(encoded.has("retryAfterMs"))
|
|
159
|
+
assertFalse(encoded.has("runId"))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Test
|
|
163
|
+
fun `encodeError includes all optional fields when set`() {
|
|
164
|
+
val encoded = IndeRunSerializer.encodeError(
|
|
165
|
+
IndeRunError(
|
|
166
|
+
errorClass = IndeRunErrorClass.RateLimited,
|
|
167
|
+
message = "Rate limit exceeded.",
|
|
168
|
+
providerId = "openai",
|
|
169
|
+
retryable = true,
|
|
170
|
+
retryAfterMs = 2000L,
|
|
171
|
+
runId = "run_abc"
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
assertEquals("RateLimited", encoded.getString("errorClass"))
|
|
175
|
+
assertEquals("openai", encoded.getString("providerId"))
|
|
176
|
+
assertEquals(true, encoded.getBoolean("retryable"))
|
|
177
|
+
assertEquals(2000L, encoded.getLong("retryAfterMs"))
|
|
178
|
+
assertEquals("run_abc", encoded.getString("runId"))
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TaskRequest, TaskResult } from "@independo/inderun-contracts";
|
|
2
|
+
export interface OpenAIProviderBootstrapOptions {
|
|
3
|
+
model: string;
|
|
4
|
+
endpointUrl?: string;
|
|
5
|
+
auth?: "authContextRef" | "none";
|
|
6
|
+
authContextRef?: string;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ConfigureOptions {
|
|
10
|
+
openAI?: OpenAIProviderBootstrapOptions;
|
|
11
|
+
allowDirectOpenAIEndpoint?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface IndeRunCapacitorPlugin {
|
|
14
|
+
configure(options?: ConfigureOptions): Promise<void>;
|
|
15
|
+
run(request: TaskRequest): Promise<TaskResult>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=definitions.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
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;
|
|
4
|
+
export interface IndeRunCapacitorInstance {
|
|
5
|
+
run(request: TaskRequest): Promise<TaskResult>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createIndeRunCapacitor(options?: ConfigureOptions): IndeRunCapacitorInstance;
|
|
8
|
+
export type { ConfigureOptions, IndeRunCapacitorPluginContract as IndeRunCapacitorPlugin, OpenAIProviderBootstrapOptions, TaskRequest, TaskResult };
|
|
9
|
+
export type * from "@independo/inderun-contracts";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { validateIndeRunError } from "@independo/inderun-contracts";
|
|
2
|
+
import { registerPlugin } from "@capacitor/core";
|
|
3
|
+
const IndeRunCapacitorNative = registerPlugin("IndeRunCapacitor", {
|
|
4
|
+
web: () => import("./web.js").then((module) => new module.IndeRunWeb())
|
|
5
|
+
});
|
|
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
|
+
export const IndeRunCapacitor = {
|
|
19
|
+
async configure(options) {
|
|
20
|
+
try {
|
|
21
|
+
await IndeRunCapacitorNative.configure(options);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw normalizePluginError(error);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
async run(request) {
|
|
28
|
+
try {
|
|
29
|
+
return await IndeRunCapacitorNative.run(request);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw normalizePluginError(error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export function createIndeRunCapacitor(options) {
|
|
37
|
+
let configured = null;
|
|
38
|
+
function ensureConfigured() {
|
|
39
|
+
configured ??= IndeRunCapacitor.configure(options).catch((error) => {
|
|
40
|
+
configured = null;
|
|
41
|
+
throw error;
|
|
42
|
+
});
|
|
43
|
+
return configured;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
async run(request) {
|
|
47
|
+
await ensureConfigured();
|
|
48
|
+
return IndeRunCapacitor.run(request);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
package/dist/web.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TaskResult } from "@independo/inderun-contracts";
|
|
2
|
+
import { WebPlugin } from "@capacitor/core";
|
|
3
|
+
import type { ConfigureOptions, IndeRunCapacitorPlugin } from "./definitions.js";
|
|
4
|
+
import type { TaskRequest } from "@independo/inderun-contracts";
|
|
5
|
+
export declare class IndeRunWeb extends WebPlugin implements IndeRunCapacitorPlugin {
|
|
6
|
+
private engine;
|
|
7
|
+
configure(options?: ConfigureOptions): Promise<void>;
|
|
8
|
+
run(request: TaskRequest): Promise<TaskResult>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAGhE,qBAAa,UAAW,SAAQ,SAAU,YAAW,sBAAsB;IACzE,OAAO,CAAC,MAAM,CAAwB;IAEhC,SAAS,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BpD,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;CAarD"}
|