bleam 0.0.10 → 0.0.11
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/dist/ai.cjs +608 -59
- package/dist/ai.d.cts +217 -89
- package/dist/ai.d.ts +217 -89
- package/dist/ai.js +601 -59
- package/dist/cli.cjs +3 -183
- package/dist/cli.d.cts +2 -46
- package/dist/cli.d.ts +2 -46
- package/dist/cli.js +3 -183
- package/dist/files.d.cts +1 -1
- package/dist/files.d.ts +1 -1
- package/dist/platform.cjs +1 -13
- package/dist/platform.d.cts +1 -28
- package/dist/platform.d.ts +1 -28
- package/dist/platform.js +1 -10
- package/dist/schema.d.cts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/state.d.cts +1 -1
- package/dist/state.d.ts +1 -1
- package/dist/{ui-Bg11tvlc.d.ts → ui-1WepaMS4.d.cts} +1 -1
- package/dist/{ui-Dd7SXdbg.d.cts → ui-CHc4xEs_.d.ts} +7 -7
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/window.d.cts +1 -1
- package/dist/window.d.ts +1 -1
- package/package.json +1 -1
- package/templates/image-generation/app/index.tsx +3 -1
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +1 -1
- package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +7 -2
- package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextGenerator.swift +46 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextModel.swift +107 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextSnapshotValidator.swift +201 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/GenerationTypes.swift +85 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/ModelCapabilities.swift +21 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/TextKVCache.swift +23 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/TokenSampler.swift +79 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3CheckpointLoader.swift +161 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Configuration.swift +119 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3InferenceSession.swift +84 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Layers.swift +197 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Model.swift +130 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Checkpoint.swift +102 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Configuration.swift +140 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Model.swift +112 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Session.swift +121 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridCache.swift +42 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridFullAttention.swift +57 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLayers.swift +58 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLinearAttention.swift +111 -0
- package/templates/native/ios/Bleam/AppDelegate.swift +0 -206
- package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +128 -122
- package/templates/native/ios/GenerationService/BonsaiTextGenerationRunner.swift +103 -0
- package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +19 -7
- package/templates/native/ios/GenerationService/main.swift +174 -48
- package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +3 -0
- package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +44 -0
- package/templates/native/modules/bleam-runtime/ios/AIModule.swift +225 -5
- package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +47 -0
- package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +0 -191
- package/templates/text-generation/app/index.tsx +93 -0
- package/templates/text-generation/app.config.ts +5 -0
- package/templates/updates/README.md +43 -199
- package/templates/updates/src/index.ts +5 -98
- package/templates/updates/src/schema.ts +18 -173
- package/templates/native/ios/PlatformHelper/main.swift +0 -726
- /package/dist/{files-DwA7pzr3.d.cts → files-4ZEoAWiv.d.ts} +0 -0
- /package/dist/{files-VrkQlKIT.d.ts → files-Dt5mbzLq.d.cts} +0 -0
- /package/dist/{schema-DOOjfXvs.d.ts → schema-CYh6n8GS.d.ts} +0 -0
- /package/dist/{schema-ENSMX_1t.d.cts → schema-oeOrd3i1.d.cts} +0 -0
|
@@ -15,7 +15,7 @@ public final class AIModule: Module {
|
|
|
15
15
|
|
|
16
16
|
public func definition() -> ModuleDefinition {
|
|
17
17
|
Name("AI")
|
|
18
|
-
Events("imagegenProgress", "textGenerationUpdate", "toolCall")
|
|
18
|
+
Events("imagegenProgress", "textGenerationUpdate", "localTextGenerationUpdate", "toolCall")
|
|
19
19
|
|
|
20
20
|
Function("isAvailable") { () -> Bool in
|
|
21
21
|
#if canImport(FoundationModels)
|
|
@@ -147,13 +147,47 @@ public final class AIModule: Module {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
AsyncFunction("cancelTextGeneration") { [weak self] (id: String) -> Bool in
|
|
150
|
-
await self?.textGeneration.cancel(id) ?? false
|
|
150
|
+
let foundationCanceled = await self?.textGeneration.cancel(id) ?? false
|
|
151
|
+
let localCanceled = (try? await ImageGenerationServiceClient.shared.cancelText(id: id)) ?? false
|
|
152
|
+
return foundationCanceled || localCanceled
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
AsyncFunction("resolveToolCall") { [weak self] (request: ToolResultRequest) -> Bool in
|
|
154
156
|
await self?.textGeneration.resolve(request) ?? false
|
|
155
157
|
}
|
|
156
158
|
|
|
159
|
+
AsyncFunction("generateLocalText") { (request: LocalTextGenerationRequest, promise: Promise) in
|
|
160
|
+
Task { @MainActor in
|
|
161
|
+
do {
|
|
162
|
+
let event = try await ImageGenerationServiceClient.shared.runText(
|
|
163
|
+
request: Self.bonsaiTextRequest(from: request)
|
|
164
|
+
)
|
|
165
|
+
promise.resolve(event.tokenIDs)
|
|
166
|
+
} catch {
|
|
167
|
+
Self.rejectLocalTextGeneration(error, promise: promise)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
AsyncFunction("streamLocalText") { [weak self] (request: LocalTextGenerationRequest, promise: Promise) in
|
|
173
|
+
Task { @MainActor in
|
|
174
|
+
do {
|
|
175
|
+
let event = try await ImageGenerationServiceClient.shared.runText(
|
|
176
|
+
request: Self.bonsaiTextRequest(from: request),
|
|
177
|
+
onUpdate: { [weak self] event in
|
|
178
|
+
self?.sendEvent("localTextGenerationUpdate", [
|
|
179
|
+
"id": event.id,
|
|
180
|
+
"tokenIds": event.tokenIDs,
|
|
181
|
+
])
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
promise.resolve(event.tokenIDs)
|
|
185
|
+
} catch {
|
|
186
|
+
Self.rejectLocalTextGeneration(error, promise: promise)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
157
191
|
AsyncFunction("generateImage") { (request: ImageGenerationRequest, promise: Promise) in
|
|
158
192
|
do {
|
|
159
193
|
let imageRequest = try Self.imageRequest(from: request)
|
|
@@ -272,12 +306,23 @@ public final class AIModule: Module {
|
|
|
272
306
|
guard !request.model.isEmpty else {
|
|
273
307
|
throw ModuleError.invalidModel("Missing image model")
|
|
274
308
|
}
|
|
275
|
-
guard request.model == "flux2-klein-4b-8bit-abliterated"
|
|
309
|
+
guard request.model == "flux2-klein-4b-8bit-abliterated"
|
|
310
|
+
|| request.model == "ternary-bonsai-image-4b"
|
|
311
|
+
else {
|
|
276
312
|
throw ModuleError.invalidModel("Unsupported image model: \(request.model)")
|
|
277
313
|
}
|
|
278
314
|
guard !request.prompt.isEmpty else {
|
|
279
315
|
throw ModuleError.invalidMessage("Expected prompt")
|
|
280
316
|
}
|
|
317
|
+
if request.model == "ternary-bonsai-image-4b",
|
|
318
|
+
request.sourceImagePath != nil
|
|
319
|
+
|| request.maskImagePath != nil
|
|
320
|
+
|| request.outpaintWidth != nil
|
|
321
|
+
|| !request.referenceImagePaths.isEmpty
|
|
322
|
+
|| !request.loraPaths.isEmpty
|
|
323
|
+
{
|
|
324
|
+
throw ModuleError.invalidModel("Ternary Bonsai Image 4B currently supports text-to-image generation")
|
|
325
|
+
}
|
|
281
326
|
if let sourceImagePath = request.sourceImagePath, sourceImagePath.isEmpty {
|
|
282
327
|
throw ModuleError.invalidMessage("Source image path must not be empty")
|
|
283
328
|
}
|
|
@@ -493,6 +538,40 @@ public final class AIModule: Module {
|
|
|
493
538
|
)
|
|
494
539
|
}
|
|
495
540
|
|
|
541
|
+
private static func bonsaiTextRequest(
|
|
542
|
+
from request: LocalTextGenerationRequest
|
|
543
|
+
) throws -> BonsaiTextGenerationRequest {
|
|
544
|
+
guard !request.id.isEmpty else {
|
|
545
|
+
throw ModuleError.invalidMessage("Expected generation id")
|
|
546
|
+
}
|
|
547
|
+
guard !request.model.isEmpty else {
|
|
548
|
+
throw ModuleError.invalidModel("Expected Bonsai model id")
|
|
549
|
+
}
|
|
550
|
+
var isDirectory: ObjCBool = false
|
|
551
|
+
guard FileManager.default.fileExists(atPath: request.modelPath, isDirectory: &isDirectory),
|
|
552
|
+
isDirectory.boolValue
|
|
553
|
+
else {
|
|
554
|
+
throw ModuleError.invalidModel("Missing Bonsai model directory: \(request.modelPath)")
|
|
555
|
+
}
|
|
556
|
+
guard !request.inputIds.isEmpty else {
|
|
557
|
+
throw ModuleError.invalidMessage("Expected tokenized prompt")
|
|
558
|
+
}
|
|
559
|
+
return BonsaiTextGenerationRequest(
|
|
560
|
+
id: request.id,
|
|
561
|
+
model: request.model,
|
|
562
|
+
modelPath: request.modelPath,
|
|
563
|
+
inputIDs: request.inputIds,
|
|
564
|
+
endTokenIDs: request.endTokenIds,
|
|
565
|
+
maximumNewTokens: request.maxTokens,
|
|
566
|
+
temperature: request.temperature,
|
|
567
|
+
topP: request.topP,
|
|
568
|
+
topK: request.topK,
|
|
569
|
+
repetitionPenalty: request.repetitionPenalty,
|
|
570
|
+
repetitionContextSize: request.repetitionContextSize,
|
|
571
|
+
seed: request.seed
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
|
|
496
575
|
private static func metadataDictionary(_ metadata: GenerationWorkerMetadata?) -> [String: Any]? {
|
|
497
576
|
guard let metadata else { return nil }
|
|
498
577
|
var value: [String: Any] = [
|
|
@@ -530,6 +609,22 @@ public final class AIModule: Module {
|
|
|
530
609
|
promise.reject(error)
|
|
531
610
|
}
|
|
532
611
|
|
|
612
|
+
private static func rejectLocalTextGeneration(_ error: Error, promise: Promise) {
|
|
613
|
+
if error is CancellationError {
|
|
614
|
+
promise.reject("GENERATION_CANCELED", "Generation canceled")
|
|
615
|
+
return
|
|
616
|
+
}
|
|
617
|
+
if error as? GenerationServiceClientError == .concurrentRequest {
|
|
618
|
+
promise.reject("CONCURRENT_REQUEST", error.localizedDescription)
|
|
619
|
+
return
|
|
620
|
+
}
|
|
621
|
+
if let failure = error as? GenerationWorkerFailure {
|
|
622
|
+
promise.reject("GENERATION_FAILED", failure.localizedDescription)
|
|
623
|
+
return
|
|
624
|
+
}
|
|
625
|
+
promise.reject(error)
|
|
626
|
+
}
|
|
627
|
+
|
|
533
628
|
#if canImport(FoundationModels)
|
|
534
629
|
@available(iOS 26, *)
|
|
535
630
|
private static func session(system: String?, tools: [any Tool]) -> LanguageModelSession {
|
|
@@ -849,6 +944,7 @@ private final class ImageGenerationServiceClient {
|
|
|
849
944
|
}
|
|
850
945
|
private var connection: NSXPCConnection?
|
|
851
946
|
private var pendingRun: PendingRun?
|
|
947
|
+
private var pendingTextRun: PendingTextRun?
|
|
852
948
|
private let eventReceiver = GenerationServiceEventReceiver()
|
|
853
949
|
|
|
854
950
|
private final class PendingRun {
|
|
@@ -864,13 +960,29 @@ private final class ImageGenerationServiceClient {
|
|
|
864
960
|
}
|
|
865
961
|
}
|
|
866
962
|
|
|
963
|
+
private final class PendingTextRun {
|
|
964
|
+
let id: String
|
|
965
|
+
let continuation: CheckedContinuation<BonsaiTextGenerationEvent, Error>
|
|
966
|
+
let onUpdate: (BonsaiTextGenerationEvent) -> Void
|
|
967
|
+
|
|
968
|
+
init(
|
|
969
|
+
id: String,
|
|
970
|
+
continuation: CheckedContinuation<BonsaiTextGenerationEvent, Error>,
|
|
971
|
+
onUpdate: @escaping (BonsaiTextGenerationEvent) -> Void
|
|
972
|
+
) {
|
|
973
|
+
self.id = id
|
|
974
|
+
self.continuation = continuation
|
|
975
|
+
self.onUpdate = onUpdate
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
867
979
|
func run(
|
|
868
980
|
request: GenerationWorkerRequest,
|
|
869
981
|
onProgress: @escaping (GenerationWorkerEvent) -> Void = { _ in }
|
|
870
982
|
) async throws -> GenerationWorkerEvent {
|
|
871
983
|
let terminalEvent: GenerationWorkerEvent = try await withCheckedThrowingContinuation { continuation in
|
|
872
984
|
do {
|
|
873
|
-
guard pendingRun == nil else {
|
|
985
|
+
guard pendingRun == nil, pendingTextRun == nil else {
|
|
874
986
|
continuation.resume(throwing: GenerationServiceClientError.concurrentRequest)
|
|
875
987
|
return
|
|
876
988
|
}
|
|
@@ -905,6 +1017,71 @@ private final class ImageGenerationServiceClient {
|
|
|
905
1017
|
return terminalEvent
|
|
906
1018
|
}
|
|
907
1019
|
|
|
1020
|
+
func runText(
|
|
1021
|
+
request: BonsaiTextGenerationRequest,
|
|
1022
|
+
onUpdate: @escaping (BonsaiTextGenerationEvent) -> Void = { _ in }
|
|
1023
|
+
) async throws -> BonsaiTextGenerationEvent {
|
|
1024
|
+
let terminalEvent: BonsaiTextGenerationEvent = try await withCheckedThrowingContinuation { continuation in
|
|
1025
|
+
do {
|
|
1026
|
+
guard pendingRun == nil, pendingTextRun == nil else {
|
|
1027
|
+
continuation.resume(throwing: GenerationServiceClientError.concurrentRequest)
|
|
1028
|
+
return
|
|
1029
|
+
}
|
|
1030
|
+
eventReceiver.onTextEvent = { [weak self] event in
|
|
1031
|
+
self?.deliverText(event)
|
|
1032
|
+
}
|
|
1033
|
+
pendingTextRun = PendingTextRun(
|
|
1034
|
+
id: request.id,
|
|
1035
|
+
continuation: continuation,
|
|
1036
|
+
onUpdate: onUpdate
|
|
1037
|
+
)
|
|
1038
|
+
let requestData = try JSONEncoder().encode(request)
|
|
1039
|
+
let service = try remoteProxy(errorHandler: { [weak self] error in
|
|
1040
|
+
Task { @MainActor in
|
|
1041
|
+
self?.failPendingTextRun(Self.mapConnectionError(error))
|
|
1042
|
+
}
|
|
1043
|
+
})
|
|
1044
|
+
service.startTextGeneration(requestData: requestData) { [weak self] accepted in
|
|
1045
|
+
Task { @MainActor in
|
|
1046
|
+
guard accepted else {
|
|
1047
|
+
self?.failPendingTextRun(GenerationServiceClientError.concurrentRequest)
|
|
1048
|
+
return
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
} catch {
|
|
1053
|
+
pendingTextRun = nil
|
|
1054
|
+
eventReceiver.onTextEvent = nil
|
|
1055
|
+
continuation.resume(throwing: error)
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
switch terminalEvent.type {
|
|
1060
|
+
case .completed:
|
|
1061
|
+
return terminalEvent
|
|
1062
|
+
case .canceled:
|
|
1063
|
+
throw CancellationError()
|
|
1064
|
+
case .failed:
|
|
1065
|
+
throw GenerationWorkerFailure(message: terminalEvent.message ?? "Text generation failed.")
|
|
1066
|
+
case .update:
|
|
1067
|
+
throw GenerationServiceClientError.invalidResponse
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
func cancelText(id: String) async throws -> Bool {
|
|
1072
|
+
guard pendingTextRun?.id == id else { return false }
|
|
1073
|
+
let service = try remoteProxy(errorHandler: { [weak self] error in
|
|
1074
|
+
Task { @MainActor in
|
|
1075
|
+
self?.failPendingTextRun(Self.mapConnectionError(error))
|
|
1076
|
+
}
|
|
1077
|
+
})
|
|
1078
|
+
return try await withCheckedThrowingContinuation { continuation in
|
|
1079
|
+
service.cancelTextGeneration(id: id) { canceled in
|
|
1080
|
+
continuation.resume(returning: canceled)
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
908
1085
|
func cancel() async throws -> Bool {
|
|
909
1086
|
guard pendingRun != nil else {
|
|
910
1087
|
return false
|
|
@@ -940,7 +1117,7 @@ private final class ImageGenerationServiceClient {
|
|
|
940
1117
|
}
|
|
941
1118
|
|
|
942
1119
|
func unload() async throws -> Bool {
|
|
943
|
-
guard pendingRun == nil else { return false }
|
|
1120
|
+
guard pendingRun == nil, pendingTextRun == nil else { return false }
|
|
944
1121
|
let service = try remoteProxy(errorHandler: { _ in })
|
|
945
1122
|
let unloaded = try await withCheckedThrowingContinuation { continuation in
|
|
946
1123
|
service.shutdown { accepted in
|
|
@@ -1025,6 +1202,17 @@ private final class ImageGenerationServiceClient {
|
|
|
1025
1202
|
}
|
|
1026
1203
|
}
|
|
1027
1204
|
|
|
1205
|
+
private func deliverText(_ event: BonsaiTextGenerationEvent) {
|
|
1206
|
+
guard let pendingTextRun, event.id == pendingTextRun.id else { return }
|
|
1207
|
+
if event.type == .update {
|
|
1208
|
+
pendingTextRun.onUpdate(event)
|
|
1209
|
+
return
|
|
1210
|
+
}
|
|
1211
|
+
self.pendingTextRun = nil
|
|
1212
|
+
eventReceiver.onTextEvent = nil
|
|
1213
|
+
pendingTextRun.continuation.resume(returning: event)
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1028
1216
|
private func failPendingRun(_ error: Error) {
|
|
1029
1217
|
guard let pendingRun else { return }
|
|
1030
1218
|
self.pendingRun = nil
|
|
@@ -1035,9 +1223,17 @@ private final class ImageGenerationServiceClient {
|
|
|
1035
1223
|
pendingRun.continuation.resume(throwing: error)
|
|
1036
1224
|
}
|
|
1037
1225
|
|
|
1226
|
+
private func failPendingTextRun(_ error: Error) {
|
|
1227
|
+
guard let pendingTextRun else { return }
|
|
1228
|
+
self.pendingTextRun = nil
|
|
1229
|
+
eventReceiver.onTextEvent = nil
|
|
1230
|
+
pendingTextRun.continuation.resume(throwing: error)
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1038
1233
|
private func handleConnectionLost() {
|
|
1039
1234
|
connection = nil
|
|
1040
1235
|
failPendingRun(GenerationServiceClientError.serviceInterrupted)
|
|
1236
|
+
failPendingTextRun(GenerationServiceClientError.serviceInterrupted)
|
|
1041
1237
|
}
|
|
1042
1238
|
|
|
1043
1239
|
private func invalidate() {
|
|
@@ -1150,6 +1346,7 @@ private enum GenerationServiceClientError: LocalizedError, Equatable {
|
|
|
1150
1346
|
|
|
1151
1347
|
private final class GenerationServiceEventReceiver: NSObject, GenerationServiceClientProtocol {
|
|
1152
1348
|
@MainActor var onEvent: ((GenerationWorkerEvent) -> Void)?
|
|
1349
|
+
@MainActor var onTextEvent: ((BonsaiTextGenerationEvent) -> Void)?
|
|
1153
1350
|
|
|
1154
1351
|
func generationWorkerDidEmit(eventData: Data) {
|
|
1155
1352
|
guard let event = try? JSONDecoder().decode(GenerationWorkerEvent.self, from: eventData) else { return }
|
|
@@ -1157,6 +1354,14 @@ private final class GenerationServiceEventReceiver: NSObject, GenerationServiceC
|
|
|
1157
1354
|
onEvent?(event)
|
|
1158
1355
|
}
|
|
1159
1356
|
}
|
|
1357
|
+
|
|
1358
|
+
|
|
1359
|
+
func textGenerationWorkerDidEmit(eventData: Data) {
|
|
1360
|
+
guard let event = try? JSONDecoder().decode(BonsaiTextGenerationEvent.self, from: eventData) else { return }
|
|
1361
|
+
Task { @MainActor in
|
|
1362
|
+
onTextEvent?(event)
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1160
1365
|
}
|
|
1161
1366
|
|
|
1162
1367
|
private struct TextGenerationRequest: Record {
|
|
@@ -1169,6 +1374,21 @@ private struct TextGenerationRequest: Record {
|
|
|
1169
1374
|
@Field var tools: [ToolDefinitionRequest] = []
|
|
1170
1375
|
}
|
|
1171
1376
|
|
|
1377
|
+
private struct LocalTextGenerationRequest: Record {
|
|
1378
|
+
@Field var id: String = ""
|
|
1379
|
+
@Field var model: String = ""
|
|
1380
|
+
@Field var modelPath: String = ""
|
|
1381
|
+
@Field var inputIds: [Int] = []
|
|
1382
|
+
@Field var endTokenIds: [Int] = []
|
|
1383
|
+
@Field var maxTokens: Int?
|
|
1384
|
+
@Field var temperature: Double?
|
|
1385
|
+
@Field var topP: Double?
|
|
1386
|
+
@Field var topK: Int?
|
|
1387
|
+
@Field var repetitionPenalty: Double?
|
|
1388
|
+
@Field var repetitionContextSize: Int?
|
|
1389
|
+
@Field var seed: Int?
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1172
1392
|
private struct ToolDefinitionRequest: Record {
|
|
1173
1393
|
@Field var name: String = ""
|
|
1174
1394
|
@Field var description: String = ""
|
|
@@ -3,12 +3,15 @@ import Foundation
|
|
|
3
3
|
@objc protocol GenerationServiceProtocol {
|
|
4
4
|
func startGeneration(requestData: Data, reply: @escaping (Bool) -> Void)
|
|
5
5
|
func cancelGeneration(reply: @escaping (Bool) -> Void)
|
|
6
|
+
func startTextGeneration(requestData: Data, reply: @escaping (Bool) -> Void)
|
|
7
|
+
func cancelTextGeneration(id: String, reply: @escaping (Bool) -> Void)
|
|
6
8
|
func memoryTelemetry(reply: @escaping (Data?) -> Void)
|
|
7
9
|
func shutdown(reply: @escaping (Bool) -> Void)
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
@objc protocol GenerationServiceClientProtocol {
|
|
11
13
|
func generationWorkerDidEmit(eventData: Data)
|
|
14
|
+
func textGenerationWorkerDidEmit(eventData: Data)
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
struct GenerationWorkerRequest: Codable, Sendable {
|
|
@@ -133,3 +136,47 @@ struct GenerationWorkerLoRA: Codable, Sendable {
|
|
|
133
136
|
let path: String
|
|
134
137
|
let scale: Double
|
|
135
138
|
}
|
|
139
|
+
|
|
140
|
+
struct BonsaiTextGenerationRequest: Codable, Sendable {
|
|
141
|
+
let id: String
|
|
142
|
+
let model: String
|
|
143
|
+
let modelPath: String
|
|
144
|
+
let inputIDs: [Int]
|
|
145
|
+
let endTokenIDs: [Int]
|
|
146
|
+
let maximumNewTokens: Int?
|
|
147
|
+
let temperature: Double?
|
|
148
|
+
let topP: Double?
|
|
149
|
+
let topK: Int?
|
|
150
|
+
let repetitionPenalty: Double?
|
|
151
|
+
let repetitionContextSize: Int?
|
|
152
|
+
let seed: Int?
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
struct BonsaiTextGenerationEvent: Codable, Sendable {
|
|
156
|
+
enum EventType: String, Codable, Sendable {
|
|
157
|
+
case update
|
|
158
|
+
case completed
|
|
159
|
+
case canceled
|
|
160
|
+
case failed
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let type: EventType
|
|
164
|
+
let id: String
|
|
165
|
+
let tokenIDs: [Int]
|
|
166
|
+
let finishReason: String?
|
|
167
|
+
let message: String?
|
|
168
|
+
|
|
169
|
+
init(
|
|
170
|
+
type: EventType,
|
|
171
|
+
id: String,
|
|
172
|
+
tokenIDs: [Int] = [],
|
|
173
|
+
finishReason: String? = nil,
|
|
174
|
+
message: String? = nil
|
|
175
|
+
) {
|
|
176
|
+
self.type = type
|
|
177
|
+
self.id = id
|
|
178
|
+
self.tokenIDs = tokenIDs
|
|
179
|
+
self.finishReason = finishReason
|
|
180
|
+
self.message = message
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -6,70 +6,10 @@ import ExpoModulesCore
|
|
|
6
6
|
import Foundation
|
|
7
7
|
import Photos
|
|
8
8
|
|
|
9
|
-
@objc private protocol PlatformHelperProtocol {
|
|
10
|
-
func health(reply: @escaping (Data?) -> Void)
|
|
11
|
-
func downloadPlatformUpdate(requestData: Data, reply: @escaping (Data?) -> Void)
|
|
12
|
-
func applyPlatformUpdate(requestData: Data, reply: @escaping (Bool) -> Void)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
9
|
public final class PlatformModule: Module {
|
|
16
10
|
public func definition() -> ModuleDefinition {
|
|
17
11
|
Name("Platform")
|
|
18
12
|
|
|
19
|
-
AsyncFunction("helperStatus") { (promise: Promise) in
|
|
20
|
-
guard Self.isSupportedInstallLocation else {
|
|
21
|
-
UserDefaults.standard.set(false, forKey: "BleamPlatformUpdatesSupported")
|
|
22
|
-
promise.resolve([
|
|
23
|
-
"healthy": false,
|
|
24
|
-
"protocolVersion": 1,
|
|
25
|
-
"helperVersion": "unavailable",
|
|
26
|
-
"platformVersion": "unavailable",
|
|
27
|
-
"capabilities": [],
|
|
28
|
-
"updatesSupported": false,
|
|
29
|
-
"installPath": Bundle.main.bundleURL.path,
|
|
30
|
-
"reason": "reinstall_required",
|
|
31
|
-
])
|
|
32
|
-
return
|
|
33
|
-
}
|
|
34
|
-
let serviceName = "\(Bundle.main.bundleIdentifier ?? "dev.bleam.app").PlatformHelper"
|
|
35
|
-
guard let connection = Self.makeConnection(serviceName: serviceName) else {
|
|
36
|
-
promise.reject("HELPER_UNAVAILABLE", "The app helper is unavailable")
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
connection.remoteObjectInterface = NSXPCInterface(with: PlatformHelperProtocol.self)
|
|
40
|
-
connection.resume()
|
|
41
|
-
|
|
42
|
-
guard let service = connection.remoteObjectProxyWithErrorHandler({ error in
|
|
43
|
-
connection.invalidate()
|
|
44
|
-
promise.reject("HELPER_UNAVAILABLE", error.localizedDescription)
|
|
45
|
-
}) as? PlatformHelperProtocol else {
|
|
46
|
-
connection.invalidate()
|
|
47
|
-
promise.reject("HELPER_UNAVAILABLE", "The app helper is unavailable")
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
service.health { data in
|
|
52
|
-
defer { connection.invalidate() }
|
|
53
|
-
guard
|
|
54
|
-
let data,
|
|
55
|
-
var status = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
56
|
-
else {
|
|
57
|
-
promise.reject("HELPER_INVALID_RESPONSE", "The app helper returned an invalid response")
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
let supported = Self.isSupportedInstallLocation
|
|
61
|
-
status["updatesSupported"] = supported
|
|
62
|
-
status["installPath"] = Bundle.main.bundleURL.path
|
|
63
|
-
if !supported {
|
|
64
|
-
status["reason"] = "reinstall_required"
|
|
65
|
-
}
|
|
66
|
-
UserDefaults.standard.set(status["healthy"], forKey: "BleamHelperHealthy")
|
|
67
|
-
UserDefaults.standard.set(status["platformVersion"], forKey: "BleamHelperPlatformVersion")
|
|
68
|
-
UserDefaults.standard.set(supported, forKey: "BleamPlatformUpdatesSupported")
|
|
69
|
-
promise.resolve(status)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
13
|
AsyncFunction("permissionState") { (capability: String, promise: Promise) in
|
|
74
14
|
guard let state = Self.permissionState(capability) else {
|
|
75
15
|
promise.reject("INVALID_CAPABILITY", "Unknown permission capability: \(capability)")
|
|
@@ -85,132 +25,6 @@ public final class PlatformModule: Module {
|
|
|
85
25
|
}
|
|
86
26
|
promise.resolve(states)
|
|
87
27
|
}
|
|
88
|
-
|
|
89
|
-
AsyncFunction("downloadPlatformUpdate") { (promise: Promise) in
|
|
90
|
-
guard Self.isSupportedInstallLocation else {
|
|
91
|
-
promise.reject("REINSTALL_REQUIRED", "Platform updates require the app to be installed directly in ~/Applications")
|
|
92
|
-
return
|
|
93
|
-
}
|
|
94
|
-
guard
|
|
95
|
-
let serviceURL = Bundle.main.object(forInfoDictionaryKey: "BleamUpdateServiceURL") as? String,
|
|
96
|
-
let bundleIdentifier = Bundle.main.bundleIdentifier,
|
|
97
|
-
let teamIdentifier = Bundle.main.object(forInfoDictionaryKey: "BleamTeamIdentifier") as? String,
|
|
98
|
-
let platformVersion = Bundle.main.object(forInfoDictionaryKey: "BleamRuntimeVersion") as? String,
|
|
99
|
-
let platformFingerprint = Bundle.main.object(forInfoDictionaryKey: "BleamPlatformFingerprint") as? String,
|
|
100
|
-
let requestData = try? JSONSerialization.data(withJSONObject: [
|
|
101
|
-
"serviceURL": serviceURL,
|
|
102
|
-
"bundleIdentifier": bundleIdentifier,
|
|
103
|
-
"teamIdentifier": teamIdentifier,
|
|
104
|
-
"platformVersion": platformVersion,
|
|
105
|
-
"platformFingerprint": platformFingerprint,
|
|
106
|
-
])
|
|
107
|
-
else {
|
|
108
|
-
promise.reject("UPDATES_UNAVAILABLE", "Platform updates are not configured")
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
guard let connection = Self.makeConnection(serviceName: "\(bundleIdentifier).PlatformHelper") else {
|
|
113
|
-
promise.reject("HELPER_UNAVAILABLE", "The app helper is unavailable")
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
connection.remoteObjectInterface = NSXPCInterface(with: PlatformHelperProtocol.self)
|
|
117
|
-
connection.resume()
|
|
118
|
-
guard let service = connection.remoteObjectProxyWithErrorHandler({ error in
|
|
119
|
-
connection.invalidate()
|
|
120
|
-
promise.reject("PLATFORM_DOWNLOAD_FAILED", error.localizedDescription)
|
|
121
|
-
}) as? PlatformHelperProtocol else {
|
|
122
|
-
connection.invalidate()
|
|
123
|
-
promise.reject("HELPER_UNAVAILABLE", "The app helper is unavailable")
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
service.downloadPlatformUpdate(requestData: requestData) { data in
|
|
128
|
-
defer { connection.invalidate() }
|
|
129
|
-
guard
|
|
130
|
-
let data,
|
|
131
|
-
let result = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
132
|
-
else {
|
|
133
|
-
promise.reject("PLATFORM_DOWNLOAD_FAILED", "The platform update could not be downloaded")
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
if let error = result["error"] as? String {
|
|
137
|
-
UserDefaults.standard.set(error, forKey: "BleamPlatformDownloadError")
|
|
138
|
-
promise.reject("PLATFORM_VERIFICATION_FAILED", error)
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
UserDefaults.standard.set(result["releaseId"], forKey: "BleamPlatformDownloadRelease")
|
|
142
|
-
promise.resolve(result)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
AsyncFunction("updateAndRelaunch") { (releaseId: String, promise: Promise) in
|
|
147
|
-
guard Self.isSupportedInstallLocation else {
|
|
148
|
-
promise.reject("REINSTALL_REQUIRED", "Platform updates require the app to be installed directly in ~/Applications")
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
guard
|
|
152
|
-
let bundleIdentifier = Bundle.main.bundleIdentifier,
|
|
153
|
-
let teamIdentifier = Bundle.main.object(forInfoDictionaryKey: "BleamTeamIdentifier") as? String,
|
|
154
|
-
let requestData = try? JSONSerialization.data(withJSONObject: [
|
|
155
|
-
"releaseId": releaseId,
|
|
156
|
-
"bundleIdentifier": bundleIdentifier,
|
|
157
|
-
"teamIdentifier": teamIdentifier,
|
|
158
|
-
"installedAppPath": Bundle.main.bundleURL.path,
|
|
159
|
-
"appPID": ProcessInfo.processInfo.processIdentifier,
|
|
160
|
-
]),
|
|
161
|
-
let connection = Self.makeConnection(serviceName: "\(bundleIdentifier).PlatformHelper")
|
|
162
|
-
else {
|
|
163
|
-
promise.reject("PLATFORM_UPDATE_UNAVAILABLE", "The platform update cannot be applied")
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
connection.remoteObjectInterface = NSXPCInterface(with: PlatformHelperProtocol.self)
|
|
167
|
-
connection.resume()
|
|
168
|
-
guard let service = connection.remoteObjectProxyWithErrorHandler({ error in
|
|
169
|
-
connection.invalidate()
|
|
170
|
-
promise.reject("PLATFORM_UPDATE_FAILED", error.localizedDescription)
|
|
171
|
-
}) as? PlatformHelperProtocol else {
|
|
172
|
-
connection.invalidate()
|
|
173
|
-
promise.reject("HELPER_UNAVAILABLE", "The app helper is unavailable")
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
service.applyPlatformUpdate(requestData: requestData) { accepted in
|
|
177
|
-
connection.invalidate()
|
|
178
|
-
UserDefaults.standard.set(accepted, forKey: "BleamPlatformApplyAccepted")
|
|
179
|
-
guard accepted else {
|
|
180
|
-
promise.reject("PLATFORM_UPDATE_REJECTED", "The platform update is not verified or the app is not installed in ~/Applications")
|
|
181
|
-
return
|
|
182
|
-
}
|
|
183
|
-
promise.resolve(nil)
|
|
184
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
|
185
|
-
exit(0)
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
private static func makeConnection(serviceName: String) -> NSXPCConnection? {
|
|
192
|
-
#if targetEnvironment(macCatalyst)
|
|
193
|
-
let connectionClass: AnyObject = NSXPCConnection.self as AnyObject
|
|
194
|
-
let allocated = connectionClass.perform(NSSelectorFromString("alloc"))?.takeUnretainedValue() as AnyObject?
|
|
195
|
-
let selector = NSSelectorFromString("initWithMachServiceName:options:")
|
|
196
|
-
guard let allocated, allocated.responds(to: selector) else { return nil }
|
|
197
|
-
|
|
198
|
-
typealias Initializer = @convention(c) (
|
|
199
|
-
AnyObject,
|
|
200
|
-
Selector,
|
|
201
|
-
NSString,
|
|
202
|
-
UInt
|
|
203
|
-
) -> Unmanaged<AnyObject>
|
|
204
|
-
let initialize = unsafeBitCast(allocated.method(for: selector), to: Initializer.self)
|
|
205
|
-
return initialize(
|
|
206
|
-
allocated,
|
|
207
|
-
selector,
|
|
208
|
-
serviceName as NSString,
|
|
209
|
-
0
|
|
210
|
-
).takeRetainedValue() as? NSXPCConnection
|
|
211
|
-
#else
|
|
212
|
-
return NSXPCConnection(machServiceName: serviceName)
|
|
213
|
-
#endif
|
|
214
28
|
}
|
|
215
29
|
|
|
216
30
|
private static let permissionCapabilities = [
|
|
@@ -321,9 +135,4 @@ public final class PlatformModule: Module {
|
|
|
321
135
|
}
|
|
322
136
|
}
|
|
323
137
|
|
|
324
|
-
private static var isSupportedInstallLocation: Bool {
|
|
325
|
-
let applications = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
|
|
326
|
-
.appendingPathComponent("Applications", isDirectory: true).standardizedFileURL
|
|
327
|
-
return Bundle.main.bundleURL.standardizedFileURL.deletingLastPathComponent() == applications
|
|
328
|
-
}
|
|
329
138
|
}
|