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.
Files changed (27) hide show
  1. package/Package.swift +3 -1
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/com/t6x/plugins/nativeagent/LanceDBBridge.kt +27 -0
  4. package/android/src/main/java/com/t6x/plugins/nativeagent/MemoryProviderImpl.kt +177 -0
  5. package/android/src/main/java/com/t6x/plugins/nativeagent/NativeAgentPlugin.kt +16 -0
  6. package/android/src/main/java/uniffi/native_agent_ffi/native_agent_ffi.kt +260 -8
  7. package/android/src/main/jniLibs/arm64-v8a/libnative_agent_ffi.so +0 -0
  8. package/dist/esm/definitions.d.ts +11 -0
  9. package/dist/esm/definitions.d.ts.map +1 -1
  10. package/dist/esm/plugin.d.ts.map +1 -1
  11. package/dist/esm/plugin.js +1 -0
  12. package/dist/esm/plugin.js.map +1 -1
  13. package/ios/Frameworks/NativeAgentFFI.xcframework/Info.plist +4 -4
  14. package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64/Headers/{native_agent_ffiFFI → native_agent_ffi}/module.modulemap +1 -1
  15. package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64/Headers/native_agent_ffi/native_agent_ffi.swift +2594 -0
  16. package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64/Headers/{native_agent_ffiFFI → native_agent_ffi}/native_agent_ffiFFI.h +104 -0
  17. package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64/libnative_agent_ffi.a +0 -0
  18. package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64-simulator/Headers/{native_agent_ffiFFI → native_agent_ffi}/module.modulemap +1 -1
  19. package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64-simulator/Headers/native_agent_ffi/native_agent_ffi.swift +2594 -0
  20. package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64-simulator/Headers/{native_agent_ffiFFI → native_agent_ffi}/native_agent_ffiFFI.h +104 -0
  21. package/ios/Frameworks/NativeAgentFFI.xcframework/ios-arm64-simulator/libnative_agent_ffi.a +0 -0
  22. package/ios/Sources/NativeAgentPlugin/Generated/native_agent_ffi.swift +273 -7
  23. package/ios/Sources/NativeAgentPlugin/Generated/native_agent_ffiFFI.h +104 -0
  24. package/ios/Sources/NativeAgentPlugin/LanceDBBridge.swift +70 -0
  25. package/ios/Sources/NativeAgentPlugin/MemoryProviderImpl.swift +206 -0
  26. package/ios/Sources/NativeAgentPlugin/NativeAgentPlugin.swift +31 -0
  27. 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.5",
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",