@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,131 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Pronunciation dictionaries for Kokoro TTS multilingual support.
|
|
4
|
+
///
|
|
5
|
+
/// Large dictionaries (French, Portuguese, Hindi) are loaded from JSON resource
|
|
6
|
+
/// files at runtime. Smaller ones (Spanish, Italian, German, Korean) are
|
|
7
|
+
/// embedded as Swift literals. Source: ipa-dict (MIT, open-dict-data/ipa-dict)
|
|
8
|
+
/// and standard phonetic references.
|
|
9
|
+
enum PronunciationDicts {
|
|
10
|
+
|
|
11
|
+
// MARK: - JSON Resource Loading
|
|
12
|
+
|
|
13
|
+
private static func loadJSON(_ name: String) -> [String: String] {
|
|
14
|
+
let url = Bundle.main.url(forResource: name, withExtension: "json")
|
|
15
|
+
?? Bundle.main.url(forResource: name, withExtension: "json", subdirectory: "KokoroTTS")
|
|
16
|
+
guard let url else { return [:] }
|
|
17
|
+
guard let data = try? Data(contentsOf: url),
|
|
18
|
+
let dict = try? JSONDecoder().decode([String: String].self, from: data) else {
|
|
19
|
+
return [:]
|
|
20
|
+
}
|
|
21
|
+
return dict
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// MARK: - French (3093 entries, JSON)
|
|
25
|
+
|
|
26
|
+
static let fr: [String: String] = loadJSON("dict_fr")
|
|
27
|
+
|
|
28
|
+
// MARK: - Portuguese (2001 entries, JSON)
|
|
29
|
+
|
|
30
|
+
static let pt: [String: String] = loadJSON("dict_pt")
|
|
31
|
+
|
|
32
|
+
// MARK: - Hindi (146 entries, JSON)
|
|
33
|
+
|
|
34
|
+
static let hi: [String: String] = loadJSON("dict_hi")
|
|
35
|
+
|
|
36
|
+
// MARK: - Spanish (125 entries)
|
|
37
|
+
|
|
38
|
+
static let es: [String: String] = [
|
|
39
|
+
"adiós": "aðjˈos", "agua": "ˈaɣwa", "ahora": "aˈoɾa", "al": "ˈal",
|
|
40
|
+
"alto": "ˈalto", "amarillo": "ˌamaɾˈiʎo", "amigo": "amˈiɣo",
|
|
41
|
+
"antes": "ˈantes", "aquí": "akˈi", "azul": "aθˈul", "año": "ˈaɲo",
|
|
42
|
+
"bajo": "bˈaxo", "bien": "bjˈen", "blanco": "blˈanko", "boca": "bˈoka",
|
|
43
|
+
"brazo": "bɾˈaθo", "bueno": "bwˈeno", "buenos": "bwˈenos",
|
|
44
|
+
"cabeza": "kaβˈeθa", "café": "kafˈe", "calle": "kˈaʎe", "casa": "kˈasa",
|
|
45
|
+
"cinco": "θˈinko", "ciudad": "θjuðˈad", "comida": "komˈiða",
|
|
46
|
+
"como": "kˈomo", "con": "kˈon", "corazón": "kˌoɾaθˈon", "cosa": "kˈosa",
|
|
47
|
+
"cuando": "kwˈando", "cuatro": "kwˈatɾo", "dar": "dˈaɾ", "de": "dˈe",
|
|
48
|
+
"decir": "deθˈiɾ", "del": "dˈel", "desde": "dˈesðe", "después": "despwˈes",
|
|
49
|
+
"donde": "dˈonde", "dos": "dˈos", "día": "dˈia", "días": "dˈias",
|
|
50
|
+
"el": "ˈel", "ella": "ˈeʎa", "ellas": "ˈeʎas", "ellos": "ˈeʎos",
|
|
51
|
+
"en": "ˈen", "entre": "ˈɛntɾe", "eso": "ˈeso", "estar": "estˈaɾ",
|
|
52
|
+
"esto": "ˈesto", "familia": "famˈilja", "gracias": "ɡɾˈaθjas",
|
|
53
|
+
"grande": "ɡɾˈande", "haber": "aβˈeɾ", "hacer": "aθˈeɾ", "hasta": "ˈasta",
|
|
54
|
+
"hermana": "eɾmˈana", "hermano": "eɾmˈano", "hija": "ˈixa",
|
|
55
|
+
"hijo": "ˈixo", "hola": "ˈola", "hombre": "ˈombɾe", "ir": "ˈiɾ",
|
|
56
|
+
"joven": "xˈoβen", "la": "lˈa", "las": "lˈas", "leche": "lˈetʃe",
|
|
57
|
+
"llegar": "ʎeɣˈaɾ", "los": "lˈos", "madre": "mˈaðɾe", "malo": "mˈalo",
|
|
58
|
+
"mano": "mˈano", "mesa": "mˈesa", "mujer": "muxˈeɾ", "mundo": "mˈundo",
|
|
59
|
+
"muy": "mˈuj", "más": "mˈas", "negro": "nˈeɣɾo", "niña": "nˈiɲa",
|
|
60
|
+
"niño": "nˈiɲo", "no": "nˈo", "nosotros": "nosˈotɾos", "nuevo": "nwˈeβo",
|
|
61
|
+
"nunca": "nˈunka", "ojo": "ˈoxo", "padre": "pˈaðɾe", "pan": "pˈan",
|
|
62
|
+
"para": "pˈaɾa", "país": "paˈis", "pequeño": "pekˈeɲo",
|
|
63
|
+
"perdón": "peɾðˈon", "pero": "pˈeɾo", "pie": "pjˈe", "poder": "poðˈeɾ",
|
|
64
|
+
"por": "pˈoɾ", "porque": "pˈoɾke", "prueba": "pɾuˈeβa",
|
|
65
|
+
"puerta": "pwˈeɾta", "querer": "keɾˈeɾ", "qué": "kˈe", "rojo": "rˈoxo",
|
|
66
|
+
"saber": "saβˈeɾ", "ser": "sˈer", "siempre": "sjˈempɾe", "sin": "sˈin",
|
|
67
|
+
"sobre": "sˈoβɾe", "sí": "sˈi", "también": "tambjˈen", "tener": "tenˈeɾ",
|
|
68
|
+
"tiempo": "tjˈempo", "todo": "tˈoðo", "tres": "tɾˈes", "tú": "tˈu",
|
|
69
|
+
"un": "ˈun", "una": "ˈuna", "uno": "ˈuno", "usted": "ustˈed",
|
|
70
|
+
"ventana": "bentˈana", "ver": "bˈeɾ", "verde": "bˈeɾðe", "vida": "bˈiða",
|
|
71
|
+
"viejo": "bjˈexo", "vino": "bˈino", "yo": "ʝˈo", "él": "ˈel",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
// MARK: - Italian (174 entries)
|
|
75
|
+
|
|
76
|
+
static let it: [String: String] = [
|
|
77
|
+
"acqua": "ˈakːwa", "alto": "ˈalto", "altro": "ˈaltro", "amico": "amˈiko",
|
|
78
|
+
"anche": "ˈanke", "andare": "andˈare", "anno": "ˈanno", "avere": "avˈere",
|
|
79
|
+
"bambina": "bambˈina", "bambino": "bambˈino", "basso": "bˈasso",
|
|
80
|
+
"bella": "bˈɛlla", "bello": "bˈɛllo", "bene": "bˈɛne", "bere": "bˈere",
|
|
81
|
+
"bianco": "bjˈanko", "blu": "blˈu", "bocca": "bˈokːa",
|
|
82
|
+
"braccio": "brˈatʃːo", "brutto": "brˈutːo", "buonasera": "bwˌɔnasˈera",
|
|
83
|
+
"buongiorno": "bʊondʒˈɔrno", "buono": "bʊˈɔno", "caffè": "kaffˈɛ",
|
|
84
|
+
"caldo": "kˈaldo", "casa": "kˈaza", "cattivo": "katːˈivo", "che": "kˈe",
|
|
85
|
+
"chi": "kˈi", "ciao": "tʃˈao", "cibo": "tʃˈibo", "cinque": "tʃˈinkwe",
|
|
86
|
+
"città": "tʃitːˈa", "come": "kˈome", "cosa": "kˈɔza", "cuore": "kʊˈɔre",
|
|
87
|
+
"dal": "dˈal", "dalla": "dˈalla", "dare": "dˈare", "debole": "dˈebole",
|
|
88
|
+
"dei": "dˈeɪ", "del": "dˈel", "della": "dˈella", "delle": "dˈelle",
|
|
89
|
+
"dello": "dˈello", "di": "dˈi", "dieci": "djˈɛtʃɪ", "dire": "dˈire",
|
|
90
|
+
"domani": "domˈanɪ", "donna": "dˈɔnna", "dopo": "dˈopo",
|
|
91
|
+
"dormire": "dormˈire", "dove": "dˈove", "dovere": "dovˈere", "due": "dˈue",
|
|
92
|
+
"erano": "ˈɛrano", "essere": "ˈɛssere", "famiglia": "famˈiʎa",
|
|
93
|
+
"fare": "fˈare", "felice": "felˈitʃe", "figlia": "fˈiʎa",
|
|
94
|
+
"figlio": "fˈiʎo", "finestra": "finˈɛstra", "forte": "fˈɔrte",
|
|
95
|
+
"fratello": "fratˈɛllo", "freddo": "frˈedːo", "gamba": "ɡˈamba",
|
|
96
|
+
"giallo": "dʒˈallo", "giorno": "dʒˈorno", "giovane": "dʒˈovane",
|
|
97
|
+
"gli": "ʎˈɪ", "grande": "ɡrˈande", "grazie": "ɡrˈatsje",
|
|
98
|
+
"ieri": "jˈɛrɪ", "il": "ˈiːl", "io": "ˈio", "la": "lˈa",
|
|
99
|
+
"latte": "lˈatːe", "le": "lˈe", "leggere": "lˈɛdʒːere", "lei": "lˈɛi",
|
|
100
|
+
"lo": "lˈo", "loro": "lˈɔro", "lui": "lˈui", "lungo": "lˈuŋɡo",
|
|
101
|
+
"ma": "mˈa", "madre": "mˈadre", "mai": "mˈaj", "mangiare": "mandʒˈare",
|
|
102
|
+
"mano": "mˈano", "mattina": "matːˈina", "migliore": "miʎˈore",
|
|
103
|
+
"molto": "mˈolto", "mondo": "mˈondo", "nero": "nˈero", "noi": "nˈoi",
|
|
104
|
+
"non": "nˈon", "notte": "nˈɔtːe", "nove": "nˈɔve", "nuovo": "nʊˈɔvo",
|
|
105
|
+
"occhio": "ˈɔkːio", "oggi": "ˈɔdʒːɪ", "ogni": "ˈoɲɲɪ", "ora": "ˈora",
|
|
106
|
+
"otto": "ˈɔtːo", "padre": "pˈadre", "paese": "paˈeze", "pane": "pˈane",
|
|
107
|
+
"parlare": "parlˈare", "peggiore": "pedʒːˈore", "pensare": "pensˈare",
|
|
108
|
+
"perché": "perkˈe", "piccolo": "pˈikːolo", "piede": "pjˈɛde",
|
|
109
|
+
"più": "pjˈu", "porta": "pˈɔrta", "potere": "potˈere", "prego": "prˈɛɡo",
|
|
110
|
+
"prima": "prˈima", "primo": "prˈimo", "prova": "prˈɔva",
|
|
111
|
+
"quale": "kwˈale", "quando": "kwˈando", "quanto": "kwˈanto",
|
|
112
|
+
"quattro": "kwˈatːro", "quello": "kwˈello", "questa": "kwˈesta",
|
|
113
|
+
"questo": "kwˈesto", "qui": "kwˈi", "rosso": "rˈosso",
|
|
114
|
+
"sapere": "sapˈere", "scrivere": "skrˈivere", "scusi": "skˈuzɪ",
|
|
115
|
+
"secondo": "sekˈondo", "sedia": "sˈɛdia", "sei": "sˈɛi",
|
|
116
|
+
"sempre": "sˈɛmpre", "sentire": "sentˈire", "sera": "sˈera",
|
|
117
|
+
"sette": "sˈɛtːe", "siamo": "sjˈamo", "siete": "sjˈete",
|
|
118
|
+
"sole": "sˈole", "sono": "sˈono", "sorella": "sorˈɛlla",
|
|
119
|
+
"splende": "splˈɛnde", "stare": "stˈare", "stata": "stˈata",
|
|
120
|
+
"stato": "stˈato", "stesso": "stˈesso", "strada": "strˈada",
|
|
121
|
+
"sì": "sˈiː", "tavola": "tˈavola", "tempo": "tˈɛmpo",
|
|
122
|
+
"terzo": "tˈɛrtso", "testa": "tˈɛsta", "tre": "trˈe",
|
|
123
|
+
"triste": "trˈiste", "tu": "tˈu", "tutto": "tˈutːo",
|
|
124
|
+
"ultimo": "ˈultimo", "un": "ˈun", "una": "ˈuna", "uno": "ˈuno",
|
|
125
|
+
"uomo": "wˈɔmo", "vecchio": "vˈɛkːio", "vedere": "vedˈere",
|
|
126
|
+
"venire": "venˈire", "verde": "vˈerde", "vino": "vˈino",
|
|
127
|
+
"vita": "vˈita", "voi": "vˈoi", "volere": "volˈere", "è": "ˈɛː",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
enum AudioModelError: LocalizedError {
|
|
5
|
+
case modelLoadFailed(modelId: String, reason: String)
|
|
6
|
+
case inferenceFailed(operation: String, reason: String)
|
|
7
|
+
case voiceNotFound(voice: String, searchPath: String)
|
|
8
|
+
|
|
9
|
+
var errorDescription: String? {
|
|
10
|
+
switch self {
|
|
11
|
+
case let .modelLoadFailed(modelId, reason):
|
|
12
|
+
return "Kokoro CoreML model load failed for \(modelId): \(reason)"
|
|
13
|
+
case let .inferenceFailed(operation, reason):
|
|
14
|
+
return "Kokoro CoreML inference failed during \(operation): \(reason)"
|
|
15
|
+
case let .voiceNotFound(voice, searchPath):
|
|
16
|
+
return "Kokoro CoreML voice not found: \(voice). \(searchPath)"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
enum AudioLog {
|
|
22
|
+
static let inference = Logger(subsystem: "ai.eliza.ios", category: "KokoroCoreMLInference")
|
|
23
|
+
static let modelLoading = Logger(subsystem: "ai.eliza.ios", category: "KokoroCoreMLModelLoading")
|
|
24
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// llama-bridge-smoke-main.swift — manual smoke test for LlamaBridgeImpl.
|
|
2
|
+
//
|
|
3
|
+
// This is NOT a unit test — it's a top-level Swift script for sanity-checking
|
|
4
|
+
// the real backend (`LlamaBridgeImpl`) against a real GGUF model. Run after
|
|
5
|
+
// `build-ios.sh` produces `LlamaCpp.xcframework` and `download-first-light.sh`
|
|
6
|
+
// places a model on disk.
|
|
7
|
+
//
|
|
8
|
+
// The smoke does NOT exercise the JS bridge surface — that's `LlamaBridge.swift`'s
|
|
9
|
+
// job. Here we just confirm that the C-API bindings, sampler chain, and decode
|
|
10
|
+
// loop produce coherent text.
|
|
11
|
+
//
|
|
12
|
+
// Usage on an iOS device (the only platform where this can actually run,
|
|
13
|
+
// since the static lib targets iOS device or simulator, not macOS):
|
|
14
|
+
//
|
|
15
|
+
// 1. Add this file + LlamaBridgeImpl.swift to an iOS test target inside
|
|
16
|
+
// Xcode. Set the target's "Other Linker Flags" to include the path to
|
|
17
|
+
// LlamaCpp.xcframework (or list it under Frameworks/Libraries).
|
|
18
|
+
//
|
|
19
|
+
// 2. Run as a unit test, or as the main entry point of an iOS test app.
|
|
20
|
+
//
|
|
21
|
+
// 3. Pass the model path via Info.plist (LlamaSmokeModelPath) or hardcode
|
|
22
|
+
// it for a quick manual run.
|
|
23
|
+
//
|
|
24
|
+
// Expected output:
|
|
25
|
+
// [smoke] Hardware: backend=metal, total_ram=8.0 GB, metal_supported=true
|
|
26
|
+
// [smoke] Loading model …
|
|
27
|
+
// [smoke] context_id=1
|
|
28
|
+
// [smoke] Generating …
|
|
29
|
+
// [smoke] >>> Hello! I'm an AI assistant. How can I help you today?
|
|
30
|
+
// [smoke] Generated 16 tokens in 234 ms.
|
|
31
|
+
|
|
32
|
+
import Foundation
|
|
33
|
+
|
|
34
|
+
@main
|
|
35
|
+
struct LlamaSmoke {
|
|
36
|
+
static func main() {
|
|
37
|
+
guard CommandLine.arguments.count >= 2 else {
|
|
38
|
+
FileHandle.standardError.write(Data("usage: llama-bridge-smoke <model.gguf>\n".utf8))
|
|
39
|
+
exit(2)
|
|
40
|
+
}
|
|
41
|
+
let modelPath = CommandLine.arguments[1]
|
|
42
|
+
|
|
43
|
+
let impl = LlamaBridgeImpl.shared
|
|
44
|
+
|
|
45
|
+
// 1. Hardware probe.
|
|
46
|
+
let hw = impl.hardwareInfo()
|
|
47
|
+
print("[smoke] Hardware: backend=\(hw.backend), total_ram=\(String(format: "%.2f", hw.totalRamGB)) GB, metal_supported=\(hw.metalSupported), simulator=\(hw.isSimulator)")
|
|
48
|
+
|
|
49
|
+
// 2. Load model.
|
|
50
|
+
print("[smoke] Loading model from \(modelPath) …")
|
|
51
|
+
let loadResult = impl.loadModel(
|
|
52
|
+
path: modelPath,
|
|
53
|
+
contextSize: 2048,
|
|
54
|
+
useGPU: true
|
|
55
|
+
)
|
|
56
|
+
guard let contextId = loadResult.contextId else {
|
|
57
|
+
FileHandle.standardError.write(Data("[smoke] load failed: \(loadResult.error ?? "unknown")\n".utf8))
|
|
58
|
+
exit(3)
|
|
59
|
+
}
|
|
60
|
+
print("[smoke] context_id=\(contextId)")
|
|
61
|
+
|
|
62
|
+
// 3. Generate (streaming).
|
|
63
|
+
print("[smoke] Generating …")
|
|
64
|
+
print("[smoke] >>> ", terminator: "")
|
|
65
|
+
fflush(stdout)
|
|
66
|
+
let genResult = impl.generate(
|
|
67
|
+
contextId: contextId,
|
|
68
|
+
prompt: "<|im_start|>user\nSay hello in one short sentence.<|im_end|>\n<|im_start|>assistant\n",
|
|
69
|
+
maxTokens: 64,
|
|
70
|
+
temperature: 0.7,
|
|
71
|
+
topP: 0.9,
|
|
72
|
+
stopSequences: ["<|im_end|>"],
|
|
73
|
+
onToken: { tok, last in
|
|
74
|
+
if !last {
|
|
75
|
+
print(tok, terminator: "")
|
|
76
|
+
fflush(stdout)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
print() // newline after streamed tokens
|
|
81
|
+
|
|
82
|
+
if let err = genResult.error {
|
|
83
|
+
FileHandle.standardError.write(Data("[smoke] generate failed: \(err)\n".utf8))
|
|
84
|
+
} else {
|
|
85
|
+
print("[smoke] Generated \(genResult.outputTokens) tokens in \(String(format: "%.0f", genResult.durationMs)) ms (prompt was \(genResult.promptTokens) tokens).")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 4. Free.
|
|
89
|
+
impl.free(contextId: contextId)
|
|
90
|
+
print("[smoke] Freed context \(contextId). Done.")
|
|
91
|
+
}
|
|
92
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elizaos/capacitor-bun-runtime",
|
|
3
|
+
"version": "2.0.3-beta.2",
|
|
4
|
+
"description": "Embedded Bun-shape JS runtime bridge for iOS and Android",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"capacitor",
|
|
7
|
+
"bun",
|
|
8
|
+
"javascriptcore",
|
|
9
|
+
"embedded-runtime",
|
|
10
|
+
"eliza"
|
|
11
|
+
],
|
|
12
|
+
"main": "./dist/plugin.cjs.js",
|
|
13
|
+
"module": "./dist/esm/index.js",
|
|
14
|
+
"types": "./dist/esm/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/esm/index.d.ts",
|
|
18
|
+
"bun": "./src/index.ts",
|
|
19
|
+
"development": "./src/index.ts",
|
|
20
|
+
"import": "./dist/esm/index.js",
|
|
21
|
+
"require": "./dist/plugin.cjs.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"unpkg": "dist/plugin.js",
|
|
26
|
+
"files": [
|
|
27
|
+
"ios/Sources/",
|
|
28
|
+
"ios/Tests/",
|
|
29
|
+
"ElizaosCapacitorBunRuntime.podspec",
|
|
30
|
+
"dist/",
|
|
31
|
+
"src/",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"author": "elizaOS",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/elizaOS/eliza.git",
|
|
39
|
+
"directory": "plugins/plugin-native-bun-runtime"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "node ../../packages/scripts/with-package-build-lock.mjs plugins/plugin-native-bun-runtime -- bun run build:unlocked",
|
|
43
|
+
"clean": "node ../../packages/scripts/rm-path-recursive.mjs dist",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"prepublishOnly": "bun run build",
|
|
46
|
+
"watch": "tsc --watch",
|
|
47
|
+
"build:unlocked": "bun run clean && tsc && bunx rollup -c rollup.config.mjs"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@capacitor/core": "^8.3.1",
|
|
51
|
+
"rollup": "^4.60.2",
|
|
52
|
+
"typescript": "^6.0.3",
|
|
53
|
+
"vitest": "^4.0.17"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@capacitor/core": "^8.3.1"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"capacitor": {
|
|
62
|
+
"ios": {
|
|
63
|
+
"src": "ios",
|
|
64
|
+
"podName": "ElizaosCapacitorBunRuntime"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"gitHead": "82fe0f44215954c2417328203f5bd6510985c1fc"
|
|
68
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the ElizaBunRuntime TypeScript public surface.
|
|
3
|
+
*
|
|
4
|
+
* These tests exercise the JS-side contract only — no native plugin is
|
|
5
|
+
* loaded. They verify:
|
|
6
|
+
* - The web fallback returns the correct unavailable shapes so callers can
|
|
7
|
+
* detect the unavailable-on-web case without throwing.
|
|
8
|
+
* - `StartOptions` and `StartResult` shapes are correct at the type level.
|
|
9
|
+
* - Malformed / missing required fields produce clear rejections in the
|
|
10
|
+
* web fallback (mirrors what native implementations must also do).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, expect, it } from "vitest";
|
|
14
|
+
import { ElizaBunRuntimeWeb } from "./web";
|
|
15
|
+
|
|
16
|
+
describe("ElizaBunRuntimeWeb — unavailable-on-web returns", () => {
|
|
17
|
+
const web = new ElizaBunRuntimeWeb();
|
|
18
|
+
|
|
19
|
+
it("start() resolves with ok:false and an error string", async () => {
|
|
20
|
+
const result = await web.start({});
|
|
21
|
+
expect(result.ok).toBe(false);
|
|
22
|
+
expect(typeof result.error).toBe("string");
|
|
23
|
+
expect(result.error?.length).toBeGreaterThan(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("getStatus() resolves with ready:false", async () => {
|
|
27
|
+
const status = await web.getStatus();
|
|
28
|
+
expect(status.ready).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("stop() resolves without throwing", async () => {
|
|
32
|
+
await expect(web.stop()).resolves.toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("ElizaBunRuntimeWeb — malformed-input rejection", () => {
|
|
37
|
+
const web = new ElizaBunRuntimeWeb();
|
|
38
|
+
|
|
39
|
+
it("sendMessage() rejects with an unavailable error on web", async () => {
|
|
40
|
+
await expect(web.sendMessage({ message: "hello" })).rejects.toThrow();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("call() rejects with an unavailable error on web", async () => {
|
|
44
|
+
await expect(web.call({ method: "status" })).rejects.toThrow();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it.each([
|
|
48
|
+
{ message: "" },
|
|
49
|
+
{ message: "<script>alert(1)</script>", conversationId: "../escape" },
|
|
50
|
+
{ message: "x".repeat(100_000) },
|
|
51
|
+
])("does not reflect hostile sendMessage payloads %#", async (options) => {
|
|
52
|
+
await expect(web.sendMessage(options)).rejects.toThrow(
|
|
53
|
+
"ElizaBunRuntime.sendMessage is unavailable on web.",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it.each([
|
|
58
|
+
{ method: "" },
|
|
59
|
+
{ method: "__proto__", args: { polluted: true } },
|
|
60
|
+
{ method: "status", args: { nested: { value: ["x"] } } },
|
|
61
|
+
])("does not reflect hostile call payloads %#", async (options) => {
|
|
62
|
+
await expect(web.call(options)).rejects.toThrow(
|
|
63
|
+
"ElizaBunRuntime.call is unavailable on web.",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("ElizaBunRuntimeWeb — local TTS web fallback", () => {
|
|
69
|
+
const web = new ElizaBunRuntimeWeb();
|
|
70
|
+
|
|
71
|
+
it("reports local TTS as unavailable on web", async () => {
|
|
72
|
+
await expect(web.getLocalTtsStatus()).resolves.toEqual({
|
|
73
|
+
ready: false,
|
|
74
|
+
status: "unavailable",
|
|
75
|
+
message:
|
|
76
|
+
"ElizaBunRuntime local TTS is not available on web. Run on an iOS device or simulator.",
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("returns diagnostics without reflecting hostile options", async () => {
|
|
81
|
+
await expect(
|
|
82
|
+
web.getLocalTtsDiagnostics({
|
|
83
|
+
bundleDir: "../escape",
|
|
84
|
+
probe: true,
|
|
85
|
+
text: "<script>alert(1)</script>",
|
|
86
|
+
}),
|
|
87
|
+
).resolves.toEqual({
|
|
88
|
+
available: false,
|
|
89
|
+
message:
|
|
90
|
+
"ElizaBunRuntime local TTS diagnostics are not available on web. Run on an iOS device or simulator.",
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("rejects synthesis with a stable unavailable error", async () => {
|
|
95
|
+
await expect(
|
|
96
|
+
web.synthesizeLocalTts({
|
|
97
|
+
text: "<script>alert(1)</script>",
|
|
98
|
+
bundleDir: "file:///etc/passwd",
|
|
99
|
+
maxSamples: Number.POSITIVE_INFINITY,
|
|
100
|
+
}),
|
|
101
|
+
).rejects.toThrow(
|
|
102
|
+
"ElizaBunRuntime.synthesizeLocalTts is unavailable on web.",
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("bridge contract — StartOptions shape", () => {
|
|
108
|
+
it("accepts engine=auto | bun | compat and optional fields", async () => {
|
|
109
|
+
const web = new ElizaBunRuntimeWeb();
|
|
110
|
+
// All option variants must be accepted at the type level (compile-time test)
|
|
111
|
+
// and produce an ok:false result at runtime (web fallback).
|
|
112
|
+
for (const engine of ["auto", "bun", "compat"] as const) {
|
|
113
|
+
const result = await web.start({
|
|
114
|
+
engine,
|
|
115
|
+
env: { FOO: "bar" },
|
|
116
|
+
argv: ["bun", "agent-bundle.js"],
|
|
117
|
+
});
|
|
118
|
+
expect(result.ok).toBe(false);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("start() without options resolves (all fields optional)", async () => {
|
|
123
|
+
const web = new ElizaBunRuntimeWeb();
|
|
124
|
+
const result = await web.start({});
|
|
125
|
+
expect(result).toHaveProperty("ok");
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @elizaos/capacitor-bun-runtime — iOS embedded Bun-shape JS runtime.
|
|
3
|
+
*
|
|
4
|
+
* Hosts a JavaScriptCore JSContext on a dedicated worker thread. The native
|
|
5
|
+
* plugin either starts a bundled full Bun engine framework or installs the
|
|
6
|
+
* `__ELIZA_BRIDGE__` host functions for the compatibility JSContext path.
|
|
7
|
+
* The full-engine ABI lives in packages/native/bun-runtime/BRIDGE_CONTRACT.md.
|
|
8
|
+
*
|
|
9
|
+
* The plugin exposes a tiny surface to the React UI: start the runtime,
|
|
10
|
+
* send messages, check status, and stop it. Everything else flows over the
|
|
11
|
+
* `ui_post_message` / `ui_register_handler` channel inside the native side.
|
|
12
|
+
*/
|
|
13
|
+
export interface StartOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Runtime engine selection:
|
|
16
|
+
* - "auto" (default): use a bundled full Bun engine when present. The
|
|
17
|
+
* JSContext compatibility fallback is for development/sideload builds only;
|
|
18
|
+
* iOS store local mode must request "bun" and fail if the engine is missing.
|
|
19
|
+
* - "bun": require ElizaBunEngine.framework and fail if it is missing.
|
|
20
|
+
* - "compat": force the JSContext compatibility bridge for development.
|
|
21
|
+
*/
|
|
22
|
+
engine?: "auto" | "bun" | "compat";
|
|
23
|
+
/**
|
|
24
|
+
* Path to the agent bundle JavaScript file. When omitted, the runtime
|
|
25
|
+
* loads the staged iOS agent payload from `public/agent/agent-bundle.js`
|
|
26
|
+
* in the main app bundle resources. Legacy JSContext development bundles
|
|
27
|
+
* named `agent-bundle-ios.js` are still probed for compatibility.
|
|
28
|
+
* Use this only for development overrides.
|
|
29
|
+
*/
|
|
30
|
+
bundlePath?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Optional polyfill prefix loaded before the agent bundle. When omitted,
|
|
33
|
+
* the runtime loads `eliza-polyfill-prefix.js` from the main app bundle
|
|
34
|
+
* resources, or falls back to a minimal embedded prefix.
|
|
35
|
+
*/
|
|
36
|
+
polyfillPath?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Initial environment variables visible to the agent via `env_get` / `env_keys`.
|
|
39
|
+
*/
|
|
40
|
+
env?: Record<string, string>;
|
|
41
|
+
/**
|
|
42
|
+
* argv vector exposed to the agent via `argv()`. Defaults to
|
|
43
|
+
* `["bun", "public/agent/agent-bundle.js"]`.
|
|
44
|
+
*/
|
|
45
|
+
argv?: string[];
|
|
46
|
+
}
|
|
47
|
+
export interface StartResult {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
error?: string;
|
|
50
|
+
/** Version string emitted by `__ELIZA_BRIDGE_VERSION__`. */
|
|
51
|
+
bridgeVersion?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface SendMessageOptions {
|
|
54
|
+
message: string;
|
|
55
|
+
/** Optional conversation/thread identifier passed through to the agent. */
|
|
56
|
+
conversationId?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface SendMessageResult {
|
|
59
|
+
reply: string;
|
|
60
|
+
}
|
|
61
|
+
export interface GetStatusResult {
|
|
62
|
+
ready: boolean;
|
|
63
|
+
/** Active runtime engine: full Bun framework or compatibility bridge. */
|
|
64
|
+
engine?: "bun" | "compat";
|
|
65
|
+
/** Currently loaded llama model path, if any. */
|
|
66
|
+
model?: string;
|
|
67
|
+
/** Last observed generation throughput. */
|
|
68
|
+
tokensPerSecond?: number;
|
|
69
|
+
/** Bridge version string, e.g. "v1". */
|
|
70
|
+
bridgeVersion?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Generic call surface for any UI handler the agent has registered via
|
|
74
|
+
* `bridge.ui_register_handler`. The React UI passes a method name and args;
|
|
75
|
+
* the native plugin dispatches into the JSContext and returns the result.
|
|
76
|
+
*/
|
|
77
|
+
export interface CallOptions {
|
|
78
|
+
method: string;
|
|
79
|
+
args?: unknown;
|
|
80
|
+
}
|
|
81
|
+
export interface CallResult {
|
|
82
|
+
result: unknown;
|
|
83
|
+
}
|
|
84
|
+
export interface LocalTtsStatusResult {
|
|
85
|
+
ready: boolean;
|
|
86
|
+
status: "assets-ready" | "engine-ready" | "ready" | "missing" | "unavailable";
|
|
87
|
+
message: string;
|
|
88
|
+
modelId?: string;
|
|
89
|
+
bundleDir?: string;
|
|
90
|
+
}
|
|
91
|
+
export interface LocalTtsDiagnosticsOptions {
|
|
92
|
+
bundleDir?: string;
|
|
93
|
+
probe?: boolean;
|
|
94
|
+
text?: string;
|
|
95
|
+
}
|
|
96
|
+
export interface LocalTtsDiagnosticsResult {
|
|
97
|
+
available: boolean;
|
|
98
|
+
selectedBundleDir?: string;
|
|
99
|
+
modelId?: string;
|
|
100
|
+
message?: string;
|
|
101
|
+
[key: string]: unknown;
|
|
102
|
+
}
|
|
103
|
+
export interface SynthesizeLocalTtsOptions {
|
|
104
|
+
text: string;
|
|
105
|
+
bundleDir?: string;
|
|
106
|
+
speakerPresetId?: string;
|
|
107
|
+
voice?: string;
|
|
108
|
+
voiceId?: string;
|
|
109
|
+
maxSamples?: number;
|
|
110
|
+
play?: boolean;
|
|
111
|
+
}
|
|
112
|
+
export interface SynthesizeLocalTtsResult {
|
|
113
|
+
audioBase64?: string;
|
|
114
|
+
contentType: "audio/wav";
|
|
115
|
+
sampleRate: number;
|
|
116
|
+
samples: number;
|
|
117
|
+
durationMs: number;
|
|
118
|
+
modelId?: string;
|
|
119
|
+
played?: boolean;
|
|
120
|
+
}
|
|
121
|
+
export interface ElizaBunRuntimePlugin {
|
|
122
|
+
start(options: StartOptions): Promise<StartResult>;
|
|
123
|
+
sendMessage(options: SendMessageOptions): Promise<SendMessageResult>;
|
|
124
|
+
getStatus(): Promise<GetStatusResult>;
|
|
125
|
+
stop(): Promise<void>;
|
|
126
|
+
getLocalTtsStatus(): Promise<LocalTtsStatusResult>;
|
|
127
|
+
getLocalTtsDiagnostics(options?: LocalTtsDiagnosticsOptions): Promise<LocalTtsDiagnosticsResult>;
|
|
128
|
+
synthesizeLocalTts(options: SynthesizeLocalTtsOptions): Promise<SynthesizeLocalTtsResult>;
|
|
129
|
+
/**
|
|
130
|
+
* Invoke an arbitrary UI handler that the agent has registered via
|
|
131
|
+
* `bridge.ui_register_handler`. Useful for routing arbitrary RPC-style
|
|
132
|
+
* traffic from the React UI into the agent.
|
|
133
|
+
*/
|
|
134
|
+
call(options: CallOptions): Promise<CallResult>;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=definitions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["definitions.ts"],"names":[],"mappings":"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"}
|