@elizaos/capacitor-bun-runtime 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.
Files changed (61) hide show
  1. package/ElizaosCapacitorBunRuntime.podspec +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +127 -0
  4. package/dist/esm/definitions.d.ts +136 -0
  5. package/dist/esm/definitions.d.ts.map +1 -0
  6. package/dist/esm/definitions.js +14 -0
  7. package/dist/esm/definitions.js.map +1 -0
  8. package/dist/esm/index.d.ts +9 -0
  9. package/dist/esm/index.d.ts.map +1 -0
  10. package/dist/esm/index.js +11 -0
  11. package/dist/esm/index.js.map +1 -0
  12. package/dist/esm/web.d.ts +19 -0
  13. package/dist/esm/web.d.ts.map +1 -0
  14. package/dist/esm/web.js +44 -0
  15. package/dist/esm/web.js.map +1 -0
  16. package/dist/plugin.cjs.js +63 -0
  17. package/dist/plugin.cjs.js.map +1 -0
  18. package/dist/plugin.js +66 -0
  19. package/dist/plugin.js.map +1 -0
  20. package/ios/Sources/ElizaBunRuntimePlugin/BridgeInstaller.swift +94 -0
  21. package/ios/Sources/ElizaBunRuntimePlugin/ElizaBunRuntime.swift +705 -0
  22. package/ios/Sources/ElizaBunRuntimePlugin/ElizaBunRuntimePlugin.swift +1109 -0
  23. package/ios/Sources/ElizaBunRuntimePlugin/FullBunEngineHost.swift +677 -0
  24. package/ios/Sources/ElizaBunRuntimePlugin/JSContextHelpers.swift +226 -0
  25. package/ios/Sources/ElizaBunRuntimePlugin/SandboxPaths.swift +46 -0
  26. package/ios/Sources/ElizaBunRuntimePlugin/bridge/CryptoBridge.swift +238 -0
  27. package/ios/Sources/ElizaBunRuntimePlugin/bridge/ElizaSqliteVecBridge.m +28 -0
  28. package/ios/Sources/ElizaBunRuntimePlugin/bridge/FSBridge.swift +270 -0
  29. package/ios/Sources/ElizaBunRuntimePlugin/bridge/HTTPBridge.swift +153 -0
  30. package/ios/Sources/ElizaBunRuntimePlugin/bridge/HTTPServerBridge.swift +32 -0
  31. package/ios/Sources/ElizaBunRuntimePlugin/bridge/LlamaBridge.swift +233 -0
  32. package/ios/Sources/ElizaBunRuntimePlugin/bridge/LlamaBridgeImpl.swift +1863 -0
  33. package/ios/Sources/ElizaBunRuntimePlugin/bridge/LogBridge.swift +36 -0
  34. package/ios/Sources/ElizaBunRuntimePlugin/bridge/PathsBridge.swift +41 -0
  35. package/ios/Sources/ElizaBunRuntimePlugin/bridge/ProcessBridge.swift +80 -0
  36. package/ios/Sources/ElizaBunRuntimePlugin/bridge/SqliteBridge.swift +406 -0
  37. package/ios/Sources/ElizaBunRuntimePlugin/bridge/SqliteBridgeInstaller.swift +17 -0
  38. package/ios/Sources/ElizaBunRuntimePlugin/bridge/SqliteVecLoader.swift +66 -0
  39. package/ios/Sources/ElizaBunRuntimePlugin/bridge/UIBridge.swift +72 -0
  40. package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlChinesePhonemizer.swift +313 -0
  41. package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlConfiguration.swift +28 -0
  42. package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlEngine.swift +325 -0
  43. package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlHindiPhonemizer.swift +150 -0
  44. package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlJapanesePhonemizer.swift +209 -0
  45. package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlLatinPhonemizer.swift +374 -0
  46. package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlModel.swift +87 -0
  47. package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlPhonemizer.swift +679 -0
  48. package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlPronunciationDicts.swift +131 -0
  49. package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlSupport.swift +24 -0
  50. package/ios/Tests/llama-bridge-smoke-main.swift +92 -0
  51. package/package.json +68 -0
  52. package/src/bridge-contract.test.ts +127 -0
  53. package/src/definitions.d.ts +136 -0
  54. package/src/definitions.d.ts.map +1 -0
  55. package/src/definitions.ts +152 -0
  56. package/src/index.d.ts +9 -0
  57. package/src/index.d.ts.map +1 -0
  58. package/src/index.ts +16 -0
  59. package/src/web.d.ts +19 -0
  60. package/src/web.d.ts.map +1 -0
  61. package/src/web.ts +80 -0
@@ -0,0 +1,226 @@
1
+ import Foundation
2
+ import JavaScriptCore
3
+
4
+ // MARK: - JSContext queue affinity
5
+
6
+ /// Thread/queue-affinity helpers for the JSContext-bound dispatch queue.
7
+ ///
8
+ /// JSContext is single-threaded. The runtime owns a serial DispatchQueue
9
+ /// (`ai.eliza.bun.runtime`). All host-function bodies execute on that queue.
10
+ /// I/O bridges (HTTP fetch, llama generation) dispatch off the runtime queue
11
+ /// to do work, then post results back via a wrapper that re-enters the
12
+ /// JSContext queue.
13
+ public enum RuntimeQueue {
14
+ public static let label = "ai.eliza.bun.runtime"
15
+
16
+ /// Returns the JS thread's dispatch queue. Created lazily by the runtime
17
+ /// at startup and stored here for the bridge modules to use.
18
+ public static var current: DispatchQueue?
19
+
20
+ /// Dispatches a block onto the JSContext queue. If the queue is not yet
21
+ /// set up (e.g. early-bridge construction during install), runs inline.
22
+ public static func dispatchOnJS(_ block: @escaping () -> Void) {
23
+ if let q = current {
24
+ q.async(execute: block)
25
+ } else {
26
+ block()
27
+ }
28
+ }
29
+ }
30
+
31
+ // MARK: - JSValue conveniences
32
+
33
+ public extension JSValue {
34
+ /// Returns `true` when the value is `null` or `undefined`.
35
+ var isNullish: Bool {
36
+ return isNull || isUndefined
37
+ }
38
+
39
+ /// Bridges a `JSValue` Uint8Array (or ArrayBuffer) to a Swift `Data` blob.
40
+ /// Returns `nil` if the value isn't a TypedArray / ArrayBuffer-backed view.
41
+ func toData() -> Data? {
42
+ guard let ctx = context else { return nil }
43
+
44
+ // Common path: JSC exposes typed-array length via .length and per-element
45
+ // access via subscript. That's slow for large blobs. Faster: call back
46
+ // into JS to expose a byteLength + slice() to a Uint8Array, then walk
47
+ // the byte values. We keep it portable across JSC builds without
48
+ // relying on `JSObjectGetTypedArrayBytesPtr` which is gated by
49
+ // availability flags.
50
+
51
+ let lenValue = forProperty("length")
52
+ guard let lenValue = lenValue, lenValue.isNumber else {
53
+ return nil
54
+ }
55
+ let count = Int(lenValue.toInt32())
56
+ if count == 0 { return Data() }
57
+
58
+ let global = ctx.globalObject
59
+ let helper = global?.forProperty("__eliza_uint8_to_array")
60
+ if helper == nil || helper?.isUndefined == true {
61
+ let install = "globalThis.__eliza_uint8_to_array = function(u){const o=new Array(u.length); for (let i=0;i<u.length;i++){o[i]=u[i]|0;} return o;};"
62
+ ctx.evaluateScript(install)
63
+ }
64
+
65
+ guard let arrayValue = ctx.evaluateScript("globalThis.__eliza_uint8_to_array")?
66
+ .call(withArguments: [self]),
67
+ let nsArray = arrayValue.toArray() else {
68
+ return nil
69
+ }
70
+ var out = Data(count: nsArray.count)
71
+ out.withUnsafeMutableBytes { (raw: UnsafeMutableRawBufferPointer) in
72
+ guard let base = raw.baseAddress else { return }
73
+ for (i, entry) in nsArray.enumerated() {
74
+ let n = (entry as? NSNumber)?.uint8Value ?? 0
75
+ base.storeBytes(of: n, toByteOffset: i, as: UInt8.self)
76
+ }
77
+ }
78
+ return out
79
+ }
80
+
81
+ /// Returns a string array from a JS array of strings. Returns nil if not
82
+ /// an array.
83
+ func toStringArray() -> [String]? {
84
+ guard isArray else { return nil }
85
+ guard let arr = toArray() else { return nil }
86
+ var out: [String] = []
87
+ out.reserveCapacity(arr.count)
88
+ for item in arr {
89
+ if let s = item as? String {
90
+ out.append(s)
91
+ } else if let n = item as? NSNumber {
92
+ out.append(n.stringValue)
93
+ } else {
94
+ out.append(String(describing: item))
95
+ }
96
+ }
97
+ return out
98
+ }
99
+
100
+ /// Returns a [String: String] map from a JS object whose values are strings.
101
+ func toStringMap() -> [String: String] {
102
+ var out: [String: String] = [:]
103
+ guard let obj = toObject() as? [String: Any] else { return out }
104
+ for (k, v) in obj {
105
+ if let s = v as? String {
106
+ out[k] = s
107
+ } else if let n = v as? NSNumber {
108
+ out[k] = n.stringValue
109
+ } else {
110
+ out[k] = String(describing: v)
111
+ }
112
+ }
113
+ return out
114
+ }
115
+ }
116
+
117
+ public extension JSContext {
118
+ /// Returns a fresh Uint8Array JSValue for the given Swift `Data`.
119
+ func newUint8Array(_ data: Data) -> JSValue {
120
+ // Constructing a Uint8Array directly from raw bytes through the public
121
+ // JSC API requires either JSObjectMakeTypedArrayWithBytesNoCopy or
122
+ // round-tripping through JS. We avoid the C API for portability.
123
+ // Strategy: stash bytes in a JS Array of numbers, then convert via
124
+ // Uint8Array.from().
125
+ let helper = """
126
+ (function(arr){return Uint8Array.from(arr);})
127
+ """
128
+ let factory = evaluateScript(helper)
129
+ var ints: [Int] = []
130
+ ints.reserveCapacity(data.count)
131
+ data.withUnsafeBytes { (raw: UnsafeRawBufferPointer) in
132
+ guard let base = raw.baseAddress else { return }
133
+ for i in 0..<data.count {
134
+ let b = base.load(fromByteOffset: i, as: UInt8.self)
135
+ ints.append(Int(b))
136
+ }
137
+ }
138
+ let result = factory?.call(withArguments: [ints])
139
+ return result ?? JSValue(undefinedIn: self)
140
+ }
141
+
142
+ /// Installs a host function on `globalThis.__ELIZA_BRIDGE__[name]`.
143
+ /// `body` runs on the JSContext queue (the caller's thread).
144
+ func installBridgeFunction(name: String, _ body: @escaping ([JSValue]) -> Any?) {
145
+ let block: @convention(block) () -> Any? = {
146
+ let args = JSContext.currentArguments() as? [JSValue] ?? []
147
+ return body(args)
148
+ }
149
+ let global = globalObject!
150
+ var bridge = global.forProperty("__ELIZA_BRIDGE__")
151
+ if bridge == nil || bridge?.isUndefined == true || bridge?.isNull == true {
152
+ evaluateScript("globalThis.__ELIZA_BRIDGE__ = {};")
153
+ bridge = global.forProperty("__ELIZA_BRIDGE__")
154
+ }
155
+ bridge?.setObject(unsafeBitCast(block, to: AnyObject.self), forKeyedSubscript: name as NSString)
156
+ }
157
+ }
158
+
159
+ // MARK: - Exception bridging
160
+
161
+ public struct JSRuntimeError: Error, CustomStringConvertible {
162
+ public let message: String
163
+ public let stack: String?
164
+
165
+ public init(message: String, stack: String? = nil) {
166
+ self.message = message
167
+ self.stack = stack
168
+ }
169
+
170
+ public var description: String {
171
+ if let s = stack, !s.isEmpty {
172
+ return "\(message)\n\(s)"
173
+ }
174
+ return message
175
+ }
176
+ }
177
+
178
+ public extension JSContext {
179
+ /// Reads and clears the pending exception on the context. Returns a
180
+ /// Swift error if one was present.
181
+ func takeException() -> JSRuntimeError? {
182
+ guard let exc = self.exception else { return nil }
183
+ defer { self.exception = nil }
184
+ let message = exc.toString() ?? "Unknown JS error"
185
+ let stack = exc.objectForKeyedSubscript("stack")?.toString()
186
+ return JSRuntimeError(message: message, stack: stack)
187
+ }
188
+ }
189
+
190
+ // MARK: - JSManagedValue wrapper
191
+
192
+ /// Holds a JS callback function safely across Swift queue hops. JSManagedValue
193
+ /// is required because raw JSValue retention can deadlock the GC.
194
+ public final class ManagedCallback {
195
+ public let managed: JSManagedValue
196
+ public weak var context: JSContext?
197
+
198
+ public init?(value: JSValue) {
199
+ guard value.isObject else { return nil }
200
+ self.context = value.context
201
+ self.managed = JSManagedValue(value: value)
202
+ // Hand the managed value to the VM so it survives GC sweeps.
203
+ value.context?.virtualMachine.addManagedReference(self.managed, withOwner: self)
204
+ }
205
+
206
+ deinit {
207
+ context?.virtualMachine.removeManagedReference(managed, withOwner: self)
208
+ }
209
+
210
+ /// Invokes the callback on the JSContext queue.
211
+ public func call(args: [Any] = []) {
212
+ RuntimeQueue.dispatchOnJS { [weak self] in
213
+ guard let self = self else { return }
214
+ guard let value = self.managed.value else { return }
215
+ _ = value.call(withArguments: args)
216
+ }
217
+ }
218
+
219
+ /// Synchronous-on-queue call, intended for use when already on the
220
+ /// JSContext queue. Returns the call's JSValue result.
221
+ @discardableResult
222
+ public func callSync(args: [Any] = []) -> JSValue? {
223
+ guard let value = managed.value else { return nil }
224
+ return value.call(withArguments: args)
225
+ }
226
+ }
@@ -0,0 +1,46 @@
1
+ import Foundation
2
+
3
+ /// Resolved sandbox directories handed to bridge modules at install time.
4
+ /// Each path is absolute and stable for the lifetime of the process.
5
+ public struct SandboxPaths {
6
+ public let appSupport: URL
7
+ public let documents: URL
8
+ public let caches: URL
9
+ public let tmp: URL
10
+ public let bundle: URL
11
+
12
+ public init(appBundle: Bundle = .main, brand: String = "Eliza") {
13
+ let fm = FileManager.default
14
+ let supportRoot = (try? fm.url(
15
+ for: .applicationSupportDirectory,
16
+ in: .userDomainMask,
17
+ appropriateFor: nil,
18
+ create: true
19
+ )) ?? URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Library/Application Support")
20
+
21
+ let appSupportURL = supportRoot.appendingPathComponent(brand, isDirectory: true)
22
+ if !fm.fileExists(atPath: appSupportURL.path) {
23
+ try? fm.createDirectory(at: appSupportURL, withIntermediateDirectories: true)
24
+ }
25
+ self.appSupport = appSupportURL
26
+
27
+ let docs = (try? fm.url(
28
+ for: .documentDirectory,
29
+ in: .userDomainMask,
30
+ appropriateFor: nil,
31
+ create: true
32
+ )) ?? URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Documents")
33
+ self.documents = docs
34
+
35
+ let caches = (try? fm.url(
36
+ for: .cachesDirectory,
37
+ in: .userDomainMask,
38
+ appropriateFor: nil,
39
+ create: true
40
+ )) ?? URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Library/Caches")
41
+ self.caches = caches
42
+
43
+ self.tmp = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
44
+ self.bundle = appBundle.bundleURL
45
+ }
46
+ }
@@ -0,0 +1,238 @@
1
+ import Foundation
2
+ import JavaScriptCore
3
+ import CryptoKit
4
+ import CommonCrypto
5
+
6
+ /// Implements the `crypto_*` host functions from `BRIDGE_CONTRACT.md`.
7
+ ///
8
+ /// SHA/HMAC/AES-GCM go through CryptoKit. PBKDF2 falls through to
9
+ /// CommonCrypto's `CCKeyDerivationPBKDF` because CryptoKit doesn't expose
10
+ /// PBKDF2 directly.
11
+ public final class CryptoBridge {
12
+ public init() {}
13
+
14
+ public func install(into ctx: JSContext) {
15
+ ctx.installBridgeFunction(name: "crypto_random_bytes") { args in
16
+ guard let len = args.first?.toNumber()?.intValue, len > 0 else {
17
+ return ctx.newUint8Array(Data())
18
+ }
19
+ var bytes = [UInt8](repeating: 0, count: len)
20
+ let status = SecRandomCopyBytes(kSecRandomDefault, len, &bytes)
21
+ if status != errSecSuccess {
22
+ // Fall back to arc4random which can't fail.
23
+ for i in 0..<len { bytes[i] = UInt8.random(in: 0...255) }
24
+ }
25
+ return ctx.newUint8Array(Data(bytes))
26
+ }
27
+
28
+ ctx.installBridgeFunction(name: "crypto_random_uuid") { _ in
29
+ return UUID().uuidString.lowercased()
30
+ }
31
+
32
+ ctx.installBridgeFunction(name: "crypto_hash") { args in
33
+ guard args.count >= 2,
34
+ let algo = args[0].toString(),
35
+ let data = args[1].toData() else {
36
+ return NSNull()
37
+ }
38
+ let out = Self.hash(algo: algo, data: data)
39
+ guard let out = out else { return NSNull() }
40
+ return ctx.newUint8Array(out)
41
+ }
42
+
43
+ ctx.installBridgeFunction(name: "crypto_hmac") { args in
44
+ guard args.count >= 3,
45
+ let algo = args[0].toString(),
46
+ let key = args[1].toData(),
47
+ let data = args[2].toData() else {
48
+ return NSNull()
49
+ }
50
+ let out = Self.hmac(algo: algo, key: key, data: data)
51
+ guard let out = out else { return NSNull() }
52
+ return ctx.newUint8Array(out)
53
+ }
54
+
55
+ ctx.installBridgeFunction(name: "crypto_pbkdf2") { args in
56
+ guard args.count >= 5,
57
+ let password = args[0].toData(),
58
+ let salt = args[1].toData(),
59
+ let iter = args[2].toNumber()?.uint32Value,
60
+ let keyLen = args[3].toNumber()?.intValue,
61
+ let digest = args[4].toString() else {
62
+ return NSNull()
63
+ }
64
+ let out = Self.pbkdf2(
65
+ password: password,
66
+ salt: salt,
67
+ iterations: iter,
68
+ keyLength: keyLen,
69
+ digest: digest
70
+ )
71
+ guard let out = out else { return NSNull() }
72
+ return ctx.newUint8Array(out)
73
+ }
74
+
75
+ ctx.installBridgeFunction(name: "crypto_aes_gcm_encrypt") { args in
76
+ guard args.count >= 3,
77
+ let key = args[0].toData(),
78
+ let nonce = args[1].toData(),
79
+ let plaintext = args[2].toData() else {
80
+ return NSNull()
81
+ }
82
+ let aad: Data? = args.count >= 4 ? args[3].toData() : nil
83
+ guard let sealed = Self.aesGcmEncrypt(key: key, nonce: nonce, plaintext: plaintext, aad: aad) else {
84
+ return NSNull()
85
+ }
86
+ return [
87
+ "ciphertext": ctx.newUint8Array(sealed.ciphertext),
88
+ "tag": ctx.newUint8Array(sealed.tag),
89
+ ] as [String: Any]
90
+ }
91
+
92
+ ctx.installBridgeFunction(name: "crypto_aes_gcm_decrypt") { args in
93
+ guard args.count >= 4,
94
+ let key = args[0].toData(),
95
+ let nonce = args[1].toData(),
96
+ let ciphertext = args[2].toData(),
97
+ let tag = args[3].toData() else {
98
+ return NSNull()
99
+ }
100
+ let aad: Data? = args.count >= 5 ? args[4].toData() : nil
101
+ guard let plain = Self.aesGcmDecrypt(key: key, nonce: nonce, ciphertext: ciphertext, tag: tag, aad: aad) else {
102
+ return NSNull()
103
+ }
104
+ return ctx.newUint8Array(plain)
105
+ }
106
+ }
107
+
108
+ // MARK: - Hashes
109
+
110
+ static func hash(algo: String, data: Data) -> Data? {
111
+ switch algo.lowercased() {
112
+ case "sha256":
113
+ return Data(SHA256.hash(data: data))
114
+ case "sha512":
115
+ return Data(SHA512.hash(data: data))
116
+ case "sha1":
117
+ return Data(Insecure.SHA1.hash(data: data))
118
+ case "md5":
119
+ return Data(Insecure.MD5.hash(data: data))
120
+ default:
121
+ return nil
122
+ }
123
+ }
124
+
125
+ static func hmac(algo: String, key: Data, data: Data) -> Data? {
126
+ let symmetricKey = SymmetricKey(data: key)
127
+ switch algo.lowercased() {
128
+ case "sha256":
129
+ let mac = HMAC<SHA256>.authenticationCode(for: data, using: symmetricKey)
130
+ return Data(mac)
131
+ case "sha512":
132
+ let mac = HMAC<SHA512>.authenticationCode(for: data, using: symmetricKey)
133
+ return Data(mac)
134
+ case "sha1":
135
+ let mac = HMAC<Insecure.SHA1>.authenticationCode(for: data, using: symmetricKey)
136
+ return Data(mac)
137
+ default:
138
+ return nil
139
+ }
140
+ }
141
+
142
+ static func pbkdf2(
143
+ password: Data,
144
+ salt: Data,
145
+ iterations: UInt32,
146
+ keyLength: Int,
147
+ digest: String
148
+ ) -> Data? {
149
+ let prf: CCPseudoRandomAlgorithm
150
+ switch digest.lowercased() {
151
+ case "sha256": prf = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256)
152
+ case "sha512": prf = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512)
153
+ case "sha1": prf = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1)
154
+ default: return nil
155
+ }
156
+
157
+ var derived = Data(count: keyLength)
158
+ let status = derived.withUnsafeMutableBytes { (derivedRaw: UnsafeMutableRawBufferPointer) -> Int32 in
159
+ guard let derivedBase = derivedRaw.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
160
+ return Int32(kCCParamError)
161
+ }
162
+ return password.withUnsafeBytes { (pwRaw: UnsafeRawBufferPointer) -> Int32 in
163
+ guard let pwBase = pwRaw.baseAddress?.assumingMemoryBound(to: Int8.self) else {
164
+ return Int32(kCCParamError)
165
+ }
166
+ return salt.withUnsafeBytes { (saltRaw: UnsafeRawBufferPointer) -> Int32 in
167
+ guard let saltBase = saltRaw.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
168
+ return Int32(kCCParamError)
169
+ }
170
+ return CCKeyDerivationPBKDF(
171
+ CCPBKDFAlgorithm(kCCPBKDF2),
172
+ pwBase, password.count,
173
+ saltBase, salt.count,
174
+ prf,
175
+ iterations,
176
+ derivedBase, keyLength
177
+ )
178
+ }
179
+ }
180
+ }
181
+ if status != 0 { return nil }
182
+ return derived
183
+ }
184
+
185
+ // MARK: - AES-GCM
186
+
187
+ struct SealedBlob {
188
+ let ciphertext: Data
189
+ let tag: Data
190
+ }
191
+
192
+ static func aesGcmEncrypt(key: Data, nonce: Data, plaintext: Data, aad: Data?) -> SealedBlob? {
193
+ guard key.count == 16 || key.count == 32 else { return nil }
194
+ guard nonce.count == 12 else { return nil }
195
+ let symmetric = SymmetricKey(data: key)
196
+ guard let aesNonce = try? AES.GCM.Nonce(data: nonce) else { return nil }
197
+ do {
198
+ let sealed: AES.GCM.SealedBox
199
+ if let aad = aad {
200
+ sealed = try AES.GCM.seal(plaintext, using: symmetric, nonce: aesNonce, authenticating: aad)
201
+ } else {
202
+ sealed = try AES.GCM.seal(plaintext, using: symmetric, nonce: aesNonce)
203
+ }
204
+ return SealedBlob(ciphertext: sealed.ciphertext, tag: sealed.tag)
205
+ } catch {
206
+ return nil
207
+ }
208
+ }
209
+
210
+ static func aesGcmDecrypt(key: Data, nonce: Data, ciphertext: Data, tag: Data, aad: Data?) -> Data? {
211
+ guard key.count == 16 || key.count == 32 else { return nil }
212
+ guard nonce.count == 12 else { return nil }
213
+ let symmetric = SymmetricKey(data: key)
214
+ guard let aesNonce = try? AES.GCM.Nonce(data: nonce),
215
+ let sealed = try? AES.GCM.SealedBox(nonce: aesNonce, ciphertext: ciphertext, tag: tag) else {
216
+ return nil
217
+ }
218
+ do {
219
+ if let aad = aad {
220
+ return try AES.GCM.open(sealed, using: symmetric, authenticating: aad)
221
+ } else {
222
+ return try AES.GCM.open(sealed, using: symmetric)
223
+ }
224
+ } catch {
225
+ return nil
226
+ }
227
+ }
228
+ }
229
+
230
+ // MARK: - JSValue numeric helpers used by the bridge
231
+
232
+ extension JSValue {
233
+ /// Returns a Swift NSNumber for numeric JSValues. Nil for non-numbers.
234
+ func toNumber() -> NSNumber? {
235
+ guard isNumber else { return nil }
236
+ return NSNumber(value: toDouble())
237
+ }
238
+ }
@@ -0,0 +1,28 @@
1
+ #import <sqlite3.h>
2
+ #import <stddef.h>
3
+
4
+ __attribute__((weak)) int sqlite3_vec_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
5
+ (void)db;
6
+ (void)pzErrMsg;
7
+ (void)pApi;
8
+ return SQLITE_MISUSE;
9
+ }
10
+
11
+ __attribute__((weak)) const char *sqlite3_vec_version(void) {
12
+ return NULL;
13
+ }
14
+
15
+ int eliza_sqlite_vec_is_available(void) {
16
+ return sqlite3_vec_version() != NULL ? 1 : 0;
17
+ }
18
+
19
+ const char *eliza_sqlite_vec_version(void) {
20
+ return sqlite3_vec_version();
21
+ }
22
+
23
+ int eliza_sqlite_vec_register(sqlite3 *db, char **pzErrMsg) {
24
+ if (eliza_sqlite_vec_is_available() != 1) {
25
+ return SQLITE_MISUSE;
26
+ }
27
+ return sqlite3_vec_init(db, pzErrMsg, NULL);
28
+ }