@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
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import Capacitor
|
|
3
|
+
import WebKit
|
|
3
4
|
|
|
4
5
|
private let maxRequestBodyBytes = 10 * 1024 * 1024
|
|
5
6
|
private let maxResponseBodyBytes = 10 * 1024 * 1024
|
|
7
|
+
private let localAgentPort = 31337
|
|
8
|
+
private let localAgentIpcScheme = "eliza-local-agent"
|
|
9
|
+
private let localAgentIpcHost = "ipc"
|
|
6
10
|
|
|
7
11
|
private struct AgentEndpoint {
|
|
8
12
|
let baseURL: URL
|
|
@@ -16,12 +20,14 @@ private struct AgentHTTPResponse {
|
|
|
16
20
|
let body: String
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
/// Eliza Agent Plugin — iOS
|
|
23
|
+
/// Eliza Agent Plugin — iOS bridge.
|
|
20
24
|
///
|
|
21
|
-
///
|
|
22
|
-
///
|
|
23
|
-
///
|
|
24
|
-
///
|
|
25
|
+
/// Remote/cloud modes bridge the Capacitor Agent API to an explicitly
|
|
26
|
+
/// configured HTTP agent endpoint, such as a local Mac dev server or a remote
|
|
27
|
+
/// Eliza agent. Local dev/sideload mode uses a path-only in-app identity; full
|
|
28
|
+
/// Bun foreground traffic goes through the ElizaBunRuntime Capacitor bridge,
|
|
29
|
+
/// while this plugin keeps the foreground WebView ITTP route kernel as a
|
|
30
|
+
/// compatibility path. It never starts an iOS local TCP listener.
|
|
25
31
|
@objc(AgentPlugin)
|
|
26
32
|
public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
27
33
|
public let identifier = "AgentPlugin"
|
|
@@ -36,8 +42,29 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
36
42
|
]
|
|
37
43
|
|
|
38
44
|
private static var conversationIdByBaseURL: [String: String] = [:]
|
|
45
|
+
private static var localStartedAt: Date?
|
|
46
|
+
private static let apiBaseConfigKeys = [
|
|
47
|
+
"apiBase",
|
|
48
|
+
"baseUrl",
|
|
49
|
+
"baseURL",
|
|
50
|
+
"agentApiBase",
|
|
51
|
+
"ELIZA_AGENT_API_BASE",
|
|
52
|
+
"ELIZA_API_BASE",
|
|
53
|
+
"ELIZA_IOS_API_BASE",
|
|
54
|
+
"ELIZA_IOS_REMOTE_API_BASE",
|
|
55
|
+
"ELIZA_MOBILE_API_BASE",
|
|
56
|
+
"VITE_ELIZA_IOS_API_BASE",
|
|
57
|
+
"VITE_ELIZA_MOBILE_API_BASE",
|
|
58
|
+
"VITE_ELIZA_IOS_API_BASE",
|
|
59
|
+
]
|
|
39
60
|
|
|
40
61
|
@objc func start(_ call: CAPPluginCall) {
|
|
62
|
+
if isLocalAgentMode(call: call) {
|
|
63
|
+
Self.localStartedAt = Self.localStartedAt ?? Date()
|
|
64
|
+
call.resolve(localAgentStatus(state: "running", error: nil))
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
41
68
|
guard let endpoint = resolveEndpoint(call: call) else {
|
|
42
69
|
call.reject(missingEndpointMessage())
|
|
43
70
|
return
|
|
@@ -60,6 +87,12 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
60
87
|
}
|
|
61
88
|
|
|
62
89
|
@objc func stop(_ call: CAPPluginCall) {
|
|
90
|
+
if isLocalAgentMode(call: call) {
|
|
91
|
+
Self.localStartedAt = nil
|
|
92
|
+
call.resolve(["ok": true])
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
63
96
|
guard let endpoint = resolveEndpoint(call: call) else {
|
|
64
97
|
call.reject(missingEndpointMessage())
|
|
65
98
|
return
|
|
@@ -82,6 +115,12 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
82
115
|
}
|
|
83
116
|
|
|
84
117
|
@objc func getStatus(_ call: CAPPluginCall) {
|
|
118
|
+
if isLocalAgentMode(call: call) {
|
|
119
|
+
Self.localStartedAt = Self.localStartedAt ?? Date()
|
|
120
|
+
call.resolve(localAgentStatus(state: "running", error: nil))
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
85
124
|
guard let endpoint = resolveEndpoint(call: call) else {
|
|
86
125
|
call.resolve(status(state: "error", agentName: nil, port: nil, startedAt: nil, error: missingEndpointMessage()))
|
|
87
126
|
return
|
|
@@ -119,6 +158,25 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
119
158
|
call.reject("Agent.chat requires non-empty text")
|
|
120
159
|
return
|
|
121
160
|
}
|
|
161
|
+
if isLocalAgentMode(call: call) {
|
|
162
|
+
let timeout = timeoutMs(from: call)
|
|
163
|
+
ensureLocalConversation(timeoutMs: timeout) { conversationResult in
|
|
164
|
+
switch conversationResult {
|
|
165
|
+
case .success(let conversationId):
|
|
166
|
+
self.sendLocalChatMessage(conversationId: conversationId, text: text, timeoutMs: timeout, retryOnMissingConversation: true) { result in
|
|
167
|
+
switch result {
|
|
168
|
+
case .success(let payload):
|
|
169
|
+
call.resolve(payload)
|
|
170
|
+
case .failure(let error):
|
|
171
|
+
call.reject(error.localizedDescription)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
case .failure(let error):
|
|
175
|
+
call.reject(error.localizedDescription)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return
|
|
179
|
+
}
|
|
122
180
|
guard let endpoint = resolveEndpoint(call: call) else {
|
|
123
181
|
call.reject(missingEndpointMessage())
|
|
124
182
|
return
|
|
@@ -142,6 +200,14 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
142
200
|
}
|
|
143
201
|
|
|
144
202
|
@objc func getLocalAgentToken(_ call: CAPPluginCall) {
|
|
203
|
+
if isLocalAgentMode(call: call) {
|
|
204
|
+
call.resolve([
|
|
205
|
+
"available": false,
|
|
206
|
+
"token": NSNull(),
|
|
207
|
+
])
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
145
211
|
let token = resolveEndpoint(call: call)?.token
|
|
146
212
|
call.resolve([
|
|
147
213
|
"available": token != nil,
|
|
@@ -150,10 +216,6 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
150
216
|
}
|
|
151
217
|
|
|
152
218
|
@objc func request(_ call: CAPPluginCall) {
|
|
153
|
-
guard let endpoint = resolveEndpoint(call: call) else {
|
|
154
|
-
call.reject(missingEndpointMessage())
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
219
|
guard let path = call.getString("path")?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
158
220
|
isSafeLocalPath(path) else {
|
|
159
221
|
call.reject("Agent.request requires a local path that starts with / and is not an absolute URL")
|
|
@@ -171,6 +233,28 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
171
233
|
}
|
|
172
234
|
|
|
173
235
|
let headers = call.getObject("headers") ?? [:]
|
|
236
|
+
if isLocalAgentMode(call: call) {
|
|
237
|
+
sendLocalIttpRequest(
|
|
238
|
+
path: path,
|
|
239
|
+
method: method,
|
|
240
|
+
headers: headers,
|
|
241
|
+
body: body,
|
|
242
|
+
timeoutMs: timeoutMs(from: call)
|
|
243
|
+
) { result in
|
|
244
|
+
switch result {
|
|
245
|
+
case .success(let response):
|
|
246
|
+
call.resolve(self.agentHTTPResponseObject(response))
|
|
247
|
+
case .failure(let error):
|
|
248
|
+
call.reject("iOS local agent request failed: \(error.localizedDescription)")
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
guard let endpoint = resolveEndpoint(call: call) else {
|
|
255
|
+
call.reject(missingEndpointMessage())
|
|
256
|
+
return
|
|
257
|
+
}
|
|
174
258
|
sendJSON(
|
|
175
259
|
endpoint: endpoint,
|
|
176
260
|
path: path,
|
|
@@ -193,6 +277,165 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
193
277
|
}
|
|
194
278
|
}
|
|
195
279
|
|
|
280
|
+
private func ensureLocalConversation(
|
|
281
|
+
timeoutMs: Int,
|
|
282
|
+
completion: @escaping (Result<String, Error>) -> Void
|
|
283
|
+
) {
|
|
284
|
+
let baseKey = "ios-local-ittp"
|
|
285
|
+
if let existing = Self.conversationIdByBaseURL[baseKey], !existing.isEmpty {
|
|
286
|
+
completion(.success(existing))
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
sendLocalIttpRequest(
|
|
291
|
+
path: "/api/conversations",
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: ["Content-Type": "application/json"],
|
|
294
|
+
body: "{\"title\":\"Quick Chat\"}",
|
|
295
|
+
timeoutMs: timeoutMs
|
|
296
|
+
) { result in
|
|
297
|
+
switch result {
|
|
298
|
+
case .success(let response):
|
|
299
|
+
guard self.isHTTPSuccess(response.status) else {
|
|
300
|
+
completion(.failure(self.pluginError(self.httpErrorMessage(prefix: "Failed to create local conversation", response: response))))
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
guard let payload = self.parseJSONObject(response.body),
|
|
304
|
+
let conversation = payload["conversation"] as? JSObject,
|
|
305
|
+
let id = conversation["id"] as? String,
|
|
306
|
+
!id.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
307
|
+
completion(.failure(self.pluginError("Local conversation create response missing id")))
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
Self.conversationIdByBaseURL[baseKey] = id
|
|
311
|
+
completion(.success(id))
|
|
312
|
+
case .failure(let error):
|
|
313
|
+
completion(.failure(self.pluginError("Failed to create local conversation: \(error.localizedDescription)")))
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private func sendLocalChatMessage(
|
|
319
|
+
conversationId: String,
|
|
320
|
+
text: String,
|
|
321
|
+
timeoutMs: Int,
|
|
322
|
+
retryOnMissingConversation: Bool,
|
|
323
|
+
completion: @escaping (Result<JSObject, Error>) -> Void
|
|
324
|
+
) {
|
|
325
|
+
let path = "/api/conversations/\(urlEncode(conversationId))/messages"
|
|
326
|
+
let bodyObject: JSObject = [
|
|
327
|
+
"text": text,
|
|
328
|
+
"channelType": "DM",
|
|
329
|
+
]
|
|
330
|
+
guard let bodyData = try? JSONSerialization.data(withJSONObject: bodyObject, options: []),
|
|
331
|
+
let body = String(data: bodyData, encoding: .utf8) else {
|
|
332
|
+
completion(.failure(pluginError("Failed to encode local chat request")))
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
sendLocalIttpRequest(
|
|
337
|
+
path: path,
|
|
338
|
+
method: "POST",
|
|
339
|
+
headers: ["Content-Type": "application/json"],
|
|
340
|
+
body: body,
|
|
341
|
+
timeoutMs: timeoutMs
|
|
342
|
+
) { result in
|
|
343
|
+
switch result {
|
|
344
|
+
case .success(let response):
|
|
345
|
+
if response.status == 404 && retryOnMissingConversation {
|
|
346
|
+
Self.conversationIdByBaseURL.removeValue(forKey: "ios-local-ittp")
|
|
347
|
+
self.ensureLocalConversation(timeoutMs: timeoutMs) { nextConversation in
|
|
348
|
+
switch nextConversation {
|
|
349
|
+
case .success(let nextId):
|
|
350
|
+
self.sendLocalChatMessage(
|
|
351
|
+
conversationId: nextId,
|
|
352
|
+
text: text,
|
|
353
|
+
timeoutMs: timeoutMs,
|
|
354
|
+
retryOnMissingConversation: false,
|
|
355
|
+
completion: completion
|
|
356
|
+
)
|
|
357
|
+
case .failure(let error):
|
|
358
|
+
completion(.failure(error))
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
guard self.isHTTPSuccess(response.status) else {
|
|
364
|
+
completion(.failure(self.pluginError(self.httpErrorMessage(prefix: "Local chat request failed", response: response))))
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
let payload = self.parseJSONObject(response.body) ?? [:]
|
|
368
|
+
let responseText = (payload["text"] as? String) ?? ""
|
|
369
|
+
let agentName = (payload["agentName"] as? String) ?? "Agent"
|
|
370
|
+
completion(.success([
|
|
371
|
+
"text": responseText,
|
|
372
|
+
"agentName": agentName,
|
|
373
|
+
]))
|
|
374
|
+
case .failure(let error):
|
|
375
|
+
completion(.failure(self.pluginError("Local chat request failed: \(error.localizedDescription)")))
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private func sendLocalIttpRequest(
|
|
381
|
+
path: String,
|
|
382
|
+
method: String,
|
|
383
|
+
headers: JSObject = [:],
|
|
384
|
+
body: String? = nil,
|
|
385
|
+
timeoutMs: Int,
|
|
386
|
+
completion: @escaping (Result<AgentHTTPResponse, Error>) -> Void
|
|
387
|
+
) {
|
|
388
|
+
guard let webView = bridge?.webView else {
|
|
389
|
+
completion(.success(localIttpUnavailableResponse()))
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
let payload: JSObject = [
|
|
394
|
+
"path": path,
|
|
395
|
+
"method": method,
|
|
396
|
+
"headers": headers,
|
|
397
|
+
"body": body ?? NSNull(),
|
|
398
|
+
"timeoutMs": timeoutMs,
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
let source = """
|
|
402
|
+
const handler = window.__ELIZA_IOS_LOCAL_AGENT_REQUEST__;
|
|
403
|
+
if (typeof handler !== "function") {
|
|
404
|
+
return {
|
|
405
|
+
status: 503,
|
|
406
|
+
statusText: "Service Unavailable",
|
|
407
|
+
headers: { "content-type": "application/json" },
|
|
408
|
+
body: JSON.stringify({
|
|
409
|
+
ok: false,
|
|
410
|
+
error: "ios_ittp_handler_unavailable",
|
|
411
|
+
reason: "The WebView ITTP local-agent request bridge is not installed yet."
|
|
412
|
+
})
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return await handler(options);
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
if #available(iOS 14.0, *) {
|
|
419
|
+
DispatchQueue.main.async {
|
|
420
|
+
Task { @MainActor in
|
|
421
|
+
do {
|
|
422
|
+
let value = try await webView.callAsyncJavaScript(
|
|
423
|
+
source,
|
|
424
|
+
arguments: ["options": payload],
|
|
425
|
+
in: nil,
|
|
426
|
+
contentWorld: .page
|
|
427
|
+
)
|
|
428
|
+
completion(self.parseAgentHTTPResponse(value))
|
|
429
|
+
} catch {
|
|
430
|
+
completion(.failure(error))
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
completion(.success(localIttpUnavailableResponse()))
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
196
439
|
private func ensureConversation(
|
|
197
440
|
endpoint: AgentEndpoint,
|
|
198
441
|
timeoutMs: Int,
|
|
@@ -377,20 +620,7 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
377
620
|
private func resolveEndpoint(call: CAPPluginCall? = nil) -> AgentEndpoint? {
|
|
378
621
|
guard let rawBaseURL = readConfiguredString(
|
|
379
622
|
call: call,
|
|
380
|
-
keys:
|
|
381
|
-
"apiBase",
|
|
382
|
-
"baseUrl",
|
|
383
|
-
"baseURL",
|
|
384
|
-
"agentApiBase",
|
|
385
|
-
"ELIZA_AGENT_API_BASE",
|
|
386
|
-
"ELIZA_API_BASE",
|
|
387
|
-
"MILADY_IOS_API_BASE",
|
|
388
|
-
"MILADY_IOS_REMOTE_API_BASE",
|
|
389
|
-
"MILADY_MOBILE_API_BASE",
|
|
390
|
-
"VITE_MILADY_IOS_API_BASE",
|
|
391
|
-
"VITE_MILADY_MOBILE_API_BASE",
|
|
392
|
-
"VITE_ELIZA_IOS_API_BASE",
|
|
393
|
-
]
|
|
623
|
+
keys: Self.apiBaseConfigKeys
|
|
394
624
|
) else {
|
|
395
625
|
return nil
|
|
396
626
|
}
|
|
@@ -412,11 +642,11 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
412
642
|
"agentApiToken",
|
|
413
643
|
"ELIZA_AGENT_API_TOKEN",
|
|
414
644
|
"ELIZA_API_TOKEN",
|
|
415
|
-
"
|
|
416
|
-
"
|
|
417
|
-
"
|
|
418
|
-
"
|
|
419
|
-
"
|
|
645
|
+
"ELIZA_IOS_API_TOKEN",
|
|
646
|
+
"ELIZA_IOS_REMOTE_API_TOKEN",
|
|
647
|
+
"ELIZA_MOBILE_API_TOKEN",
|
|
648
|
+
"VITE_ELIZA_IOS_API_TOKEN",
|
|
649
|
+
"VITE_ELIZA_MOBILE_API_TOKEN",
|
|
420
650
|
"VITE_ELIZA_IOS_API_TOKEN",
|
|
421
651
|
]
|
|
422
652
|
)
|
|
@@ -462,6 +692,69 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
462
692
|
return nil
|
|
463
693
|
}
|
|
464
694
|
|
|
695
|
+
private func isLocalAgentMode(call: CAPPluginCall? = nil) -> Bool {
|
|
696
|
+
if let endpoint = resolveEndpoint(call: call),
|
|
697
|
+
isLocalAgentEndpoint(endpoint.baseURL) {
|
|
698
|
+
return true
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if let rawBaseURL = readConfiguredString(
|
|
702
|
+
call: call,
|
|
703
|
+
keys: Self.apiBaseConfigKeys
|
|
704
|
+
), isLocalAgentIdentity(rawBaseURL) {
|
|
705
|
+
return true
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
guard let rawMode = readConfiguredString(
|
|
709
|
+
call: call,
|
|
710
|
+
keys: [
|
|
711
|
+
"mode",
|
|
712
|
+
"runtimeMode",
|
|
713
|
+
"agentRuntimeMode",
|
|
714
|
+
"ELIZA_IOS_RUNTIME_MODE",
|
|
715
|
+
"ELIZA_MOBILE_RUNTIME_MODE",
|
|
716
|
+
"VITE_ELIZA_IOS_RUNTIME_MODE",
|
|
717
|
+
"VITE_ELIZA_MOBILE_RUNTIME_MODE",
|
|
718
|
+
]
|
|
719
|
+
) else {
|
|
720
|
+
return false
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
switch rawMode.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
|
|
724
|
+
case "local", "ios-local", "sideload-local", "dev-local":
|
|
725
|
+
return true
|
|
726
|
+
default:
|
|
727
|
+
return false
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
private func isLocalAgentEndpoint(_ url: URL) -> Bool {
|
|
732
|
+
guard let scheme = url.scheme?.lowercased(),
|
|
733
|
+
let host = url.host?.lowercased() else { return false }
|
|
734
|
+
if scheme == localAgentIpcScheme && host == localAgentIpcHost {
|
|
735
|
+
return true
|
|
736
|
+
}
|
|
737
|
+
guard scheme == "http" else { return false }
|
|
738
|
+
return (host == "127.0.0.1" || host == "localhost") && (url.port ?? 80) == localAgentPort
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
private func isLocalAgentIdentity(_ value: String) -> Bool {
|
|
742
|
+
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
743
|
+
guard let url = URL(string: trimmed) else { return false }
|
|
744
|
+
return isLocalAgentEndpoint(url)
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private func localAgentStatus(state: String, error: String?) -> JSObject {
|
|
748
|
+
let startedAt = Self.localStartedAt.map { $0.timeIntervalSince1970 * 1000 }
|
|
749
|
+
return status(
|
|
750
|
+
state: state,
|
|
751
|
+
agentName: "Eliza",
|
|
752
|
+
port: nil,
|
|
753
|
+
startedAt: startedAt,
|
|
754
|
+
error: error
|
|
755
|
+
)
|
|
756
|
+
}
|
|
757
|
+
|
|
465
758
|
private func normalizedStatus(
|
|
466
759
|
_ payload: JSObject,
|
|
467
760
|
fallbackState: String,
|
|
@@ -543,6 +836,61 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
543
836
|
return json
|
|
544
837
|
}
|
|
545
838
|
|
|
839
|
+
private func parseAgentHTTPResponse(_ value: Any?) -> Result<AgentHTTPResponse, Error> {
|
|
840
|
+
guard let payload = value as? JSObject else {
|
|
841
|
+
return .failure(pluginError("iOS local ITTP bridge returned a non-object response"))
|
|
842
|
+
}
|
|
843
|
+
let status = (payload["status"] as? Int)
|
|
844
|
+
?? (payload["status"] as? NSNumber)?.intValue
|
|
845
|
+
?? 500
|
|
846
|
+
let statusText = payload["statusText"] as? String
|
|
847
|
+
?? HTTPURLResponse.localizedString(forStatusCode: status)
|
|
848
|
+
let rawHeaders = payload["headers"] as? JSObject ?? [:]
|
|
849
|
+
var headers: [String: String] = [:]
|
|
850
|
+
for (key, value) in rawHeaders {
|
|
851
|
+
headers[key.lowercased()] = String(describing: value)
|
|
852
|
+
}
|
|
853
|
+
let body = payload["body"] as? String ?? ""
|
|
854
|
+
if let bodyBytes = body.data(using: .utf8), bodyBytes.count > maxResponseBodyBytes {
|
|
855
|
+
return .failure(pluginError("Response body is too large"))
|
|
856
|
+
}
|
|
857
|
+
return .success(AgentHTTPResponse(
|
|
858
|
+
status: status,
|
|
859
|
+
statusText: statusText,
|
|
860
|
+
headers: headers,
|
|
861
|
+
body: body
|
|
862
|
+
))
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
private func agentHTTPResponseObject(_ response: AgentHTTPResponse) -> JSObject {
|
|
866
|
+
var headers: JSObject = [:]
|
|
867
|
+
for (key, value) in response.headers {
|
|
868
|
+
headers[key] = value
|
|
869
|
+
}
|
|
870
|
+
return [
|
|
871
|
+
"status": response.status,
|
|
872
|
+
"statusText": response.statusText,
|
|
873
|
+
"headers": headers,
|
|
874
|
+
"body": response.body,
|
|
875
|
+
]
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
private func localIttpUnavailableResponse() -> AgentHTTPResponse {
|
|
879
|
+
let bodyObject: JSObject = [
|
|
880
|
+
"ok": false,
|
|
881
|
+
"error": "ios_ittp_handler_unavailable",
|
|
882
|
+
"reason": localIttpOnlyMessage(),
|
|
883
|
+
]
|
|
884
|
+
let bodyData = try? JSONSerialization.data(withJSONObject: bodyObject, options: [])
|
|
885
|
+
let body = bodyData.flatMap { String(data: $0, encoding: .utf8) } ?? "{\"ok\":false,\"error\":\"ios_ittp_handler_unavailable\"}"
|
|
886
|
+
return AgentHTTPResponse(
|
|
887
|
+
status: 503,
|
|
888
|
+
statusText: HTTPURLResponse.localizedString(forStatusCode: 503),
|
|
889
|
+
headers: ["content-type": "application/json"],
|
|
890
|
+
body: body
|
|
891
|
+
)
|
|
892
|
+
}
|
|
893
|
+
|
|
546
894
|
private func httpErrorMessage(prefix: String, response: AgentHTTPResponse) -> String {
|
|
547
895
|
let body = response.body.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
548
896
|
if body.isEmpty {
|
|
@@ -552,7 +900,11 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
552
900
|
}
|
|
553
901
|
|
|
554
902
|
private func missingEndpointMessage() -> String {
|
|
555
|
-
return "iOS Agent requires a configured HTTP endpoint. Set Agent.apiBase in capacitor.config, an Info.plist/UserDefaults key such as
|
|
903
|
+
return "iOS Agent requires a configured HTTP endpoint for remote/cloud mode, or runtimeMode=local for dev/sideload local mode. Set Agent.apiBase in capacitor.config, an Info.plist/UserDefaults key such as ELIZA_IOS_API_BASE or ELIZA_AGENT_API_BASE, or a simulator environment variable."
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
private func localIttpOnlyMessage() -> String {
|
|
907
|
+
return "iOS local agent requests require the WebView ITTP route kernel bridge. Start the app WebView before calling native Agent.request or Agent.chat in local mode."
|
|
556
908
|
}
|
|
557
909
|
|
|
558
910
|
private func urlEncode(_ value: String) -> String {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/capacitor-agent",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3-beta.2",
|
|
4
4
|
"description": "Starts, stops, and monitors the embedded Eliza agent runtime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-runtime",
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
16
|
"types": "./dist/esm/index.d.ts",
|
|
17
|
+
"bun": "./src/index.ts",
|
|
18
|
+
"development": "./src/index.ts",
|
|
17
19
|
"import": "./dist/esm/index.js",
|
|
18
20
|
"require": "./dist/plugin.cjs.js"
|
|
19
21
|
},
|
|
@@ -33,19 +35,21 @@
|
|
|
33
35
|
"repository": {
|
|
34
36
|
"type": "git",
|
|
35
37
|
"url": "https://github.com/elizaOS/eliza.git",
|
|
36
|
-
"directory": "
|
|
38
|
+
"directory": "plugins/plugin-native-agent"
|
|
37
39
|
},
|
|
38
40
|
"scripts": {
|
|
39
|
-
"build": "
|
|
40
|
-
"clean": "node
|
|
41
|
+
"build": "node ../../packages/scripts/with-package-build-lock.mjs plugins/plugin-native-agent -- bun run build:unlocked",
|
|
42
|
+
"clean": "node ../../packages/scripts/rm-path-recursive.mjs dist",
|
|
43
|
+
"test": "vitest run",
|
|
41
44
|
"prepublishOnly": "bun run build",
|
|
42
|
-
"watch": "tsc --watch"
|
|
45
|
+
"watch": "tsc --watch",
|
|
46
|
+
"build:unlocked": "bun run clean && tsc && bunx rollup -c rollup.config.mjs"
|
|
43
47
|
},
|
|
44
48
|
"devDependencies": {
|
|
45
49
|
"@capacitor/core": "^8.3.1",
|
|
46
|
-
"rimraf": "^6.0.0",
|
|
47
50
|
"rollup": "^4.60.2",
|
|
48
|
-
"typescript": "^6.0.3"
|
|
51
|
+
"typescript": "^6.0.3",
|
|
52
|
+
"vitest": "^4.0.0"
|
|
49
53
|
},
|
|
50
54
|
"peerDependencies": {
|
|
51
55
|
"@capacitor/core": "^8.3.1"
|
|
@@ -61,5 +65,6 @@
|
|
|
61
65
|
"android": {
|
|
62
66
|
"src": "android"
|
|
63
67
|
}
|
|
64
|
-
}
|
|
68
|
+
},
|
|
69
|
+
"gitHead": "82fe0f44215954c2417328203f5bd6510985c1fc"
|
|
65
70
|
}
|