@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.
- package/ElizaosCapacitorBunRuntime.podspec +54 -0
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/esm/definitions.d.ts +136 -0
- package/dist/esm/definitions.d.ts.map +1 -0
- package/dist/esm/definitions.js +14 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +19 -0
- package/dist/esm/web.d.ts.map +1 -0
- package/dist/esm/web.js +44 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +63 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +66 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/ElizaBunRuntimePlugin/BridgeInstaller.swift +94 -0
- package/ios/Sources/ElizaBunRuntimePlugin/ElizaBunRuntime.swift +705 -0
- package/ios/Sources/ElizaBunRuntimePlugin/ElizaBunRuntimePlugin.swift +1109 -0
- package/ios/Sources/ElizaBunRuntimePlugin/FullBunEngineHost.swift +677 -0
- package/ios/Sources/ElizaBunRuntimePlugin/JSContextHelpers.swift +226 -0
- package/ios/Sources/ElizaBunRuntimePlugin/SandboxPaths.swift +46 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/CryptoBridge.swift +238 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/ElizaSqliteVecBridge.m +28 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/FSBridge.swift +270 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/HTTPBridge.swift +153 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/HTTPServerBridge.swift +32 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/LlamaBridge.swift +233 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/LlamaBridgeImpl.swift +1863 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/LogBridge.swift +36 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/PathsBridge.swift +41 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/ProcessBridge.swift +80 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/SqliteBridge.swift +406 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/SqliteBridgeInstaller.swift +17 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/SqliteVecLoader.swift +66 -0
- package/ios/Sources/ElizaBunRuntimePlugin/bridge/UIBridge.swift +72 -0
- package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlChinesePhonemizer.swift +313 -0
- package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlConfiguration.swift +28 -0
- package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlEngine.swift +325 -0
- package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlHindiPhonemizer.swift +150 -0
- package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlJapanesePhonemizer.swift +209 -0
- package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlLatinPhonemizer.swift +374 -0
- package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlModel.swift +87 -0
- package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlPhonemizer.swift +679 -0
- package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlPronunciationDicts.swift +131 -0
- package/ios/Sources/ElizaBunRuntimePlugin/kokoro/KokoroCoreMlSupport.swift +24 -0
- package/ios/Tests/llama-bridge-smoke-main.swift +92 -0
- package/package.json +68 -0
- package/src/bridge-contract.test.ts +127 -0
- package/src/definitions.d.ts +136 -0
- package/src/definitions.d.ts.map +1 -0
- package/src/definitions.ts +152 -0
- package/src/index.d.ts +9 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.ts +16 -0
- package/src/web.d.ts +19 -0
- package/src/web.d.ts.map +1 -0
- 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
|
+
}
|