@elizaos/capacitor-agent 2.0.0-beta.1 → 2.0.3-beta.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.
- package/LICENSE +21 -0
- package/README.md +145 -0
- package/android/build.gradle +16 -2
- package/android/src/main/java/ai/eliza/plugins/agent/AgentPlugin.kt +160 -63
- package/dist/esm/definitions.d.ts +60 -2
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/definitions.js +4 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +3 -2
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +81 -6
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +82 -7
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +82 -7
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/AgentPlugin/AgentPlugin.swift +381 -29
- package/package.json +13 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shaw Walters and elizaOS Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# @elizaos/capacitor-agent
|
|
2
|
+
|
|
3
|
+
Capacitor plugin for managing an embedded Eliza agent runtime from a WebView-based app. Provides a uniform `Agent.*` JavaScript API across iOS, Android, and web/desktop, with platform-specific native bridges and an HTTP-based web fallback.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- **Start / stop** the agent runtime and poll its state.
|
|
8
|
+
- **Send chat messages** (DM channel) and receive agent replies.
|
|
9
|
+
- **Forward arbitrary HTTP requests** to the local agent API server via a path-only bridge.
|
|
10
|
+
- **Read the per-boot bearer token** on Android local deployments.
|
|
11
|
+
|
|
12
|
+
The plugin handles three deployment shapes automatically:
|
|
13
|
+
|
|
14
|
+
| Platform | Mechanism |
|
|
15
|
+
|---|---|
|
|
16
|
+
| iOS remote/cloud | HTTP to a configured API endpoint (reads `ELIZA_IOS_API_BASE` or equivalent) |
|
|
17
|
+
| iOS local / sideload | WebView ITTP bridge (`window.__ELIZA_IOS_LOCAL_AGENT_REQUEST__`) |
|
|
18
|
+
| Android local | Reflection call into `ElizaAgentService` in the host app |
|
|
19
|
+
| Web / Electrobun | HTTP fetch to `window.__ELIZA_API_BASE__` or relative URLs |
|
|
20
|
+
|
|
21
|
+
## Capacitor methods
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Agent } from "@elizaos/capacitor-agent";
|
|
25
|
+
|
|
26
|
+
// Start the agent
|
|
27
|
+
const status = await Agent.start({ mode: "cloud" });
|
|
28
|
+
|
|
29
|
+
// Get status
|
|
30
|
+
const status = await Agent.getStatus();
|
|
31
|
+
// status.state: "not_started" | "starting" | "running" | "stopped" | "error"
|
|
32
|
+
|
|
33
|
+
// Chat
|
|
34
|
+
const reply = await Agent.chat({ text: "Hello" });
|
|
35
|
+
// reply.text, reply.agentName
|
|
36
|
+
|
|
37
|
+
// Forward a request
|
|
38
|
+
const result = await Agent.request({
|
|
39
|
+
path: "/api/status",
|
|
40
|
+
method: "GET",
|
|
41
|
+
timeoutMs: 5000,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Stop the agent
|
|
45
|
+
await Agent.stop();
|
|
46
|
+
|
|
47
|
+
// Read local agent token (Android only)
|
|
48
|
+
const { available, token } = await Agent.getLocalAgentToken();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
This is a Capacitor plugin distributed as part of the elizaOS monorepo. Add it as a dependency in your Capacitor app, then run `npx cap sync` to install the native modules.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install @elizaos/capacitor-agent
|
|
57
|
+
npx cap sync
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### iOS (CocoaPods)
|
|
61
|
+
|
|
62
|
+
The pod is named `ElizaosCapacitorAgent`. It is registered automatically via `capacitor.config` after `pod install`. Minimum deployment target: **iOS 13.0** (note: local ITTP mode requires iOS 14+).
|
|
63
|
+
|
|
64
|
+
### Android
|
|
65
|
+
|
|
66
|
+
The plugin is registered automatically. The host app must implement `ElizaAgentService` and register it in `AndroidManifest.xml`. The plugin locates it via reflection (no direct Gradle dependency).
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
### iOS endpoint (remote/cloud mode)
|
|
71
|
+
|
|
72
|
+
Set one of the following in your `capacitor.config.json` (under the `Agent` plugin key), `Info.plist`, or environment:
|
|
73
|
+
|
|
74
|
+
- `ELIZA_AGENT_API_BASE` / `ELIZA_IOS_API_BASE` / `ELIZA_MOBILE_API_BASE` — HTTP/HTTPS base URL of the agent API server
|
|
75
|
+
- `ELIZA_AGENT_API_TOKEN` / `ELIZA_IOS_API_TOKEN` / `ELIZA_MOBILE_API_TOKEN` — optional bearer token
|
|
76
|
+
- `ELIZA_IOS_RUNTIME_MODE` / `VITE_ELIZA_IOS_RUNTIME_MODE` — set to `local` / `ios-local` / `sideload-local` to activate local ITTP mode instead of HTTP
|
|
77
|
+
|
|
78
|
+
Example `capacitor.config.json` fragment:
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"plugins": {
|
|
82
|
+
"Agent": {
|
|
83
|
+
"apiBase": "https://your-eliza-host.example.com",
|
|
84
|
+
"apiToken": "your-bearer-token"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Web / Electrobun
|
|
91
|
+
|
|
92
|
+
- `window.__ELIZA_API_BASE__` — API server base URL; falls back to relative URLs on `http:`/`https:` origins.
|
|
93
|
+
- `window.__ELIZA_API_TOKEN__` — bearer token; falls back to `sessionStorage.eliza_api_token`.
|
|
94
|
+
|
|
95
|
+
## Exported types
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
interface AgentStatus {
|
|
99
|
+
state: "not_started" | "starting" | "running" | "stopped" | "error";
|
|
100
|
+
agentName: string | null;
|
|
101
|
+
port: number | null;
|
|
102
|
+
startedAt: number | null; // epoch ms
|
|
103
|
+
error: string | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface ChatResult {
|
|
107
|
+
text: string;
|
|
108
|
+
agentName: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface AgentStartOptions {
|
|
112
|
+
apiBase?: string;
|
|
113
|
+
mode?: "remote-mac" | "cloud" | "cloud-hybrid" | "local" | string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface AgentRequestOptions {
|
|
117
|
+
path: string; // must start with /, not an absolute URL
|
|
118
|
+
method?: string;
|
|
119
|
+
headers?: Record<string, string>;
|
|
120
|
+
body?: string | null;
|
|
121
|
+
timeoutMs?: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface AgentRequestResult {
|
|
125
|
+
status: number;
|
|
126
|
+
statusText: string;
|
|
127
|
+
headers: Record<string, string>;
|
|
128
|
+
body: string;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Building
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
bun run --cwd plugins/plugin-native-agent build # tsc + rollup → dist/
|
|
136
|
+
bun run --cwd plugins/plugin-native-agent watch # tsc --watch
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Limitations
|
|
140
|
+
|
|
141
|
+
- `Agent.request` only accepts path-only URLs (must start with `/`). Absolute URLs are rejected by all implementations.
|
|
142
|
+
- Request and response bodies are capped at 10 MB.
|
|
143
|
+
- iOS local mode (`Agent.chat` / `Agent.request` via ITTP) requires `window.__ELIZA_IOS_LOCAL_AGENT_REQUEST__` to be installed by the host WebView. If it is absent, a 503 is returned.
|
|
144
|
+
- The Android bridge uses reflection; renaming or unregistering `ElizaAgentService` breaks all Android calls silently at runtime.
|
|
145
|
+
- The `chat` method maintains one conversation per session (lazily created via `POST /api/conversations`). The conversation ID is not persisted across app restarts.
|
package/android/build.gradle
CHANGED
|
@@ -4,6 +4,16 @@ ext {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
apply plugin: 'com.android.library'
|
|
7
|
+
// Explicitly apply the Kotlin Android plugin. The kotlin-gradle-plugin is on
|
|
8
|
+
// the root buildscript classpath, but without applying it here AGP 8.13 falls
|
|
9
|
+
// back to its "built-in Kotlin" compile path (build/intermediates/
|
|
10
|
+
// built_in_kotlinc), which compiles the .kt sources but does NOT bundle the
|
|
11
|
+
// resulting .class files into the *release* library jar. The app's
|
|
12
|
+
// :app:assembleRelease then links a library AAR with zero plugin classes, so
|
|
13
|
+
// the Capacitor plugin (and any manifest-declared component) is absent from
|
|
14
|
+
// the release dex. Applying the standard Kotlin plugin wires Kotlin
|
|
15
|
+
// compilation into both the debug and release jar-bundling tasks.
|
|
16
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
7
17
|
android {
|
|
8
18
|
namespace = "ai.eliza.plugins.agent"
|
|
9
19
|
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
|
|
@@ -12,8 +22,12 @@ android {
|
|
|
12
22
|
targetSdk project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
|
|
13
23
|
}
|
|
14
24
|
compileOptions {
|
|
15
|
-
sourceCompatibility JavaVersion.
|
|
16
|
-
targetCompatibility JavaVersion.
|
|
25
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
26
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
kotlinOptions {
|
|
30
|
+
jvmTarget = "21"
|
|
17
31
|
}
|
|
18
32
|
}
|
|
19
33
|
|
|
@@ -5,22 +5,20 @@ import com.getcapacitor.Plugin
|
|
|
5
5
|
import com.getcapacitor.PluginCall
|
|
6
6
|
import com.getcapacitor.PluginMethod
|
|
7
7
|
import com.getcapacitor.annotation.CapacitorPlugin
|
|
8
|
-
import
|
|
9
|
-
import java.net.HttpURLConnection
|
|
10
|
-
import java.net.URL
|
|
8
|
+
import android.content.pm.PackageManager
|
|
11
9
|
import java.util.Locale
|
|
12
10
|
import org.json.JSONObject
|
|
13
11
|
|
|
14
|
-
private const val LOCAL_AGENT_BASE_URL = "http://127.0.0.1:31337"
|
|
15
12
|
private const val MAX_REQUEST_BODY_BYTES = 10 * 1024 * 1024
|
|
16
|
-
private const val
|
|
13
|
+
private const val DEFAULT_REQUEST_TIMEOUT_MS = 10_000
|
|
14
|
+
private const val MAX_REQUEST_TIMEOUT_MS = 600_000
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
17
|
* Eliza Agent Plugin — Android bridge.
|
|
20
18
|
*
|
|
21
19
|
* The app module owns ElizaAgentService, so this library uses reflection to
|
|
22
|
-
* avoid a Gradle dependency cycle while still exposing the per-boot
|
|
23
|
-
*
|
|
20
|
+
* avoid a Gradle dependency cycle while still exposing the per-boot bearer
|
|
21
|
+
* token and request dispatch surface to the WebView.
|
|
24
22
|
*/
|
|
25
23
|
@CapacitorPlugin(name = "Agent")
|
|
26
24
|
class AgentPlugin : Plugin() {
|
|
@@ -80,7 +78,7 @@ class AgentPlugin : Plugin() {
|
|
|
80
78
|
@PluginMethod
|
|
81
79
|
fun request(call: PluginCall) {
|
|
82
80
|
val path = call.getString("path")?.trim()
|
|
83
|
-
if (path == null || !
|
|
81
|
+
if (path == null || !isSafeLocalPath(path)) {
|
|
84
82
|
call.reject("Agent.request requires a local path that starts with /")
|
|
85
83
|
return
|
|
86
84
|
}
|
|
@@ -91,7 +89,8 @@ class AgentPlugin : Plugin() {
|
|
|
91
89
|
return
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
val timeoutMs = (call.getInt("timeoutMs") ?:
|
|
92
|
+
val timeoutMs = (call.getInt("timeoutMs") ?: DEFAULT_REQUEST_TIMEOUT_MS)
|
|
93
|
+
.coerceIn(1_000, MAX_REQUEST_TIMEOUT_MS)
|
|
95
94
|
val body = call.getString("body")
|
|
96
95
|
val headers = call.getObject("headers") ?: JSObject()
|
|
97
96
|
val token = readLocalAgentToken()
|
|
@@ -101,7 +100,80 @@ class AgentPlugin : Plugin() {
|
|
|
101
100
|
val result = forwardLocalRequest(path, method, headers, body, timeoutMs, token)
|
|
102
101
|
call.resolve(result)
|
|
103
102
|
} catch (error: Exception) {
|
|
104
|
-
call.
|
|
103
|
+
call.resolve(localAgentUnavailableResponse(error.message ?: "Local agent request failed"))
|
|
104
|
+
}
|
|
105
|
+
}.start()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Streaming variant of [request]. Where [request] buffers the whole loopback
|
|
110
|
+
* response, this pushes it incrementally: the service reads the response
|
|
111
|
+
* stream and hands us per-fragment JSON envelopes, which we forward to the
|
|
112
|
+
* WebView as `agentStream*` Capacitor events tagged with a per-request
|
|
113
|
+
* `streamId`. Resolves immediately with the `streamId`; events follow.
|
|
114
|
+
*/
|
|
115
|
+
@PluginMethod
|
|
116
|
+
fun requestStream(call: PluginCall) {
|
|
117
|
+
val path = call.getString("path")?.trim()
|
|
118
|
+
if (path == null || !isSafeLocalPath(path)) {
|
|
119
|
+
call.reject("Agent.requestStream requires a local path that starts with /")
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
val method = (call.getString("method") ?: "GET").trim().uppercase(Locale.US)
|
|
123
|
+
if (!method.matches(Regex("^[A-Z]{1,16}$"))) {
|
|
124
|
+
call.reject("Unsupported HTTP method")
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
val timeoutMs = (call.getInt("timeoutMs") ?: DEFAULT_REQUEST_TIMEOUT_MS)
|
|
128
|
+
.coerceIn(1_000, MAX_REQUEST_TIMEOUT_MS)
|
|
129
|
+
val body = call.getString("body")
|
|
130
|
+
val headers = call.getObject("headers") ?: JSObject()
|
|
131
|
+
val streamId = java.util.UUID.randomUUID().toString()
|
|
132
|
+
|
|
133
|
+
val requestJson = JSONObject().apply {
|
|
134
|
+
put("method", method)
|
|
135
|
+
put("path", path)
|
|
136
|
+
put("headers", JSONObject(headers.toString()))
|
|
137
|
+
put("body", body ?: JSONObject.NULL)
|
|
138
|
+
put("timeoutMs", timeoutMs)
|
|
139
|
+
}.toString()
|
|
140
|
+
|
|
141
|
+
val onEvent = java.util.function.Consumer<String> { eventJson ->
|
|
142
|
+
try {
|
|
143
|
+
val event = JSONObject(eventJson)
|
|
144
|
+
when (event.optString("type")) {
|
|
145
|
+
"response" -> notifyListeners("agentStreamResponse", JSObject().apply {
|
|
146
|
+
put("streamId", streamId)
|
|
147
|
+
put("status", event.optInt("status"))
|
|
148
|
+
put("statusText", event.optString("statusText"))
|
|
149
|
+
put("headers", event.optJSONObject("headers") ?: JSONObject())
|
|
150
|
+
})
|
|
151
|
+
"chunk" -> notifyListeners("agentStreamChunk", JSObject().apply {
|
|
152
|
+
put("streamId", streamId)
|
|
153
|
+
put("dataBase64", event.optString("dataBase64"))
|
|
154
|
+
})
|
|
155
|
+
"complete" -> notifyListeners("agentStreamComplete", JSObject().apply {
|
|
156
|
+
put("streamId", streamId)
|
|
157
|
+
if (event.has("error")) put("error", event.optString("error"))
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
} catch (_: Exception) {
|
|
161
|
+
// A malformed envelope shouldn't kill the stream; drop it.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Resolve before the work starts so the WebView can attach listeners for
|
|
166
|
+
// this streamId; the service emits on the background thread below.
|
|
167
|
+
call.resolve(JSObject().apply { put("streamId", streamId) })
|
|
168
|
+
|
|
169
|
+
Thread {
|
|
170
|
+
try {
|
|
171
|
+
invokeAgentServiceRequestStream(requestJson, onEvent)
|
|
172
|
+
} catch (error: Exception) {
|
|
173
|
+
notifyListeners("agentStreamComplete", JSObject().apply {
|
|
174
|
+
put("streamId", streamId)
|
|
175
|
+
put("error", error.message ?: "Local agent stream failed")
|
|
176
|
+
})
|
|
105
177
|
}
|
|
106
178
|
}.start()
|
|
107
179
|
}
|
|
@@ -117,14 +189,28 @@ class AgentPlugin : Plugin() {
|
|
|
117
189
|
}
|
|
118
190
|
|
|
119
191
|
private fun invokeAgentService(methodName: String) {
|
|
120
|
-
val serviceClass = Class.forName(
|
|
192
|
+
val serviceClass = Class.forName(resolveAgentServiceClassName())
|
|
121
193
|
val method = serviceClass.getMethod(methodName, android.content.Context::class.java)
|
|
122
194
|
method.invoke(null, context)
|
|
123
195
|
}
|
|
124
196
|
|
|
197
|
+
private fun localAgentUnavailableResponse(message: String): JSObject {
|
|
198
|
+
return JSObject().apply {
|
|
199
|
+
put("status", 503)
|
|
200
|
+
put("statusText", "Service Unavailable")
|
|
201
|
+
put("headers", JSObject().apply {
|
|
202
|
+
put("content-type", "application/json")
|
|
203
|
+
})
|
|
204
|
+
put("body", JSONObject().apply {
|
|
205
|
+
put("error", "local_agent_unavailable")
|
|
206
|
+
put("message", message)
|
|
207
|
+
}.toString())
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
125
211
|
private fun readLocalAgentToken(): String? {
|
|
126
212
|
return try {
|
|
127
|
-
val serviceClass = Class.forName(
|
|
213
|
+
val serviceClass = Class.forName(resolveAgentServiceClassName())
|
|
128
214
|
val method = serviceClass.getMethod("localAgentToken")
|
|
129
215
|
(method.invoke(null) as? String)?.trim()?.takeIf { it.isNotEmpty() }
|
|
130
216
|
} catch (_: Exception) {
|
|
@@ -145,67 +231,78 @@ class AgentPlugin : Plugin() {
|
|
|
145
231
|
throw IllegalArgumentException("Request body is too large")
|
|
146
232
|
}
|
|
147
233
|
|
|
148
|
-
val
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
key.equals("connection", ignoreCase = true) ||
|
|
159
|
-
key.equals("content-length", ignoreCase = true)
|
|
160
|
-
) {
|
|
161
|
-
continue
|
|
162
|
-
}
|
|
163
|
-
val value = headers.opt(key) as? String
|
|
164
|
-
if (!value.isNullOrBlank()) {
|
|
165
|
-
connection.setRequestProperty(key, value)
|
|
234
|
+
val request = JSONObject().apply {
|
|
235
|
+
put("method", method)
|
|
236
|
+
put("path", path)
|
|
237
|
+
put("headers", headers)
|
|
238
|
+
put("body", body ?: JSONObject.NULL)
|
|
239
|
+
put("timeoutMs", timeoutMs)
|
|
240
|
+
if (!token.isNullOrBlank() && !hasHeader(headers, "authorization")) {
|
|
241
|
+
put("headers", JSONObject(headers.toString()).apply {
|
|
242
|
+
put("Authorization", "Bearer $token")
|
|
243
|
+
})
|
|
166
244
|
}
|
|
167
245
|
}
|
|
246
|
+
val raw = invokeAgentServiceRequest(request.toString())
|
|
247
|
+
return jsObjectFromJson(JSONObject(raw))
|
|
248
|
+
}
|
|
168
249
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
250
|
+
private fun invokeAgentServiceRequest(requestJson: String): String {
|
|
251
|
+
val serviceClass = Class.forName(resolveAgentServiceClassName())
|
|
252
|
+
val method = serviceClass.getMethod("requestLocalAgent", String::class.java)
|
|
253
|
+
return method.invoke(null, requestJson) as? String
|
|
254
|
+
?: throw IllegalStateException("ElizaAgentService.requestLocalAgent returned null")
|
|
255
|
+
}
|
|
172
256
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
257
|
+
private fun invokeAgentServiceRequestStream(
|
|
258
|
+
requestJson: String,
|
|
259
|
+
onEvent: java.util.function.Consumer<String>,
|
|
260
|
+
) {
|
|
261
|
+
val serviceClass = Class.forName(resolveAgentServiceClassName())
|
|
262
|
+
val method = serviceClass.getMethod(
|
|
263
|
+
"requestLocalAgentStream",
|
|
264
|
+
String::class.java,
|
|
265
|
+
java.util.function.Consumer::class.java,
|
|
266
|
+
)
|
|
267
|
+
method.invoke(null, requestJson, onEvent)
|
|
268
|
+
}
|
|
179
269
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
270
|
+
private fun isSafeLocalPath(path: String): Boolean {
|
|
271
|
+
return path.startsWith("/") && !path.startsWith("//") && !path.contains("://")
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private fun resolveAgentServiceClassName(): String {
|
|
275
|
+
val ctx = context ?: throw IllegalStateException("Android context is unavailable")
|
|
276
|
+
val packageInfo = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
|
|
277
|
+
ctx.packageManager.getPackageInfo(
|
|
278
|
+
ctx.packageName,
|
|
279
|
+
PackageManager.PackageInfoFlags.of(PackageManager.GET_SERVICES.toLong()),
|
|
280
|
+
)
|
|
281
|
+
} else {
|
|
282
|
+
@Suppress("DEPRECATION")
|
|
283
|
+
ctx.packageManager.getPackageInfo(ctx.packageName, PackageManager.GET_SERVICES)
|
|
284
|
+
}
|
|
285
|
+
return packageInfo.services
|
|
286
|
+
?.firstOrNull {
|
|
287
|
+
it.packageName == ctx.packageName && it.name.endsWith(".ElizaAgentService")
|
|
194
288
|
}
|
|
195
|
-
|
|
196
|
-
|
|
289
|
+
?.name
|
|
290
|
+
?: throw IllegalStateException("ElizaAgentService is not registered")
|
|
291
|
+
}
|
|
197
292
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
293
|
+
private fun hasHeader(headers: JSObject, expected: String): Boolean {
|
|
294
|
+
val keys = headers.keys()
|
|
295
|
+
while (keys.hasNext()) {
|
|
296
|
+
if (expected.equals(keys.next(), ignoreCase = true)) return true
|
|
202
297
|
}
|
|
298
|
+
return false
|
|
299
|
+
}
|
|
203
300
|
|
|
301
|
+
private fun jsObjectFromJson(json: JSONObject): JSObject {
|
|
204
302
|
return JSObject().apply {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
put("body", responseBody)
|
|
303
|
+
for (key in json.keys()) {
|
|
304
|
+
put(key, json.opt(key))
|
|
305
|
+
}
|
|
209
306
|
}
|
|
210
307
|
}
|
|
211
308
|
}
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* communicating with the embedded Eliza agent.
|
|
6
6
|
*
|
|
7
7
|
* - Electrobun desktop: RPC to the main-process AgentManager
|
|
8
|
-
* -
|
|
8
|
+
* - Android/Web: HTTP calls to the API server or bundled loopback agent
|
|
9
|
+
* - iOS: HTTP for remote/cloud endpoints; local dev/sideload foreground
|
|
10
|
+
* requests bridge into the WebView ITTP kernel until the native route-kernel
|
|
11
|
+
* backend lands
|
|
9
12
|
*/
|
|
10
13
|
export interface AgentStatus {
|
|
11
14
|
state: "not_started" | "starting" | "running" | "stopped" | "error";
|
|
@@ -22,6 +25,16 @@ export interface LocalAgentTokenResult {
|
|
|
22
25
|
available: boolean;
|
|
23
26
|
token: string | null;
|
|
24
27
|
}
|
|
28
|
+
export interface AgentStartOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Optional API base for native shells that need an explicit endpoint.
|
|
31
|
+
* Android local uses loopback; iOS local dev/sideload builds use the same
|
|
32
|
+
* URL shape as a stable identity but route app requests through ITTP.
|
|
33
|
+
*/
|
|
34
|
+
apiBase?: string;
|
|
35
|
+
/** Runtime mode hint for native shells that cannot read WebView storage. */
|
|
36
|
+
mode?: "remote-mac" | "cloud" | "cloud-hybrid" | "local" | string;
|
|
37
|
+
}
|
|
25
38
|
export interface AgentRequestOptions {
|
|
26
39
|
method?: string;
|
|
27
40
|
path: string;
|
|
@@ -35,9 +48,36 @@ export interface AgentRequestResult {
|
|
|
35
48
|
headers: Record<string, string>;
|
|
36
49
|
body: string;
|
|
37
50
|
}
|
|
51
|
+
/** Handle returned by `addListener`; call `.remove()` to detach. */
|
|
52
|
+
export interface AgentPluginListenerHandle {
|
|
53
|
+
remove: () => Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
/** Acknowledgement of a started streaming request. */
|
|
56
|
+
export interface AgentStreamHandle {
|
|
57
|
+
/** Correlates the `agentStream*` events for THIS request. */
|
|
58
|
+
streamId: string;
|
|
59
|
+
}
|
|
60
|
+
/** First `agentStreamResponse` event — the response head, before any body. */
|
|
61
|
+
export interface AgentStreamResponseEvent {
|
|
62
|
+
streamId: string;
|
|
63
|
+
status: number;
|
|
64
|
+
statusText: string;
|
|
65
|
+
headers: Record<string, string>;
|
|
66
|
+
}
|
|
67
|
+
/** A body chunk as it arrives. `dataBase64` is the lossless raw bytes (base64)
|
|
68
|
+
* so SSE frames / binary survive the bridge; decode + enqueue verbatim. */
|
|
69
|
+
export interface AgentStreamChunkEvent {
|
|
70
|
+
streamId: string;
|
|
71
|
+
dataBase64: string;
|
|
72
|
+
}
|
|
73
|
+
/** Terminal event for a stream — `error` set only on failure. */
|
|
74
|
+
export interface AgentStreamCompleteEvent {
|
|
75
|
+
streamId: string;
|
|
76
|
+
error?: string | null;
|
|
77
|
+
}
|
|
38
78
|
export interface AgentPlugin {
|
|
39
79
|
/** Start the agent runtime. Resolves when it's ready. */
|
|
40
|
-
start(): Promise<AgentStatus>;
|
|
80
|
+
start(options?: AgentStartOptions): Promise<AgentStatus>;
|
|
41
81
|
/** Stop the agent runtime. */
|
|
42
82
|
stop(): Promise<{
|
|
43
83
|
ok: boolean;
|
|
@@ -56,7 +96,25 @@ export interface AgentPlugin {
|
|
|
56
96
|
* Native implementations must reject absolute URLs and route only to the
|
|
57
97
|
* app-owned local backend. This is a transitional transport before the
|
|
58
98
|
* backend route kernel can run over Binder/LocalSocket/WKURLSchemeHandler.
|
|
99
|
+
* On iOS local dev/sideload builds this requires the WebView ITTP bridge to
|
|
100
|
+
* be installed, so it is a foreground-only path.
|
|
59
101
|
*/
|
|
60
102
|
request?(options: AgentRequestOptions): Promise<AgentRequestResult>;
|
|
103
|
+
/**
|
|
104
|
+
* STREAMING variant of {@link request}. Where `request` buffers the whole
|
|
105
|
+
* response into one string (so SSE token frames arrive all at once — the chat
|
|
106
|
+
* reply never streams on mobile), `requestStream` opens the same loopback
|
|
107
|
+
* request and pushes the response incrementally via `agentStream*` events,
|
|
108
|
+
* letting the WebView reconstruct a real streaming `Response`.
|
|
109
|
+
*
|
|
110
|
+
* Resolves with the `streamId` once the request is started; the head arrives
|
|
111
|
+
* as `agentStreamResponse`, body bytes as `agentStreamChunk`, and the stream
|
|
112
|
+
* ends with `agentStreamComplete`. Absent on platforms without a streaming
|
|
113
|
+
* bridge — callers MUST fall back to {@link request}.
|
|
114
|
+
*/
|
|
115
|
+
requestStream?(options: AgentRequestOptions): Promise<AgentStreamHandle>;
|
|
116
|
+
addListener?(eventName: "agentStreamResponse", listener: (event: AgentStreamResponseEvent) => void): Promise<AgentPluginListenerHandle> | AgentPluginListenerHandle;
|
|
117
|
+
addListener?(eventName: "agentStreamChunk", listener: (event: AgentStreamChunkEvent) => void): Promise<AgentPluginListenerHandle> | AgentPluginListenerHandle;
|
|
118
|
+
addListener?(eventName: "agentStreamComplete", listener: (event: AgentStreamCompleteEvent) => void): Promise<AgentPluginListenerHandle> | AgentPluginListenerHandle;
|
|
61
119
|
}
|
|
62
120
|
//# sourceMappingURL=definitions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IACpE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,GAAG,cAAc,GAAG,OAAO,GAAG,MAAM,CAAC;CACnE;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,oEAAoE;AACpE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;4EAC4E;AAC5E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iEAAiE;AACjE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,KAAK,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAEzD,8BAA8B;IAC9B,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAEjC,gCAAgC;IAChC,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAElC,gDAAgD;IAChD,IAAI,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAErD,0EAA0E;IAC1E,kBAAkB,CAAC,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAEtD;;;;;;;;OAQG;IACH,OAAO,CAAC,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAEpE;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAEzE,WAAW,CAAC,CACV,SAAS,EAAE,qBAAqB,EAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,GAClD,OAAO,CAAC,yBAAyB,CAAC,GAAG,yBAAyB,CAAC;IAClE,WAAW,CAAC,CACV,SAAS,EAAE,kBAAkB,EAC7B,QAAQ,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,GAC/C,OAAO,CAAC,yBAAyB,CAAC,GAAG,yBAAyB,CAAC;IAClE,WAAW,CAAC,CACV,SAAS,EAAE,qBAAqB,EAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,GAClD,OAAO,CAAC,yBAAyB,CAAC,GAAG,yBAAyB,CAAC;CACnE"}
|
package/dist/esm/definitions.js
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* communicating with the embedded Eliza agent.
|
|
6
6
|
*
|
|
7
7
|
* - Electrobun desktop: RPC to the main-process AgentManager
|
|
8
|
-
* -
|
|
8
|
+
* - Android/Web: HTTP calls to the API server or bundled loopback agent
|
|
9
|
+
* - iOS: HTTP for remote/cloud endpoints; local dev/sideload foreground
|
|
10
|
+
* requests bridge into the WebView ITTP kernel until the native route-kernel
|
|
11
|
+
* backend lands
|
|
9
12
|
*/
|
|
10
13
|
export {};
|
|
11
14
|
//# sourceMappingURL=definitions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
package/dist/esm/index.js
CHANGED
|
@@ -3,6 +3,6 @@ export * from "./definitions";
|
|
|
3
3
|
export const Agent = registerPlugin("Agent", {
|
|
4
4
|
web: () => import("./web").then((m) => new m.AgentWeb()),
|
|
5
5
|
// Electrobun uses the preload bridge (agent:start, agent:stop, etc.)
|
|
6
|
-
// iOS/Android
|
|
6
|
+
// iOS/Android use the native bridge when registered, otherwise the HTTP web fallback.
|
|
7
7
|
});
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,cAAc,eAAe,CAAC;AAE9B,MAAM,CAAC,MAAM,KAAK,GAAG,cAAc,CAAc,OAAO,EAAE;IACxD,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxD,qEAAqE;IACrE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,cAAc,eAAe,CAAC;AAE9B,MAAM,CAAC,MAAM,KAAK,GAAG,cAAc,CAAc,OAAO,EAAE;IACxD,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxD,qEAAqE;IACrE,sFAAsF;CACvF,CAAC,CAAC"}
|
package/dist/esm/web.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WebPlugin } from "@capacitor/core";
|
|
2
|
-
import type { AgentPlugin, AgentRequestOptions, AgentRequestResult, AgentStatus, ChatResult, LocalAgentTokenResult } from "./definitions";
|
|
2
|
+
import type { AgentPlugin, AgentRequestOptions, AgentRequestResult, AgentStartOptions, AgentStatus, ChatResult, LocalAgentTokenResult } from "./definitions";
|
|
3
3
|
/**
|
|
4
4
|
* Web fallback implementation.
|
|
5
5
|
*
|
|
@@ -27,11 +27,12 @@ export declare class AgentWeb extends WebPlugin implements AgentPlugin {
|
|
|
27
27
|
private ensureLegacyConversationId;
|
|
28
28
|
private chatViaConversation;
|
|
29
29
|
private apiBase;
|
|
30
|
+
private isLocalAgentIpcBase;
|
|
30
31
|
private apiToken;
|
|
31
32
|
private authHeaders;
|
|
32
33
|
/** True when we can reach the API via HTTP. */
|
|
33
34
|
private canReachApi;
|
|
34
|
-
start(): Promise<AgentStatus>;
|
|
35
|
+
start(_options?: AgentStartOptions): Promise<AgentStatus>;
|
|
35
36
|
stop(): Promise<{
|
|
36
37
|
ok: boolean;
|
|
37
38
|
}>;
|
package/dist/esm/web.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,qBAAqB,EACtB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,qBAAqB,EACtB,MAAM,eAAe,CAAC;AAuDvB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,QAAS,SAAQ,SAAU,YAAW,WAAW;IAC5D,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,wBAAwB;IAQhC,OAAO,CAAC,yBAAyB;YAUnB,0BAA0B;YA0B1B,mBAAmB;IA8BjC,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,mBAAmB;IAwB3B,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,WAAW;IAKnB,+CAA+C;IAC/C,OAAO,CAAC,WAAW;IAab,KAAK,CAAC,QAAQ,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC;IAkBzD,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAWhC,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC;IAgBjC,IAAI,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAQpD,kBAAkB,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAQpD,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAkCzE"}
|