capacitor-native-agent 0.3.5 → 0.3.7
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/Package.swift +3 -1
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/t6x/plugins/nativeagent/LanceDBBridge.kt +27 -0
- package/android/src/main/java/com/t6x/plugins/nativeagent/MemoryProviderImpl.kt +177 -0
- package/android/src/main/java/com/t6x/plugins/nativeagent/NativeAgentPlugin.kt +16 -0
- package/android/src/main/java/uniffi/native_agent_ffi/native_agent_ffi.kt +260 -8
- package/android/src/main/jniLibs/arm64-v8a/libnative_agent_ffi.so +0 -0
- package/dist/esm/definitions.d.ts +11 -0
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/plugin.d.ts.map +1 -1
- package/dist/esm/plugin.js +1 -0
- package/dist/esm/plugin.js.map +1 -1
- package/ios/Frameworks/NativeAgentFFI.xcframework/Info.plist +4 -4
- package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64/Headers/{native_agent_ffiFFI → native_agent_ffi}/module.modulemap +1 -1
- package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64/Headers/native_agent_ffi/native_agent_ffi.swift +2594 -0
- package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64/Headers/{native_agent_ffiFFI → native_agent_ffi}/native_agent_ffiFFI.h +104 -0
- package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64/libnative_agent_ffi.a +0 -0
- package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64-simulator/Headers/{native_agent_ffiFFI → native_agent_ffi}/module.modulemap +1 -1
- package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64-simulator/Headers/native_agent_ffi/native_agent_ffi.swift +2594 -0
- package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64-simulator/Headers/{native_agent_ffiFFI → native_agent_ffi}/native_agent_ffiFFI.h +104 -0
- package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64-simulator/libnative_agent_ffi.a +0 -0
- package/ios/Sources/NativeAgentPlugin/Generated/native_agent_ffi.swift +273 -7
- package/ios/Sources/NativeAgentPlugin/Generated/native_agent_ffiFFI.h +104 -0
- package/ios/Sources/NativeAgentPlugin/LanceDBBridge.swift +70 -0
- package/ios/Sources/NativeAgentPlugin/MemoryProviderImpl.swift +206 -0
- package/ios/Sources/NativeAgentPlugin/NativeAgentPlugin.swift +31 -0
- package/package.json +8 -2
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
#if canImport(CapacitorLanceDB)
|
|
4
|
+
import CapacitorLanceDB
|
|
5
|
+
#elseif canImport(CapacitorLancedb)
|
|
6
|
+
import CapacitorLancedb
|
|
7
|
+
#elseif canImport(LanceDBPlugin)
|
|
8
|
+
import LanceDBPlugin
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
#if canImport(CapacitorLanceDB) || canImport(CapacitorLancedb) || canImport(LanceDBPlugin)
|
|
12
|
+
final class LanceDBBridge {
|
|
13
|
+
static let shared = LanceDBBridge()
|
|
14
|
+
static let embeddingDim: Int32 = 1536
|
|
15
|
+
|
|
16
|
+
private let lock = NSLock()
|
|
17
|
+
private var handle: LanceDbHandle?
|
|
18
|
+
|
|
19
|
+
func getOrCreateHandle() -> LanceDbHandle? {
|
|
20
|
+
lock.lock()
|
|
21
|
+
defer { lock.unlock() }
|
|
22
|
+
|
|
23
|
+
if let handle {
|
|
24
|
+
return handle
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let dbPath = FileManager.default
|
|
28
|
+
.urls(for: .libraryDirectory, in: .userDomainMask)
|
|
29
|
+
.first!
|
|
30
|
+
.appendingPathComponent("lancedb-memories")
|
|
31
|
+
.path
|
|
32
|
+
|
|
33
|
+
try? FileManager.default.createDirectory(
|
|
34
|
+
atPath: dbPath,
|
|
35
|
+
withIntermediateDirectories: true,
|
|
36
|
+
attributes: nil
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
let opened = try? Self.awaitResult {
|
|
40
|
+
try await LanceDbHandle.open(dbPath: dbPath, embeddingDim: Self.embeddingDim)
|
|
41
|
+
}
|
|
42
|
+
handle = opened
|
|
43
|
+
return opened
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static func awaitResult<T>(_ operation: @escaping () async throws -> T) throws -> T {
|
|
47
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
48
|
+
let resultLock = NSLock()
|
|
49
|
+
var outcome: Result<T, Error>?
|
|
50
|
+
|
|
51
|
+
Task.detached {
|
|
52
|
+
let result: Result<T, Error>
|
|
53
|
+
do {
|
|
54
|
+
result = .success(try await operation())
|
|
55
|
+
} catch {
|
|
56
|
+
result = .failure(error)
|
|
57
|
+
}
|
|
58
|
+
resultLock.lock()
|
|
59
|
+
outcome = result
|
|
60
|
+
resultLock.unlock()
|
|
61
|
+
semaphore.signal()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
semaphore.wait()
|
|
65
|
+
resultLock.lock()
|
|
66
|
+
defer { resultLock.unlock() }
|
|
67
|
+
return try outcome!.get()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
#endif
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
#if canImport(CapacitorLanceDB) || canImport(CapacitorLancedb) || canImport(LanceDBPlugin)
|
|
4
|
+
public final class MemoryProviderImpl: MemoryProvider {
|
|
5
|
+
public static func makeIfAvailable() -> MemoryProvider? {
|
|
6
|
+
guard LanceDBBridge.shared.getOrCreateHandle() != nil else {
|
|
7
|
+
return nil
|
|
8
|
+
}
|
|
9
|
+
return MemoryProviderImpl()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public func store(key: String, text: String, metadataJson: String?) -> String {
|
|
13
|
+
guard let handle = LanceDBBridge.shared.getOrCreateHandle() else {
|
|
14
|
+
return errorJson("Memory provider not configured")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let resolvedKey = key.isEmpty
|
|
18
|
+
? "mem-\(Int(Date().timeIntervalSince1970 * 1000))-\(UUID().uuidString.prefix(8))"
|
|
19
|
+
: key
|
|
20
|
+
let embedding = localHashEmbed(text, dim: Int(LanceDBBridge.embeddingDim))
|
|
21
|
+
|
|
22
|
+
do {
|
|
23
|
+
try LanceDBBridge.awaitResult {
|
|
24
|
+
try await handle.store(
|
|
25
|
+
key: resolvedKey,
|
|
26
|
+
agentId: Self.defaultAgentId,
|
|
27
|
+
text: text,
|
|
28
|
+
embedding: embedding,
|
|
29
|
+
metadata: metadataJson
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
return jsonString([
|
|
33
|
+
"success": true,
|
|
34
|
+
"key": resolvedKey,
|
|
35
|
+
])
|
|
36
|
+
} catch {
|
|
37
|
+
return errorJson(error.localizedDescription)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public func recall(query: String, limit: UInt32) -> String {
|
|
42
|
+
guard let handle = LanceDBBridge.shared.getOrCreateHandle() else {
|
|
43
|
+
return errorJson("Memory provider not configured")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let embedding = localHashEmbed(query, dim: Int(LanceDBBridge.embeddingDim))
|
|
47
|
+
|
|
48
|
+
do {
|
|
49
|
+
let results = try LanceDBBridge.awaitResult {
|
|
50
|
+
try await handle.search(
|
|
51
|
+
queryVector: embedding,
|
|
52
|
+
limit: limit,
|
|
53
|
+
filter: "agent_id = '\(Self.defaultAgentId)'"
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
return jsonString(results.map { result in
|
|
57
|
+
var object: [String: Any] = [
|
|
58
|
+
"key": result.key,
|
|
59
|
+
"text": result.text,
|
|
60
|
+
"score": result.score,
|
|
61
|
+
]
|
|
62
|
+
if let metadata = result.metadata {
|
|
63
|
+
object["metadata"] = metadata
|
|
64
|
+
}
|
|
65
|
+
return object
|
|
66
|
+
})
|
|
67
|
+
} catch {
|
|
68
|
+
return errorJson(error.localizedDescription)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public func forget(key: String) -> String {
|
|
73
|
+
guard let handle = LanceDBBridge.shared.getOrCreateHandle() else {
|
|
74
|
+
return errorJson("Memory provider not configured")
|
|
75
|
+
}
|
|
76
|
+
guard !key.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
77
|
+
return errorJson("Provide query or key.")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
do {
|
|
81
|
+
try LanceDBBridge.awaitResult {
|
|
82
|
+
try await handle.delete(key: key)
|
|
83
|
+
}
|
|
84
|
+
return jsonString([
|
|
85
|
+
"success": true,
|
|
86
|
+
"key": key,
|
|
87
|
+
])
|
|
88
|
+
} catch {
|
|
89
|
+
return errorJson(error.localizedDescription)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public func search(query: String, maxResults: UInt32) -> String {
|
|
94
|
+
guard let handle = LanceDBBridge.shared.getOrCreateHandle() else {
|
|
95
|
+
return errorJson("Memory provider not configured")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let embedding = localHashEmbed(query, dim: Int(LanceDBBridge.embeddingDim))
|
|
99
|
+
|
|
100
|
+
do {
|
|
101
|
+
let results = try LanceDBBridge.awaitResult {
|
|
102
|
+
try await handle.search(
|
|
103
|
+
queryVector: embedding,
|
|
104
|
+
limit: maxResults,
|
|
105
|
+
filter: "agent_id = '\(Self.defaultAgentId)'"
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
return jsonString(results.map { result in
|
|
109
|
+
var object: [String: Any] = [
|
|
110
|
+
"key": result.key,
|
|
111
|
+
"text": result.text,
|
|
112
|
+
"score": result.score,
|
|
113
|
+
]
|
|
114
|
+
if let metadata = result.metadata {
|
|
115
|
+
object["metadata"] = metadata
|
|
116
|
+
}
|
|
117
|
+
return object
|
|
118
|
+
})
|
|
119
|
+
} catch {
|
|
120
|
+
return errorJson(error.localizedDescription)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public func list(prefix: String?, limit: UInt32?) -> String {
|
|
125
|
+
guard let handle = LanceDBBridge.shared.getOrCreateHandle() else {
|
|
126
|
+
return errorJson("Memory provider not configured")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
do {
|
|
130
|
+
let keys = try LanceDBBridge.awaitResult {
|
|
131
|
+
try await handle.list(prefix: prefix, limit: limit)
|
|
132
|
+
}
|
|
133
|
+
return jsonString(keys)
|
|
134
|
+
} catch {
|
|
135
|
+
return errorJson(error.localizedDescription)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private static let defaultAgentId = "main"
|
|
140
|
+
private static let whitespace = CharacterSet.whitespacesAndNewlines
|
|
141
|
+
private static let posixLocale = Locale(identifier: "en_US_POSIX")
|
|
142
|
+
private static let fnvOffset: UInt32 = 0x811c9dc5
|
|
143
|
+
private static let fnvPrime: UInt32 = 0x01000193
|
|
144
|
+
private static let goldenRatio: UInt32 = 2654435761
|
|
145
|
+
private static let mixConstant: UInt32 = 0x45d9f3b
|
|
146
|
+
private static let uintMaxDouble = Double(UInt32.max)
|
|
147
|
+
|
|
148
|
+
private func jsonString(_ value: Any) -> String {
|
|
149
|
+
guard JSONSerialization.isValidJSONObject(value),
|
|
150
|
+
let data = try? JSONSerialization.data(withJSONObject: value),
|
|
151
|
+
let string = String(data: data, encoding: .utf8) else {
|
|
152
|
+
return "{\"error\":\"Failed to encode JSON\"}"
|
|
153
|
+
}
|
|
154
|
+
return string
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private func errorJson(_ message: String) -> String {
|
|
158
|
+
let escaped = message
|
|
159
|
+
.replacingOccurrences(of: "\\", with: "\\\\")
|
|
160
|
+
.replacingOccurrences(of: "\"", with: "\\\"")
|
|
161
|
+
return "{\"error\":\"\(escaped)\"}"
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private func fnv1a(_ text: String) -> UInt32 {
|
|
165
|
+
var hash = Self.fnvOffset
|
|
166
|
+
for scalar in text.unicodeScalars {
|
|
167
|
+
hash ^= UInt32(scalar.value)
|
|
168
|
+
hash = hash &* Self.fnvPrime
|
|
169
|
+
}
|
|
170
|
+
return hash
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private func seededRandom(seed: UInt32, dim: Int) -> Float {
|
|
174
|
+
var h = seed ^ (UInt32(dim) &* Self.goldenRatio)
|
|
175
|
+
h = ((h >> 16) ^ h) &* Self.mixConstant
|
|
176
|
+
h = ((h >> 16) ^ h) &* Self.mixConstant
|
|
177
|
+
h = (h >> 16) ^ h
|
|
178
|
+
return Float((Double(h) / Self.uintMaxDouble) * 2.0 - 1.0)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private func localHashEmbed(_ text: String, dim: Int) -> [Float] {
|
|
182
|
+
let tokens = text
|
|
183
|
+
.lowercased(with: Self.posixLocale)
|
|
184
|
+
.components(separatedBy: Self.whitespace)
|
|
185
|
+
.filter { !$0.isEmpty }
|
|
186
|
+
var vector = Array(repeating: 0.0, count: dim)
|
|
187
|
+
|
|
188
|
+
for token in tokens {
|
|
189
|
+
let hash = fnv1a(token)
|
|
190
|
+
for index in 0..<dim {
|
|
191
|
+
vector[index] += Double(seededRandom(seed: hash, dim: index))
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let norm = sqrt(vector.reduce(0.0) { $0 + ($1 * $1) })
|
|
196
|
+
let divisor = norm > 0 ? norm : 1
|
|
197
|
+
return vector.map { Float($0 / divisor) }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
#else
|
|
201
|
+
public final class MemoryProviderImpl {
|
|
202
|
+
public static func makeIfAvailable() -> MemoryProvider? {
|
|
203
|
+
nil
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
#endif
|
|
@@ -25,6 +25,7 @@ public class NativeAgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
25
25
|
CAPPluginMethod(name: "deleteAuth", returnType: CAPPluginReturnPromise),
|
|
26
26
|
CAPPluginMethod(name: "refreshToken", returnType: CAPPluginReturnPromise),
|
|
27
27
|
CAPPluginMethod(name: "getAuthStatus", returnType: CAPPluginReturnPromise),
|
|
28
|
+
CAPPluginMethod(name: "exchangeOAuthCode", returnType: CAPPluginReturnPromise),
|
|
28
29
|
// Sessions
|
|
29
30
|
CAPPluginMethod(name: "listSessions", returnType: CAPPluginReturnPromise),
|
|
30
31
|
CAPPluginMethod(name: "loadSession", returnType: CAPPluginReturnPromise),
|
|
@@ -153,6 +154,9 @@ public class NativeAgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
153
154
|
let h = try NativeAgentHandle(config: config)
|
|
154
155
|
try h.setEventCallback(callback: NativeAgentEventBridge(plugin: self))
|
|
155
156
|
try h.setNotifier(notifier: NativeNotifierImpl())
|
|
157
|
+
if let memoryProvider = MemoryProviderImpl.makeIfAvailable() {
|
|
158
|
+
try h.setMemoryProvider(provider: memoryProvider)
|
|
159
|
+
}
|
|
156
160
|
try h.persistConfig()
|
|
157
161
|
self.handle = h
|
|
158
162
|
UserDefaults.standard.set(
|
|
@@ -357,6 +361,33 @@ public class NativeAgentPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
357
361
|
}
|
|
358
362
|
}
|
|
359
363
|
|
|
364
|
+
@objc func exchangeOAuthCode(_ call: CAPPluginCall) {
|
|
365
|
+
withHandle(call) { h in
|
|
366
|
+
guard let tokenUrl = call.getString("tokenUrl") else {
|
|
367
|
+
return call.reject("tokenUrl is required")
|
|
368
|
+
}
|
|
369
|
+
guard let bodyJson = call.getString("bodyJson") else {
|
|
370
|
+
return call.reject("bodyJson is required")
|
|
371
|
+
}
|
|
372
|
+
do {
|
|
373
|
+
let resultJson = try h.exchangeOauthCode(
|
|
374
|
+
tokenUrl: tokenUrl,
|
|
375
|
+
bodyJson: bodyJson,
|
|
376
|
+
contentType: call.getString("contentType")
|
|
377
|
+
)
|
|
378
|
+
// Parse the JSON string into a dictionary and resolve
|
|
379
|
+
if let data = resultJson.data(using: .utf8),
|
|
380
|
+
let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
381
|
+
call.resolve(dict)
|
|
382
|
+
} else {
|
|
383
|
+
call.resolve(["resultJson": resultJson])
|
|
384
|
+
}
|
|
385
|
+
} catch {
|
|
386
|
+
call.reject("exchangeOAuthCode failed: \(error.localizedDescription)")
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
360
391
|
// ── Sessions ─────────────────────────────────────────────────────────────
|
|
361
392
|
|
|
362
393
|
@objc func listSessions(_ call: CAPPluginCall) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capacitor-native-agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Native AI agent loop for Capacitor — runs LLM completions, tool execution, and cron jobs in native Rust, enabling background execution.",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"types": "dist/esm/index.d.ts",
|
|
@@ -56,7 +56,13 @@
|
|
|
56
56
|
"url": "https://github.com/ArcadeLabsInc/capacitor-native-agent/issues"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@capacitor/core": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
59
|
+
"@capacitor/core": "^6.0.0 || ^7.0.0 || ^8.0.0",
|
|
60
|
+
"capacitor-lancedb": "^0.2.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"capacitor-lancedb": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
60
66
|
},
|
|
61
67
|
"devDependencies": {
|
|
62
68
|
"@capacitor/core": "^8.1.0",
|