diff --git a/.gitignore b/.gitignore index 63bf7a737..9d6755bfd 100644 --- a/.gitignore +++ b/.gitignore @@ -340,6 +340,9 @@ sdk/runanywhere-swift/Sources/RunAnywhere/Data/Network/DevelopmentConfig.swift # Swift SDK - Local XCFrameworks sdk/runanywhere-swift/Binaries/*.xcframework +# Swift SDK - Vendor directory (locally built xcframeworks) +sdk/runanywhere-swift/vendor/ + # Kotlin SDK - Local JNI libs sdk/runanywhere-kotlin/src/androidMain/jniLibs/ diff --git a/Package.resolved b/Package.resolved index b865f22e4..4130d1086 100644 --- a/Package.resolved +++ b/Package.resolved @@ -36,6 +36,15 @@ "version" : "4.3.0" } }, + { + "identity" : "ml-stable-diffusion", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/ml-stable-diffusion.git", + "state" : { + "revision" : "5a170d29cf38e674b80541d7ce22929c6a11cdde", + "version" : "1.1.1" + } + }, { "identity" : "sentry-cocoa", "kind" : "remoteSourceControl", @@ -54,13 +63,22 @@ "version" : "4.8.6" } }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "c5d11a805e765f52ba34ec7284bd4fcd6ba68615", + "version" : "1.7.0" + } + }, { "identity" : "swift-asn1", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-asn1.git", "state" : { - "revision" : "810496cf121e525d660cd0ea89a758740476b85f", - "version" : "1.5.1" + "revision" : "40d25bbb2fc5b557a9aa8512210bded327c0f60d", + "version" : "1.5.0" } }, { diff --git a/Package.swift b/Package.swift index e212a0e09..413532287 100644 --- a/Package.swift +++ b/Package.swift @@ -87,6 +87,8 @@ let package = Package( .package(url: "https://github.com/devicekit/DeviceKit.git", from: "5.6.0"), .package(url: "https://github.com/tsolomko/SWCompression.git", from: "4.8.0"), .package(url: "https://github.com/getsentry/sentry-cocoa", from: "8.40.0"), + // ml-stable-diffusion for CoreML-based image generation + .package(url: "https://github.com/apple/ml-stable-diffusion.git", from: "1.1.0"), ], targets: [ // ================================================================= @@ -132,6 +134,7 @@ let package = Package( .product(name: "DeviceKit", package: "DeviceKit"), .product(name: "SWCompression", package: "SWCompression"), .product(name: "Sentry", package: "sentry-cocoa"), + .product(name: "StableDiffusion", package: "ml-stable-diffusion"), "CRACommons", ], path: "sdk/runanywhere-swift/Sources/RunAnywhere", diff --git a/examples/android/RunAnywhereAI/.gitignore b/examples/android/RunAnywhereAI/.gitignore index 4ee96e605..11400209d 100644 --- a/examples/android/RunAnywhereAI/.gitignore +++ b/examples/android/RunAnywhereAI/.gitignore @@ -115,3 +115,15 @@ hs_err_pid* # Detekt detekt.sarif + +# Native libraries - these come from SDK module dependencies, NOT local copies. +# NEVER put .so files directly in app/src/main/jniLibs/. They are provided by: +# - :sdk:runanywhere-kotlin (core + commons) +# - :sdk:runanywhere-kotlin:modules:runanywhere-core-onnx (ONNX/Sherpa-ONNX) +# - :sdk:runanywhere-kotlin:modules:runanywhere-core-llamacpp (LlamaCPP) +# Having stale .so here will OVERRIDE the SDK module versions and cause crashes. +app/src/main/jniLibs/ + +# Crash/debug log captures +stt_crash_logcat.txt +app_logcat.txt diff --git a/examples/android/RunAnywhereAI/app/build.gradle.kts b/examples/android/RunAnywhereAI/app/build.gradle.kts index 06c26a526..fa67c423f 100644 --- a/examples/android/RunAnywhereAI/app/build.gradle.kts +++ b/examples/android/RunAnywhereAI/app/build.gradle.kts @@ -152,16 +152,11 @@ android { // and 16KB alignment during packaging. useLegacyPackaging = true - // Handle duplicate native libraries from multiple backend modules - // (ONNX and LlamaCPP both include some common libraries) - pickFirsts += listOf( - "lib/arm64-v8a/libomp.so", - "lib/arm64-v8a/libc++_shared.so", - "lib/arm64-v8a/librac_commons.so", - "lib/armeabi-v7a/libomp.so", - "lib/armeabi-v7a/libc++_shared.so", - "lib/armeabi-v7a/librac_commons.so", - ) + // Handle duplicate native libraries from multiple SDK modules. + // The main SDK, ONNX module, and LlamaCPP module may share common + // libraries (libc++_shared, libomp, librac_commons). Use a wildcard + // to safely pick the first copy for any ABI. + pickFirsts += listOf("lib/**/*.so") } } diff --git a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/RunAnywhereApplication.kt b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/RunAnywhereApplication.kt index cd26439e2..30fc5e4d2 100644 --- a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/RunAnywhereApplication.kt +++ b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/RunAnywhereApplication.kt @@ -313,6 +313,21 @@ class RunAnywhereApplication : Application() { framework = InferenceFramework.LLAMA_CPP, memoryRequirement = 400_000_000, ) + // LFM2-Tool models - For tool calling / function calling support + RunAnywhere.registerModel( + id = "lfm2-1.2b-tool-q4_k_m", + name = "LiquidAI LFM2 1.2B Tool Q4_K_M", + url = "https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q4_K_M.gguf", + framework = InferenceFramework.LLAMA_CPP, + memoryRequirement = 800_000_000, + ) + RunAnywhere.registerModel( + id = "lfm2-1.2b-tool-q8_0", + name = "LiquidAI LFM2 1.2B Tool Q8_0", + url = "https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q8_0.gguf", + framework = InferenceFramework.LLAMA_CPP, + memoryRequirement = 1_400_000_000, + ) Log.i("RunAnywhereApp", "โœ… LLM models registered") // Register ONNX STT and TTS models diff --git a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/domain/models/ChatMessage.kt b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/domain/models/ChatMessage.kt index 5efebbc73..d670af0a2 100644 --- a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/domain/models/ChatMessage.kt +++ b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/domain/models/ChatMessage.kt @@ -117,6 +117,19 @@ data class PerformanceSummary( val successRate: Double = 1.0, ) +/** + * App-local tool call info. + * Matches iOS ToolCallInfo exactly. + */ +@Serializable +data class ToolCallInfo( + val toolName: String, + val arguments: String, // JSON string for display + val result: String? = null, // JSON string for display + val success: Boolean, + val error: String? = null, +) + /** * App-specific ChatMessage for conversations. * Self-contained with app-local types. @@ -130,6 +143,7 @@ data class ChatMessage( val timestamp: Long = System.currentTimeMillis(), val analytics: MessageAnalytics? = null, val modelInfo: MessageModelInfo? = null, + val toolCallInfo: ToolCallInfo? = null, val metadata: Map? = null, ) { val isFromUser: Boolean get() = role == MessageRole.USER @@ -158,6 +172,7 @@ data class ChatMessage( thinkingContent: String? = null, analytics: MessageAnalytics? = null, modelInfo: MessageModelInfo? = null, + toolCallInfo: ToolCallInfo? = null, metadata: Map? = null, ): ChatMessage = ChatMessage( @@ -166,6 +181,7 @@ data class ChatMessage( thinkingContent = thinkingContent, analytics = analytics, modelInfo = modelInfo, + toolCallInfo = toolCallInfo, metadata = metadata, ) diff --git a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatScreen.kt b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatScreen.kt index 9c93da250..3d17616c3 100644 --- a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatScreen.kt +++ b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatScreen.kt @@ -49,11 +49,13 @@ import com.runanywhere.runanywhereai.data.ConversationStore import com.runanywhere.runanywhereai.domain.models.ChatMessage import com.runanywhere.runanywhereai.domain.models.Conversation import com.runanywhere.runanywhereai.domain.models.MessageRole +import com.runanywhere.runanywhereai.presentation.settings.ToolSettingsViewModel import com.runanywhere.runanywhereai.presentation.chat.components.MarkdownText import com.runanywhere.runanywhereai.presentation.chat.components.ModelLoadedToast import com.runanywhere.runanywhereai.presentation.chat.components.ModelRequiredOverlay import com.runanywhere.runanywhereai.util.getModelLogoResIdForName import com.runanywhere.runanywhereai.ui.theme.AppColors +import android.app.Application import com.runanywhere.runanywhereai.ui.theme.AppTypography import com.runanywhere.runanywhereai.ui.theme.Dimensions import kotlinx.coroutines.launch @@ -221,6 +223,20 @@ fun ChatScreen(viewModel: ChatViewModel = viewModel()) { } } + // Tool calling indicator - matching iOS + val toolContext = LocalContext.current + val application = toolContext.applicationContext as Application + val toolSettingsViewModel = remember { ToolSettingsViewModel.getInstance(application) } + val toolState by toolSettingsViewModel.uiState.collectAsStateWithLifecycle() + + AnimatedVisibility( + visible = toolState.toolCallingEnabled && toolState.registeredTools.isNotEmpty(), + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + ) { + ToolCallingBadge(toolCount = toolState.registeredTools.size) + } + // ModelRequiredOverlay when no model - animated circles + Get Started if (!uiState.isModelLoaded && !uiState.isGenerating) { ModelRequiredOverlay( @@ -554,6 +570,8 @@ fun MessageBubbleView( isGenerating: Boolean = false, modifier: Modifier = Modifier, ) { + var showToolCallSheet by remember { mutableStateOf(false) } + val alignment = if (message.role == MessageRole.USER) { Arrangement.End @@ -595,6 +613,15 @@ fun MessageBubbleView( Spacer(modifier = Modifier.height(Dimensions.small)) } + // Tool call indicator (for assistant messages with tool calls) - matching iOS + if (message.role == MessageRole.ASSISTANT && message.toolCallInfo != null) { + com.runanywhere.runanywhereai.presentation.chat.components.ToolCallIndicator( + toolCallInfo = message.toolCallInfo, + onTap = { showToolCallSheet = true }, + ) + Spacer(modifier = Modifier.height(Dimensions.small)) + } + // Thinking toggle (if thinking content exists) message.thinkingContent?.let { thinking -> ThinkingToggle( @@ -754,6 +781,14 @@ fun MessageBubbleView( Spacer(modifier = Modifier.width(Dimensions.messageBubbleMinSpacing)) } } + + // Tool call detail sheet - matching iOS + if (showToolCallSheet && message.toolCallInfo != null) { + com.runanywhere.runanywhereai.presentation.chat.components.ToolCallDetailSheet( + toolCallInfo = message.toolCallInfo, + onDismiss = { showToolCallSheet = false }, + ) + } } // Helper function to format timestamp @@ -1241,6 +1276,42 @@ fun EmptyStateView() { // MODEL SELECTION PROMPT // ==================== +/** + * Tool calling indicator badge - matching iOS ChatInterfaceView toolCallingBadge + */ +@Composable +fun ToolCallingBadge(toolCount: Int) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = Dimensions.mediumLarge, vertical = Dimensions.small), + horizontalArrangement = Arrangement.Center, + ) { + Row( + modifier = Modifier + .background( + color = AppColors.primaryAccent.copy(alpha = 0.1f), + shape = RoundedCornerShape(6.dp) + ) + .padding(horizontal = 10.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Icon( + imageVector = Icons.Default.Build, + contentDescription = "Tools enabled", + modifier = Modifier.size(10.dp), + tint = AppColors.primaryAccent, + ) + Text( + text = "Tools enabled ($toolCount)", + style = AppTypography.caption2, + color = AppColors.primaryAccent, + ) + } + } +} + @Composable fun ModelSelectionPrompt(onSelectModel: () -> Unit) { Surface( diff --git a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatViewModel.kt b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatViewModel.kt index 83ff83a0b..d8743e1ae 100644 --- a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatViewModel.kt +++ b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatViewModel.kt @@ -12,6 +12,8 @@ import com.runanywhere.runanywhereai.domain.models.Conversation import com.runanywhere.runanywhereai.domain.models.MessageAnalytics import com.runanywhere.runanywhereai.domain.models.MessageModelInfo import com.runanywhere.runanywhereai.domain.models.MessageRole +import com.runanywhere.runanywhereai.domain.models.ToolCallInfo +import com.runanywhere.sdk.public.extensions.LLM.ToolValue import com.runanywhere.sdk.public.RunAnywhere import com.runanywhere.sdk.public.events.EventBus import com.runanywhere.sdk.public.events.LLMEvent @@ -23,8 +25,19 @@ import com.runanywhere.sdk.public.extensions.generate import com.runanywhere.sdk.public.extensions.generateStream import com.runanywhere.sdk.public.extensions.isLLMModelLoaded import com.runanywhere.sdk.public.extensions.loadLLMModel +import com.runanywhere.sdk.public.extensions.LLM.ToolCallingOptions +import com.runanywhere.sdk.public.extensions.LLM.ToolCallFormat +import com.runanywhere.sdk.public.extensions.LLM.RunAnywhereToolCalling +import com.runanywhere.runanywhereai.presentation.settings.ToolSettingsViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterIsInstance @@ -202,7 +215,15 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { // Clear metrics from previous generation tokensPerSecondHistory.clear() - if (currentState.useStreaming) { + // Check if tool calling is enabled and tools are registered + val toolViewModel = ToolSettingsViewModel.getInstance(app) + val useToolCalling = toolViewModel.toolCallingEnabled + val registeredTools = RunAnywhereToolCalling.getRegisteredTools() + + if (useToolCalling && registeredTools.isNotEmpty()) { + Log.i(TAG, "๐Ÿ”ง Using tool calling with ${registeredTools.size} tools") + generateWithToolCalling(prompt, assistantMessage.id) + } else if (currentState.useStreaming) { generateWithStreaming(prompt, assistantMessage.id) } else { generateWithoutStreaming(prompt, assistantMessage.id) @@ -213,6 +234,90 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { } } + /** + * Generate with tool calling support + * Matches iOS generateWithToolCalling pattern + */ + private suspend fun generateWithToolCalling( + prompt: String, + messageId: String, + ) { + val startTime = System.currentTimeMillis() + + try { + // Detect the appropriate tool call format based on loaded model + // Note: loadedModelName can be null if model state changes during generation + val modelName = _uiState.value.loadedModelName + if (modelName == null) { + Log.w(TAG, "โš ๏ธ Tool calling initiated but model name is null, using default format") + } + val toolViewModel = ToolSettingsViewModel.getInstance(app) + val format = toolViewModel.detectToolCallFormat(modelName) + + Log.i(TAG, "๐Ÿ”ง Tool calling with format: $format for model: ${modelName ?: "unknown"}") + + // Create tool calling options + val toolOptions = ToolCallingOptions( + maxToolCalls = 3, + autoExecute = true, + temperature = 0.7f, + maxTokens = 1024, + format = format + ) + + // Generate with tools + val result = RunAnywhereToolCalling.generateWithTools(prompt, toolOptions) + val endTime = System.currentTimeMillis() + + // Update the assistant message with the result + val response = result.text + updateAssistantMessage(messageId, response, null) + + // Log tool calls and create tool call info + if (result.toolCalls.isNotEmpty()) { + Log.i(TAG, "๐Ÿ”ง Tool calls made: ${result.toolCalls.map { it.toolName }}") + result.toolResults.forEach { toolResult -> + Log.i(TAG, "๐Ÿ“‹ Tool result: ${toolResult.toolName} - success: ${toolResult.success}") + } + + // Create ToolCallInfo from the first tool call and result + val firstToolCall = result.toolCalls.first() + val firstToolResult = result.toolResults.firstOrNull { it.toolName == firstToolCall.toolName } + + val toolCallInfo = ToolCallInfo( + toolName = firstToolCall.toolName, + arguments = formatToolValueMapToJson(firstToolCall.arguments), + result = firstToolResult?.result?.let { formatToolValueMapToJson(it) }, + success = firstToolResult?.success ?: false, + error = firstToolResult?.error, + ) + + updateAssistantMessageWithToolCallInfo(messageId, toolCallInfo) + } + + // Create analytics + val analytics = createMessageAnalytics( + startTime = startTime, + endTime = endTime, + firstTokenTime = null, + thinkingStartTime = null, + thinkingEndTime = null, + inputText = prompt, + outputText = response, + thinkingText = null, + wasInterrupted = false, + ) + + updateAssistantMessageWithAnalytics(messageId, analytics) + + } catch (e: Exception) { + Log.e(TAG, "Tool calling failed", e) + throw e + } finally { + _uiState.value = _uiState.value.copy(isGenerating = false) + } + } + /** * Generate with streaming support and thinking mode * Matches iOS streaming generation pattern @@ -456,6 +561,23 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { _uiState.value = _uiState.value.copy(messages = updatedMessages) } + private fun updateAssistantMessageWithToolCallInfo( + messageId: String, + toolCallInfo: ToolCallInfo, + ) { + val currentMessages = _uiState.value.messages + val updatedMessages = + currentMessages.map { message -> + if (message.id == messageId) { + message.copy(toolCallInfo = toolCallInfo) + } else { + message + } + } + + _uiState.value = _uiState.value.copy(messages = updatedMessages) + } + /** * Create message analytics using app-local types */ @@ -724,6 +846,38 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { _uiState.value = _uiState.value.copy(error = null) } + /** + * Format a ToolValue map to JSON string for display. + * Uses kotlinx.serialization for proper JSON escaping of special characters. + */ + private fun formatToolValueMapToJson(map: Map): String { + val jsonObject = buildJsonObject { + map.forEach { (key, value) -> + put(key, formatToolValueToJsonElement(value)) + } + } + return Json.encodeToString(JsonObject.serializer(), jsonObject) + } + + /** + * Convert a ToolValue to the appropriate JsonElement type. + * Handles all ToolValue variants with proper JSON escaping. + */ + private fun formatToolValueToJsonElement(value: ToolValue): JsonElement { + return when (value) { + is ToolValue.StringValue -> JsonPrimitive(value.value) + is ToolValue.NumberValue -> JsonPrimitive(value.value) + is ToolValue.BoolValue -> JsonPrimitive(value.value) + is ToolValue.NullValue -> JsonNull + is ToolValue.ArrayValue -> buildJsonArray { + value.value.forEach { add(formatToolValueToJsonElement(it)) } + } + is ToolValue.ObjectValue -> buildJsonObject { + value.value.forEach { (k, v) -> put(k, formatToolValueToJsonElement(v)) } + } + } + } + companion object { private const val TAG = "ChatViewModel" } diff --git a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/components/ToolCallViews.kt b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/components/ToolCallViews.kt new file mode 100644 index 000000000..e97fb20b0 --- /dev/null +++ b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/components/ToolCallViews.kt @@ -0,0 +1,265 @@ +package com.runanywhere.runanywhereai.presentation.chat.components + +import androidx.compose.animation.core.* +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp +import com.runanywhere.runanywhereai.domain.models.ToolCallInfo +import com.runanywhere.runanywhereai.ui.theme.AppColors +import com.runanywhere.runanywhereai.ui.theme.AppTypography + +/** + * Tool call indicator button - matches iOS ToolCallIndicator + * Shows a small chip with the tool name that can be tapped to see details + */ +@Composable +fun ToolCallIndicator( + toolCallInfo: ToolCallInfo, + onTap: () -> Unit, + modifier: Modifier = Modifier, +) { + val backgroundColor = if (toolCallInfo.success) { + AppColors.primaryAccent.copy(alpha = 0.1f) + } else { + AppColors.primaryOrange.copy(alpha = 0.1f) + } + + val borderColor = if (toolCallInfo.success) { + AppColors.primaryAccent.copy(alpha = 0.3f) + } else { + AppColors.primaryOrange.copy(alpha = 0.3f) + } + + val iconTint = if (toolCallInfo.success) { + AppColors.primaryAccent + } else { + AppColors.primaryOrange + } + + Surface( + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + .clickable { onTap() }, + color = backgroundColor, + shape = RoundedCornerShape(8.dp), + ) { + Row( + modifier = Modifier + .border(0.5.dp, borderColor, RoundedCornerShape(8.dp)) + .padding(horizontal = 10.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Icon( + imageVector = if (toolCallInfo.success) Icons.Default.Build else Icons.Default.Warning, + contentDescription = null, + modifier = Modifier.size(12.dp), + tint = iconTint, + ) + Text( + text = toolCallInfo.toolName, + style = AppTypography.caption2, + color = AppColors.textSecondary, + maxLines = 1, + ) + } + } +} + +/** + * Tool call detail sheet - matches iOS ToolCallDetailSheet + * Shows full details of a tool call including arguments and results as JSON + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ToolCallDetailSheet( + toolCallInfo: ToolCallInfo, + onDismiss: () -> Unit, +) { + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + ModalBottomSheet( + onDismissRequest = onDismiss, + sheetState = sheetState, + containerColor = MaterialTheme.colorScheme.surface, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 32.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + // Header + Text( + text = "Tool Call", + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onSurface, + ) + + // Status section + StatusSection(success = toolCallInfo.success) + + // Tool name section + DetailSection(title = "Tool", content = toolCallInfo.toolName) + + // Arguments section + CodeSection(title = "Arguments", code = toolCallInfo.arguments) + + // Result section (if available) + toolCallInfo.result?.let { result -> + CodeSection(title = "Result", code = result) + } + + // Error section (if available) + toolCallInfo.error?.let { error -> + DetailSection(title = "Error", content = error, isError = true) + } + + Spacer(modifier = Modifier.height(16.dp)) + } + } +} + +@Composable +private fun StatusSection(success: Boolean) { + val backgroundColor = if (success) { + AppColors.statusGreen.copy(alpha = 0.1f) + } else { + AppColors.primaryRed.copy(alpha = 0.1f) + } + + val iconTint = if (success) AppColors.statusGreen else AppColors.primaryRed + + Row( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(12.dp)) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + Icon( + imageVector = if (success) Icons.Default.CheckCircle else Icons.Default.Cancel, + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = iconTint, + ) + Text( + text = if (success) "Success" else "Failed", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + } +} + +@Composable +private fun DetailSection( + title: String, + content: String, + isError: Boolean = false, +) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = title, + style = AppTypography.caption, + color = AppColors.textSecondary, + ) + Text( + text = content, + style = MaterialTheme.typography.bodyMedium, + color = if (isError) AppColors.primaryRed else MaterialTheme.colorScheme.onSurface, + ) + } +} + +@Composable +private fun CodeSection( + title: String, + code: String, +) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = title, + style = AppTypography.caption, + color = AppColors.textSecondary, + ) + Box( + modifier = Modifier + .fillMaxWidth() + .background( + MaterialTheme.colorScheme.surfaceVariant, + RoundedCornerShape(8.dp) + ) + .padding(12.dp) + .horizontalScroll(rememberScrollState()) + ) { + Text( + text = code, + style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace), + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } +} + +/** + * Tool calling active indicator - matches iOS ToolCallingActiveIndicator + * Shows animated indicator when tool is being called + */ +@Composable +fun ToolCallingActiveIndicator( + modifier: Modifier = Modifier, +) { + val infiniteTransition = rememberInfiniteTransition(label = "rotation") + val rotation by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 360f, + animationSpec = infiniteRepeatable( + animation = tween(2000, easing = LinearEasing), + repeatMode = RepeatMode.Restart + ), + label = "rotation" + ) + + Row( + modifier = modifier + .background( + AppColors.primaryAccent.copy(alpha = 0.1f), + RoundedCornerShape(8.dp) + ) + .padding(horizontal = 10.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Icon( + imageVector = Icons.Default.Settings, + contentDescription = null, + modifier = Modifier + .size(12.dp) + .graphicsLayer { rotationZ = rotation }, + tint = AppColors.primaryAccent, + ) + Text( + text = "Calling tool...", + style = AppTypography.caption2, + color = AppColors.textSecondary, + ) + } +} diff --git a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/navigation/AppNavigation.kt b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/navigation/AppNavigation.kt index 81e6cc737..00a3fa66c 100644 --- a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/navigation/AppNavigation.kt +++ b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/navigation/AppNavigation.kt @@ -25,7 +25,7 @@ import com.runanywhere.runanywhereai.presentation.voice.VoiceAssistantScreen import com.runanywhere.runanywhereai.ui.theme.AppColors /** - * Main navigation component + * Main navigation component matching iOS app structure * 5 tabs: Chat, STT, TTS, Voice, Settings */ @OptIn(ExperimentalMaterial3Api::class) diff --git a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/SettingsScreen.kt b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/SettingsScreen.kt index 361356dda..a097ba54c 100644 --- a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/SettingsScreen.kt +++ b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/SettingsScreen.kt @@ -31,6 +31,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.runanywhere.runanywhereai.ui.theme.AppColors import com.runanywhere.runanywhereai.ui.theme.AppTypography import com.runanywhere.runanywhereai.ui.theme.Dimensions +import android.app.Application /** * Settings screen @@ -181,6 +182,9 @@ fun SettingsScreen(viewModel: SettingsViewModel = viewModel()) { ) } + // Tool Calling Section + ToolSettingsSection() + // 3. Storage Overview - iOS Label(systemImage: "externaldrive") etc. SettingsSection( title = "Storage Overview", @@ -701,3 +705,158 @@ private fun ApiConfigurationDialog( }, ) } + +// ============================================================================= +// Tool Settings Section +// ============================================================================= + +/** + * Tool Calling Settings Section + * + * Allows users to: + * - Enable/disable tool calling + * - Register demo tools (weather, time, calculator) + * - Clear all registered tools + * - View registered tools count + */ +@Composable +fun ToolSettingsSection() { + val context = LocalContext.current + val application = context.applicationContext as Application + val toolViewModel = remember { ToolSettingsViewModel.getInstance(application) } + val toolState by toolViewModel.uiState.collectAsStateWithLifecycle() + + SettingsSection(title = "Tool Calling") { + // Enable/Disable Toggle + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Enable Tool Calling", + style = MaterialTheme.typography.bodyLarge, + ) + Text( + text = "Allow LLMs to use registered tools", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Switch( + checked = toolState.toolCallingEnabled, + onCheckedChange = { toolViewModel.setToolCallingEnabled(it) }, + colors = SwitchDefaults.colors( + checkedThumbColor = AppColors.primaryAccent, + checkedTrackColor = AppColors.primaryAccent.copy(alpha = 0.5f), + ), + ) + } + + if (toolState.toolCallingEnabled) { + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + + // Registered Tools Count + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Outlined.Build, + contentDescription = null, + tint = AppColors.primaryAccent, + modifier = Modifier.size(20.dp), + ) + Text( + text = "Registered Tools", + style = MaterialTheme.typography.bodyMedium, + ) + } + Text( + text = "${toolState.registeredTools.size}", + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + color = AppColors.primaryAccent, + ) + } + + // Tool List (if any) + if (toolState.registeredTools.isNotEmpty()) { + toolState.registeredTools.forEach { tool -> + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 28.dp, top = 4.dp, bottom = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "โ€ข ${tool.name}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + + // Action Buttons + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + OutlinedButton( + onClick = { toolViewModel.registerDemoTools() }, + enabled = !toolState.isLoading, + colors = ButtonDefaults.outlinedButtonColors( + contentColor = AppColors.primaryGreen, + ), + modifier = Modifier.weight(1f), + ) { + Icon( + imageVector = Icons.Outlined.Add, + contentDescription = null, + modifier = Modifier.size(18.dp), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text(if (toolState.isLoading) "Loading..." else "Add Demo Tools") + } + + if (toolState.registeredTools.isNotEmpty()) { + OutlinedButton( + onClick = { toolViewModel.clearAllTools() }, + enabled = !toolState.isLoading, + colors = ButtonDefaults.outlinedButtonColors( + contentColor = AppColors.primaryRed, + ), + ) { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = null, + modifier = Modifier.size(18.dp), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text("Clear") + } + } + } + + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Demo tools: get_weather (Open-Meteo API), get_current_time, calculate", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } +} diff --git a/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/ToolSettingsViewModel.kt b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/ToolSettingsViewModel.kt new file mode 100644 index 000000000..ff97fafec --- /dev/null +++ b/examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/ToolSettingsViewModel.kt @@ -0,0 +1,439 @@ +package com.runanywhere.runanywhereai.presentation.settings + +import android.app.Application +import android.content.Context +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.runanywhere.sdk.public.extensions.LLM.ToolDefinition +import com.runanywhere.sdk.public.extensions.LLM.ToolParameter +import com.runanywhere.sdk.public.extensions.LLM.ToolParameterType +import com.runanywhere.sdk.public.extensions.LLM.ToolValue +import com.runanywhere.sdk.public.extensions.LLM.ToolCallFormat +import com.runanywhere.sdk.public.extensions.LLM.RunAnywhereToolCalling +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout +import java.net.HttpURLConnection +import java.net.URL +import java.net.URLEncoder +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.TimeZone + +/** + * Tool Settings UI State + */ +data class ToolSettingsUiState( + val toolCallingEnabled: Boolean = false, + val registeredTools: List = emptyList(), + val isLoading: Boolean = false, +) + +/** + * Tool Settings ViewModel + * + * Manages tool calling configuration and demo tool registration. + * Mirrors iOS ToolSettingsViewModel.swift functionality. + */ +class ToolSettingsViewModel private constructor(application: Application) : AndroidViewModel(application) { + + private val _uiState = MutableStateFlow(ToolSettingsUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + private val prefs = application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + val toolCallingEnabled: Boolean + get() = _uiState.value.toolCallingEnabled + + companion object { + private const val TAG = "ToolSettingsVM" + private const val PREFS_NAME = "tool_settings" + private const val KEY_TOOL_CALLING_ENABLED = "tool_calling_enabled" + + /** Timeout for weather API requests (covers geocoding + weather fetch) */ + private const val WEATHER_API_TIMEOUT_MS = 15_000L + + @Volatile + private var instance: ToolSettingsViewModel? = null + + fun getInstance(application: Application): ToolSettingsViewModel { + return instance ?: synchronized(this) { + instance ?: ToolSettingsViewModel(application).also { instance = it } + } + } + } + + init { + // Load saved preference + val enabled = prefs.getBoolean(KEY_TOOL_CALLING_ENABLED, false) + _uiState.update { it.copy(toolCallingEnabled = enabled) } + + // Refresh registered tools + viewModelScope.launch { + refreshRegisteredTools() + } + } + + fun setToolCallingEnabled(enabled: Boolean) { + prefs.edit().putBoolean(KEY_TOOL_CALLING_ENABLED, enabled).apply() + _uiState.update { it.copy(toolCallingEnabled = enabled) } + } + + suspend fun refreshRegisteredTools() { + val tools = RunAnywhereToolCalling.getRegisteredTools() + _uiState.update { it.copy(registeredTools = tools) } + } + + /** + * Register demo tools matching iOS implementation: + * - get_weather: Uses Open-Meteo API (free, no API key) + * - get_current_time: Returns system time with timezone + * - calculate: Evaluates math expressions + */ + fun registerDemoTools() { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + + try { + // Weather Tool - Uses Open-Meteo API (free, no API key required) + RunAnywhereToolCalling.registerTool( + definition = ToolDefinition( + name = "get_weather", + description = "Gets the current weather for a given location using Open-Meteo API", + parameters = listOf( + ToolParameter( + name = "location", + type = ToolParameterType.STRING, + description = "City name (e.g., 'San Francisco', 'London', 'Tokyo')", + required = true + ) + ), + category = "Utility" + ), + executor = { args: Map -> + fetchWeather((args["location"] as? ToolValue.StringValue)?.value ?: "San Francisco") + } + ) + + // Time Tool - Real system time with timezone + RunAnywhereToolCalling.registerTool( + definition = ToolDefinition( + name = "get_current_time", + description = "Gets the current date, time, and timezone information", + parameters = emptyList(), + category = "Utility" + ), + executor = { _: Map -> + val now = Date() + val dateFormatter = SimpleDateFormat("EEEE, MMMM d, yyyy 'at' h:mm:ss a", Locale.getDefault()) + val timeFormatter = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) + val isoFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault()).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + val tz = TimeZone.getDefault() + + mapOf( + "datetime" to ToolValue.StringValue(dateFormatter.format(now)), + "time" to ToolValue.StringValue(timeFormatter.format(now)), + "timestamp" to ToolValue.StringValue(isoFormatter.format(now)), + "timezone" to ToolValue.StringValue(tz.id), + "utc_offset" to ToolValue.StringValue(tz.getDisplayName(false, TimeZone.SHORT)) + ) + } + ) + + // Calculator Tool - Math evaluation + RunAnywhereToolCalling.registerTool( + definition = ToolDefinition( + name = "calculate", + description = "Performs math calculations. Supports +, -, *, /, and parentheses", + parameters = listOf( + ToolParameter( + name = "expression", + type = ToolParameterType.STRING, + description = "Math expression (e.g., '2 + 2 * 3', '(10 + 5) / 3')", + required = true + ) + ), + category = "Utility" + ), + executor = { args: Map -> + val expression = (args["expression"] as? ToolValue.StringValue)?.value + ?: (args["input"] as? ToolValue.StringValue)?.value + ?: "0" + evaluateMathExpression(expression) + } + ) + + Log.i(TAG, "โœ… Demo tools registered") + refreshRegisteredTools() + + } catch (e: Exception) { + Log.e(TAG, "Failed to register demo tools", e) + } finally { + _uiState.update { it.copy(isLoading = false) } + } + } + } + + fun clearAllTools() { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + try { + RunAnywhereToolCalling.clearTools() + refreshRegisteredTools() + Log.i(TAG, "โœ… All tools cleared") + } catch (e: Exception) { + Log.e(TAG, "Failed to clear tools", e) + } finally { + _uiState.update { it.copy(isLoading = false) } + } + } + } + + /** + * Detect the appropriate tool call format based on model name. + * LFM2-Tool models use the lfm2 format, others use default JSON format. + */ + fun detectToolCallFormat(modelName: String?): ToolCallFormat { + val name = modelName?.lowercase() ?: return ToolCallFormat.Default + return if (name.contains("lfm2") && name.contains("tool")) { + ToolCallFormat.LFM2 + } else { + ToolCallFormat.Default + } + } + + // ======================================================================== + // Tool Executor Implementations + // ======================================================================== + + /** + * Fetch weather using Open-Meteo API (free, no API key required) + * + * Uses a 15-second timeout for the entire operation (geocoding + weather fetch) + * to ensure tool execution respects LLM timeout settings. + */ + private suspend fun fetchWeather(location: String): Map { + return withContext(Dispatchers.IO) { + try { + // 15 second timeout for entire weather fetch operation + // This covers both geocoding and weather API calls + withTimeout(WEATHER_API_TIMEOUT_MS) { + // First, geocode the location + val geocodeUrl = "https://geocoding-api.open-meteo.com/v1/search?name=${URLEncoder.encode(location, "UTF-8")}&count=1" + val geocodeResponse = fetchUrl(geocodeUrl) + + // Parse geocode response (simple JSON parsing) + val latMatch = Regex("\"latitude\":\\s*(-?\\d+\\.?\\d*)").find(geocodeResponse) + val lonMatch = Regex("\"longitude\":\\s*(-?\\d+\\.?\\d*)").find(geocodeResponse) + val nameMatch = Regex("\"name\":\\s*\"([^\"]+)\"").find(geocodeResponse) + + if (latMatch == null || lonMatch == null) { + return@withTimeout mapOf( + "error" to ToolValue.StringValue("Location not found: $location"), + "location" to ToolValue.StringValue(location) + ) + } + + val lat = latMatch.groupValues[1] + val lon = lonMatch.groupValues[1] + val resolvedName = nameMatch?.groupValues?.get(1) ?: location + + // Fetch weather + val weatherUrl = "https://api.open-meteo.com/v1/forecast?latitude=$lat&longitude=$lon¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m" + val weatherResponse = fetchUrl(weatherUrl) + + // Parse weather response + val tempMatch = Regex("\"temperature_2m\":\\s*(-?\\d+\\.?\\d*)").find(weatherResponse) + val humidityMatch = Regex("\"relative_humidity_2m\":\\s*(\\d+)").find(weatherResponse) + val windMatch = Regex("\"wind_speed_10m\":\\s*(-?\\d+\\.?\\d*)").find(weatherResponse) + val codeMatch = Regex("\"weather_code\":\\s*(\\d+)").find(weatherResponse) + + val temperature = tempMatch?.groupValues?.get(1)?.toDoubleOrNull() ?: 0.0 + val humidity = humidityMatch?.groupValues?.get(1)?.toIntOrNull() ?: 0 + val windSpeed = windMatch?.groupValues?.get(1)?.toDoubleOrNull() ?: 0.0 + val weatherCode = codeMatch?.groupValues?.get(1)?.toIntOrNull() ?: 0 + + val condition = when (weatherCode) { + 0 -> "Clear sky" + 1, 2, 3 -> "Partly cloudy" + 45, 48 -> "Foggy" + 51, 53, 55 -> "Drizzle" + 61, 63, 65 -> "Rain" + 71, 73, 75 -> "Snow" + 80, 81, 82 -> "Rain showers" + 95, 96, 99 -> "Thunderstorm" + else -> "Unknown" + } + + mapOf( + "location" to ToolValue.StringValue(resolvedName), + "temperature_celsius" to ToolValue.NumberValue(temperature), + "temperature_fahrenheit" to ToolValue.NumberValue(temperature * 9/5 + 32), + "humidity_percent" to ToolValue.NumberValue(humidity.toDouble()), + "wind_speed_kmh" to ToolValue.NumberValue(windSpeed), + "condition" to ToolValue.StringValue(condition) + ) + } + } catch (e: TimeoutCancellationException) { + Log.w(TAG, "Weather API request timed out for location: $location") + mapOf( + "error" to ToolValue.StringValue("Weather API request timed out. Please try again."), + "location" to ToolValue.StringValue(location) + ) + } catch (e: Exception) { + Log.e(TAG, "Weather fetch failed", e) + mapOf( + "error" to ToolValue.StringValue("Failed to fetch weather: ${e.message}"), + "location" to ToolValue.StringValue(location) + ) + } + } + } + + private fun fetchUrl(urlString: String): String { + val url = URL(urlString) + val connection = url.openConnection() as HttpURLConnection + connection.requestMethod = "GET" + connection.connectTimeout = 10000 + connection.readTimeout = 10000 + + return try { + connection.inputStream.bufferedReader().use { it.readText() } + } finally { + connection.disconnect() + } + } + + /** + * Evaluate a math expression + */ + private fun evaluateMathExpression(expression: String): Map { + return try { + // Clean the expression + val cleaned = expression + .replace("=", "") + .replace("x", "*") + .replace("ร—", "*") + .replace("รท", "/") + .trim() + + // Simple expression evaluator (handles basic math) + val result = evaluateSimpleExpression(cleaned) + + mapOf( + "result" to ToolValue.NumberValue(result), + "expression" to ToolValue.StringValue(expression) + ) + } catch (e: Exception) { + mapOf( + "error" to ToolValue.StringValue("Could not evaluate expression: $expression"), + "expression" to ToolValue.StringValue(expression) + ) + } + } + + /** + * Token parser with index-based iteration supporting peek operations. + * Enables lookahead for recursive descent parsing without consuming tokens. + */ + private class TokenParser(private val tokens: List) { + private var index = 0 + + /** Returns true if there are more tokens to consume */ + fun hasNext(): Boolean = index < tokens.size + + /** Returns and consumes the next token */ + fun next(): String { + if (!hasNext()) throw NoSuchElementException("No more tokens") + return tokens[index++] + } + + /** Returns the next token without consuming it, or null if no more tokens */ + fun peek(): String? = if (hasNext()) tokens[index] else null + } + + /** + * Simple recursive descent parser for math expressions + */ + private fun evaluateSimpleExpression(expr: String): Double { + val tokens = tokenize(expr) + val parser = TokenParser(tokens) + return parseExpression(parser) + } + + private fun tokenize(expr: String): List { + val tokens = mutableListOf() + var current = StringBuilder() + + for (char in expr) { + when { + char.isDigit() || char == '.' -> current.append(char) + char in "+-*/()" -> { + if (current.isNotEmpty()) { + tokens.add(current.toString()) + current = StringBuilder() + } + tokens.add(char.toString()) + } + char.isWhitespace() -> { + if (current.isNotEmpty()) { + tokens.add(current.toString()) + current = StringBuilder() + } + } + } + } + if (current.isNotEmpty()) { + tokens.add(current.toString()) + } + return tokens + } + + private fun parseExpression(parser: TokenParser): Double { + var left = parseTerm(parser) + while (parser.hasNext()) { + val op = parser.peek() ?: break + if (op != "+" && op != "-") break + parser.next() // consume the operator + val right = parseTerm(parser) + left = if (op == "+") left + right else left - right + } + return left + } + + private fun parseTerm(parser: TokenParser): Double { + var left = parseFactor(parser) + while (parser.hasNext()) { + val op = parser.peek() ?: break + if (op != "*" && op != "/") break + parser.next() // consume the operator + val right = parseFactor(parser) + left = if (op == "*") left * right else left / right + } + return left + } + + private fun parseFactor(parser: TokenParser): Double { + if (!parser.hasNext()) return 0.0 + val token = parser.next() + return when { + token == "(" -> { + val result = parseExpression(parser) + if (parser.hasNext()) parser.next() // consume ')' + result + } + token == "-" -> -parseFactor(parser) + else -> token.toDoubleOrNull() ?: 0.0 + } + } +} diff --git a/examples/flutter/RunAnywhereAI/ios/Runner.xcodeproj/project.pbxproj b/examples/flutter/RunAnywhereAI/ios/Runner.xcodeproj/project.pbxproj index 86437972e..dc8c54d29 100644 --- a/examples/flutter/RunAnywhereAI/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/flutter/RunAnywhereAI/ios/Runner.xcodeproj/project.pbxproj @@ -470,7 +470,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = AFAL2647U9; + DEVELOPMENT_TEAM = L86FH3K93L; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -654,7 +654,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = AFAL2647U9; + DEVELOPMENT_TEAM = L86FH3K93L; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -678,7 +678,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = AFAL2647U9; + DEVELOPMENT_TEAM = L86FH3K93L; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/examples/flutter/RunAnywhereAI/lib/app/content_view.dart b/examples/flutter/RunAnywhereAI/lib/app/content_view.dart index 19b3dabe4..4c9065e4f 100644 --- a/examples/flutter/RunAnywhereAI/lib/app/content_view.dart +++ b/examples/flutter/RunAnywhereAI/lib/app/content_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:runanywhere_ai/core/design_system/app_colors.dart'; import 'package:runanywhere_ai/features/chat/chat_interface_view.dart'; import 'package:runanywhere_ai/features/settings/combined_settings_view.dart'; +import 'package:runanywhere_ai/features/tools/tools_view.dart'; import 'package:runanywhere_ai/features/voice/speech_to_text_view.dart'; import 'package:runanywhere_ai/features/voice/text_to_speech_view.dart'; import 'package:runanywhere_ai/features/voice/voice_assistant_view.dart'; @@ -26,7 +27,8 @@ class _ContentViewState extends State { SpeechToTextView(), // Tab 1: Speech-to-Text (Transcribe) TextToSpeechView(), // Tab 2: Text-to-Speech (Speak) VoiceAssistantView(), // Tab 3: Voice Assistant (STT + LLM + TTS) - CombinedSettingsView(), // Tab 4: Settings (includes Storage) + ToolsView(), // Tab 4: Tools (Tool Calling) + CombinedSettingsView(), // Tab 5: Settings (includes Storage) ]; @override @@ -66,6 +68,11 @@ class _ContentViewState extends State { selectedIcon: Icon(Icons.mic), label: 'Voice', ), + NavigationDestination( + icon: Icon(Icons.build_outlined), + selectedIcon: Icon(Icons.build), + label: 'Tools', + ), NavigationDestination( icon: Icon(Icons.settings_outlined), selectedIcon: Icon(Icons.settings), diff --git a/examples/flutter/RunAnywhereAI/lib/app/runanywhere_ai_app.dart b/examples/flutter/RunAnywhereAI/lib/app/runanywhere_ai_app.dart index aa10da675..17181fb64 100644 --- a/examples/flutter/RunAnywhereAI/lib/app/runanywhere_ai_app.dart +++ b/examples/flutter/RunAnywhereAI/lib/app/runanywhere_ai_app.dart @@ -189,11 +189,31 @@ class _RunAnywhereAIAppState extends State { 'https://huggingface.co/LiquidAI/LFM2-350M-GGUF/resolve/main/LFM2-350M-Q8_0.gguf', memoryRequirement: 400000000, ); - debugPrint('โœ… LlamaCPP module registered with LLM models'); + + // Tool Calling Optimized Models + // LFM2-1.2B-Tool - Designed for concise and precise tool calling (Liquid AI) + LlamaCpp.addModel( + id: 'lfm2-1.2b-tool-q4_k_m', + name: 'LiquidAI LFM2 1.2B Tool Q4_K_M', + url: + 'https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q4_K_M.gguf', + memoryRequirement: 800000000, + ); + LlamaCpp.addModel( + id: 'lfm2-1.2b-tool-q8_0', + name: 'LiquidAI LFM2 1.2B Tool Q8_0', + url: + 'https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q8_0.gguf', + memoryRequirement: 1400000000, + ); + debugPrint('โœ… LlamaCPP module registered with LLM models (including tool-calling optimized models)'); // Yield between module registrations await Future.delayed(Duration.zero); + // Diffusion (image generation) is not registered here. CoreML diffusion is supported + // only in the Swift SDK and Swift example app; Flutter/RN do not register diffusion. + // ONNX module with STT and TTS models // Using tar.gz format hosted on RunanywhereAI/sherpa-onnx for fast native extraction // Using explicit IDs ensures models are recognized after download across app restarts diff --git a/examples/flutter/RunAnywhereAI/lib/features/chat/chat_interface_view.dart b/examples/flutter/RunAnywhereAI/lib/features/chat/chat_interface_view.dart index d92b98f32..941c8ecff 100644 --- a/examples/flutter/RunAnywhereAI/lib/features/chat/chat_interface_view.dart +++ b/examples/flutter/RunAnywhereAI/lib/features/chat/chat_interface_view.dart @@ -1,16 +1,21 @@ import 'dart:async'; +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:runanywhere/runanywhere.dart' as sdk; +import 'package:runanywhere/public/runanywhere_tool_calling.dart'; +import 'package:runanywhere/public/types/tool_calling_types.dart'; import 'package:runanywhere_ai/core/design_system/app_colors.dart'; import 'package:runanywhere_ai/core/design_system/app_spacing.dart'; import 'package:runanywhere_ai/core/design_system/typography.dart'; import 'package:runanywhere_ai/core/services/conversation_store.dart'; import 'package:runanywhere_ai/core/utilities/constants.dart'; +import 'package:runanywhere_ai/features/chat/tool_call_views.dart'; import 'package:runanywhere_ai/features/models/model_selection_sheet.dart'; import 'package:runanywhere_ai/features/models/model_status_components.dart'; import 'package:runanywhere_ai/features/models/model_types.dart'; +import 'package:runanywhere_ai/features/settings/tool_settings_view_model.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// ChatInterfaceView (mirroring iOS ChatInterfaceView.swift) @@ -117,16 +122,25 @@ class _ChatInterfaceViewState extends State { prefs.getDouble(PreferenceKeys.defaultTemperature) ?? 0.7; final maxTokens = prefs.getInt(PreferenceKeys.defaultMaxTokens) ?? 500; - // Streaming now runs in a background isolate, so no ANR concerns - final options = sdk.LLMGenerationOptions( - maxTokens: maxTokens, - temperature: temperature, - ); + // Check if tool calling is enabled and has registered tools + final toolSettings = ToolSettingsViewModel.shared; + final useToolCalling = toolSettings.toolCallingEnabled && + toolSettings.registeredTools.isNotEmpty; - if (_useStreaming) { - await _generateStreaming(userMessage, options); + if (useToolCalling) { + await _generateWithToolCalling(userMessage, maxTokens, temperature); } else { - await _generateNonStreaming(userMessage, options); + // Streaming now runs in a background isolate, so no ANR concerns + final options = sdk.LLMGenerationOptions( + maxTokens: maxTokens, + temperature: temperature, + ); + + if (_useStreaming) { + await _generateStreaming(userMessage, options); + } else { + await _generateNonStreaming(userMessage, options); + } } } catch (e) { setState(() { @@ -136,6 +150,147 @@ class _ChatInterfaceViewState extends State { } } + /// Determines the optimal tool calling format based on the model name/ID. + /// Different models are trained on different tool calling formats. + /// Returns format name string (C++ is single source of truth for valid formats). + String _detectToolCallFormat(String? modelName) { + if (modelName == null) return ToolCallFormatName.defaultFormat; + final name = modelName.toLowerCase(); + + // LFM2-Tool models use Pythonic format: <|tool_call_start|>[func(args)]<|tool_call_end|> + if (name.contains('lfm2') && name.contains('tool')) { + return ToolCallFormatName.lfm2; + } + + // Default JSON format for general-purpose models + return ToolCallFormatName.defaultFormat; + } + + Future _generateWithToolCalling( + String prompt, + int maxTokens, + double temperature, + ) async { + // Capture model name from local state (matches Swift pattern) + final modelName = _loadedModelName; + + // Auto-detect the tool calling format based on the loaded model + final format = _detectToolCallFormat(modelName); + debugPrint('Using tool calling with format: $format for model: ${modelName ?? "unknown"}'); + + // Add empty assistant message + final assistantMessage = ChatMessage( + id: DateTime.now().millisecondsSinceEpoch.toString(), + role: MessageRole.assistant, + content: '', + timestamp: DateTime.now(), + ); + + setState(() { + _messages.add(assistantMessage); + }); + + final messageIndex = _messages.length - 1; + + try { + final result = await RunAnywhereTools.generateWithTools( + prompt, + options: ToolCallingOptions( + maxToolCalls: 3, + autoExecute: true, + formatName: format, + maxTokens: maxTokens, + temperature: temperature, + ), + ); + + final totalTime = _generationStartTime != null + ? DateTime.now().difference(_generationStartTime!).inMilliseconds / + 1000.0 + : 0.0; + + // Create ToolCallInfo from the result if tools were called + ToolCallInfo? toolCallInfo; + debugPrint('๐Ÿ“Š Tool calling result: toolCalls=${result.toolCalls.length}, toolResults=${result.toolResults.length}'); + if (result.toolCalls.isNotEmpty) { + final lastCall = result.toolCalls.last; + final lastResult = result.toolResults.isNotEmpty + ? result.toolResults.last + : null; + debugPrint('๐Ÿ“Š Creating ToolCallInfo for: ${lastCall.toolName}'); + + toolCallInfo = ToolCallInfo( + toolName: lastCall.toolName, + arguments: _formatToolValueMapToJson(lastCall.arguments), + result: lastResult?.result != null + ? _formatToolValueMapToJson(lastResult!.result!) + : null, + success: lastResult?.success ?? false, + error: lastResult?.error, + ); + debugPrint('๐Ÿ“Š ToolCallInfo created: ${toolCallInfo.toolName}, success=${toolCallInfo.success}'); + } else { + debugPrint('๐Ÿ“Š No tool calls in result - badge will NOT show'); + } + + final analytics = MessageAnalytics( + messageId: assistantMessage.id, + modelName: modelName, + totalGenerationTime: totalTime, + ); + + if (!mounted) return; + setState(() { + _messages[messageIndex] = _messages[messageIndex].copyWith( + content: result.text, + analytics: analytics, + toolCallInfo: toolCallInfo, + ); + _isGenerating = false; + }); + + _scrollToBottom(); + } catch (e) { + if (!mounted) return; + setState(() { + _messages.removeLast(); + _errorMessage = 'Tool calling failed: $e'; + _isGenerating = false; + }); + } + } + + String _formatToolValueMapToJson(Map map) { + try { + final jsonMap = {}; + for (final entry in map.entries) { + jsonMap[entry.key] = _toolValueToJson(entry.value); + } + const encoder = JsonEncoder.withIndent(' '); + return encoder.convert(jsonMap); + } catch (e) { + return map.toString(); + } + } + + dynamic _toolValueToJson(ToolValue value) { + if (value is StringToolValue) return value.value; + if (value is NumberToolValue) return value.value; + if (value is BoolToolValue) return value.value; + if (value is NullToolValue) return null; + if (value is ArrayToolValue) { + return value.value.map((v) => _toolValueToJson(v)).toList(); + } + if (value is ObjectToolValue) { + final result = {}; + for (final entry in value.value.entries) { + result[entry.key] = _toolValueToJson(entry.value); + } + return result; + } + return value.toString(); + } + Future _generateStreaming( String prompt, sdk.LLMGenerationOptions options, @@ -455,6 +610,10 @@ class _ChatInterfaceViewState extends State { } Widget _buildInputArea() { + final toolSettings = ToolSettingsViewModel.shared; + final showToolBadge = toolSettings.toolCallingEnabled && + toolSettings.registeredTools.isNotEmpty; + return Container( padding: const EdgeInsets.all(AppSpacing.large), decoration: BoxDecoration( @@ -468,43 +627,54 @@ class _ChatInterfaceViewState extends State { ], ), child: SafeArea( - child: Row( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: TextField( - controller: _controller, - focusNode: _focusNode, - maxLines: 4, - minLines: 1, - textInputAction: TextInputAction.send, - decoration: InputDecoration( - hintText: 'Type a message...', - border: OutlineInputBorder( - borderRadius: - BorderRadius.circular(AppSpacing.cornerRadiusBubble), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: AppSpacing.large, - vertical: AppSpacing.mediumLarge, + // Tool calling badge (matches iOS) + if (showToolBadge) ...[ + ToolCallingBadge(toolCount: toolSettings.registeredTools.length), + const SizedBox(height: AppSpacing.smallMedium), + ], + Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + focusNode: _focusNode, + maxLines: 4, + minLines: 1, + textInputAction: TextInputAction.send, + decoration: InputDecoration( + hintText: 'Type a message...', + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(AppSpacing.cornerRadiusBubble), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: AppSpacing.large, + vertical: AppSpacing.mediumLarge, + ), + ), + onSubmitted: (_) => _sendMessage(), + onChanged: (_) => setState(() {}), ), ), - onSubmitted: (_) => _sendMessage(), - onChanged: (_) => setState(() {}), - ), - ), - const SizedBox(width: AppSpacing.smallMedium), - IconButton.filled( - onPressed: _canSend ? _sendMessage : null, - icon: _isGenerating - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - color: Colors.white, - strokeWidth: 2, - ), - ) - : const Icon(Icons.arrow_upward), + const SizedBox(width: AppSpacing.smallMedium), + IconButton.filled( + onPressed: _canSend ? _sendMessage : null, + icon: _isGenerating + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : const Icon(Icons.arrow_upward), + ), + ], ), ], ), @@ -524,6 +694,7 @@ class ChatMessage { final String? thinkingContent; final DateTime timestamp; final MessageAnalytics? analytics; + final ToolCallInfo? toolCallInfo; const ChatMessage({ required this.id, @@ -532,6 +703,7 @@ class ChatMessage { this.thinkingContent, required this.timestamp, this.analytics, + this.toolCallInfo, }); ChatMessage copyWith({ @@ -541,6 +713,7 @@ class ChatMessage { String? thinkingContent, DateTime? timestamp, MessageAnalytics? analytics, + ToolCallInfo? toolCallInfo, }) { return ChatMessage( id: id ?? this.id, @@ -549,6 +722,7 @@ class ChatMessage { thinkingContent: thinkingContent ?? this.thinkingContent, timestamp: timestamp ?? this.timestamp, analytics: analytics ?? this.analytics, + toolCallInfo: toolCallInfo ?? this.toolCallInfo, ); } } @@ -565,6 +739,7 @@ class _MessageBubble extends StatefulWidget { class _MessageBubbleState extends State<_MessageBubble> { bool _showThinking = false; + bool _showToolCallSheet = false; @override Widget build(BuildContext context) { @@ -581,6 +756,15 @@ class _MessageBubbleState extends State<_MessageBubble> { crossAxisAlignment: isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ + // Tool call indicator (if present, matches iOS toolCallSection) + if (widget.message.toolCallInfo != null && !isUser) ...[ + ToolCallIndicator( + toolCallInfo: widget.message.toolCallInfo!, + onTap: () => _showToolCallDetails(context), + ), + const SizedBox(height: AppSpacing.smallMedium), + ], + // Thinking section (if present) if (widget.message.thinkingContent != null && widget.message.thinkingContent!.isNotEmpty) @@ -638,6 +822,21 @@ class _MessageBubbleState extends State<_MessageBubble> { ); } + void _showToolCallDetails(BuildContext context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => DraggableScrollableSheet( + initialChildSize: 0.6, + minChildSize: 0.3, + maxChildSize: 0.9, + builder: (context, scrollController) => + ToolCallDetailSheet(toolCallInfo: widget.message.toolCallInfo!), + ), + ); + } + Widget _buildThinkingSection() { return Container( margin: const EdgeInsets.only(bottom: AppSpacing.smallMedium), diff --git a/examples/flutter/RunAnywhereAI/lib/features/chat/tool_call_views.dart b/examples/flutter/RunAnywhereAI/lib/features/chat/tool_call_views.dart new file mode 100644 index 000000000..f856cc559 --- /dev/null +++ b/examples/flutter/RunAnywhereAI/lib/features/chat/tool_call_views.dart @@ -0,0 +1,384 @@ +import 'package:flutter/material.dart'; +import 'package:runanywhere_ai/core/design_system/app_colors.dart'; +import 'package:runanywhere_ai/core/design_system/app_spacing.dart'; +import 'package:runanywhere_ai/core/design_system/typography.dart'; + +/// Tool Call Info model (mirroring iOS ToolCallInfo) +class ToolCallInfo { + final String toolName; + final String arguments; // JSON string for display + final String? result; // JSON string for display + final bool success; + final String? error; + + const ToolCallInfo({ + required this.toolName, + required this.arguments, + this.result, + required this.success, + this.error, + }); +} + +/// Tool Call Indicator (mirroring iOS ToolCallIndicator) +/// +/// A tappable badge that shows tool call status and opens detail sheet. +class ToolCallIndicator extends StatelessWidget { + final ToolCallInfo toolCallInfo; + final VoidCallback onTap; + + const ToolCallIndicator({ + super.key, + required this.toolCallInfo, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final isSuccess = toolCallInfo.success; + final accentColor = + isSuccess ? AppColors.primaryAccent : AppColors.primaryOrange; + + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + decoration: BoxDecoration( + color: accentColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: accentColor.withOpacity(0.3), + width: 0.5, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + isSuccess ? Icons.build_outlined : Icons.warning_amber, + size: 12, + color: accentColor, + ), + const SizedBox(width: 6), + Text( + toolCallInfo.toolName, + style: AppTypography.caption2(context).copyWith( + color: AppColors.textSecondary(context), + ), + ), + ], + ), + ), + ); + } +} + +/// Tool Call Detail Sheet (mirroring iOS ToolCallDetailSheet) +/// +/// Shows full details of a tool call. +class ToolCallDetailSheet extends StatelessWidget { + final ToolCallInfo toolCallInfo; + + const ToolCallDetailSheet({ + super.key, + required this.toolCallInfo, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColors.backgroundPrimary(context), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Handle bar + Container( + width: 40, + height: 4, + margin: const EdgeInsets.only(top: 12), + decoration: BoxDecoration( + color: AppColors.separator(context), + borderRadius: BorderRadius.circular(2), + ), + ), + + // Header + Padding( + padding: const EdgeInsets.all(AppSpacing.large), + child: Row( + children: [ + Text( + 'Tool Call', + style: AppTypography.headline(context), + ), + const Spacer(), + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Done'), + ), + ], + ), + ), + + const Divider(height: 1), + + // Content + Flexible( + child: SingleChildScrollView( + padding: const EdgeInsets.all(AppSpacing.large), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status section + _buildStatusSection(context), + + const SizedBox(height: AppSpacing.xLarge), + + // Tool name + _buildDetailSection( + context, + title: 'Tool', + content: toolCallInfo.toolName, + ), + + const SizedBox(height: AppSpacing.xLarge), + + // Arguments + _buildCodeSection( + context, + title: 'Arguments', + code: toolCallInfo.arguments, + ), + + // Result (if present) + if (toolCallInfo.result != null) ...[ + const SizedBox(height: AppSpacing.xLarge), + _buildCodeSection( + context, + title: 'Result', + code: toolCallInfo.result!, + ), + ], + + // Error (if present) + if (toolCallInfo.error != null) ...[ + const SizedBox(height: AppSpacing.xLarge), + _buildDetailSection( + context, + title: 'Error', + content: toolCallInfo.error!, + isError: true, + ), + ], + + const SizedBox(height: AppSpacing.xxLarge), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildStatusSection(BuildContext context) { + final isSuccess = toolCallInfo.success; + final statusColor = + isSuccess ? AppColors.statusGreen : AppColors.primaryRed; + + return Container( + padding: const EdgeInsets.all(AppSpacing.large), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Icon( + isSuccess ? Icons.check_circle : Icons.cancel, + size: 24, + color: statusColor, + ), + const SizedBox(width: 10), + Text( + isSuccess ? 'Success' : 'Failed', + style: AppTypography.headline(context), + ), + ], + ), + ); + } + + Widget _buildDetailSection( + BuildContext context, { + required String title, + required String content, + bool isError = false, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppTypography.caption(context).copyWith( + color: AppColors.textSecondary(context), + ), + ), + const SizedBox(height: 8), + Text( + content, + style: AppTypography.body(context).copyWith( + color: isError ? AppColors.primaryRed : null, + ), + ), + ], + ); + } + + Widget _buildCodeSection( + BuildContext context, { + required String title, + required String code, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppTypography.caption(context).copyWith( + color: AppColors.textSecondary(context), + ), + ), + const SizedBox(height: 8), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColors.backgroundSecondary(context), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + code, + style: AppTypography.monospaced, + ), + ), + ], + ); + } +} + +/// Tool Calling Active Indicator (mirroring iOS ToolCallingActiveIndicator) +/// +/// Shows "Calling tool..." with animated gear icon. +class ToolCallingActiveIndicator extends StatefulWidget { + const ToolCallingActiveIndicator({super.key}); + + @override + State createState() => + _ToolCallingActiveIndicatorState(); +} + +class _ToolCallingActiveIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + )..repeat(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + decoration: BoxDecoration( + color: AppColors.primaryAccent.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + RotationTransition( + turns: _controller, + child: const Icon( + Icons.settings, + size: 12, + color: AppColors.primaryBlue, + ), + ), + const SizedBox(width: 6), + Text( + 'Calling tool...', + style: AppTypography.caption2(context).copyWith( + color: AppColors.textSecondary(context), + ), + ), + ], + ), + ); + } +} + +/// Tool Calling Badge (mirroring iOS toolCallingBadge) +/// +/// Shows "Tools enabled" badge above chat input. +class ToolCallingBadge extends StatelessWidget { + final int toolCount; + + const ToolCallingBadge({ + super.key, + required this.toolCount, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + decoration: BoxDecoration( + color: AppColors.primaryAccent.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.build_outlined, + size: 10, + color: AppColors.primaryBlue, + ), + const SizedBox(width: 6), + Text( + 'Tools enabled ($toolCount)', + style: AppTypography.caption2(context).copyWith( + color: AppColors.primaryAccent, + ), + ), + ], + ), + ); + } +} diff --git a/examples/flutter/RunAnywhereAI/lib/features/settings/combined_settings_view.dart b/examples/flutter/RunAnywhereAI/lib/features/settings/combined_settings_view.dart index 388059db6..7f34ff25d 100644 --- a/examples/flutter/RunAnywhereAI/lib/features/settings/combined_settings_view.dart +++ b/examples/flutter/RunAnywhereAI/lib/features/settings/combined_settings_view.dart @@ -2,12 +2,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:runanywhere/runanywhere.dart' as sdk; +import 'package:runanywhere/public/types/tool_calling_types.dart'; import 'package:runanywhere_ai/core/design_system/app_colors.dart'; import 'package:runanywhere_ai/core/design_system/app_spacing.dart'; import 'package:runanywhere_ai/core/design_system/typography.dart'; import 'package:runanywhere_ai/core/models/app_types.dart'; import 'package:runanywhere_ai/core/utilities/constants.dart'; import 'package:runanywhere_ai/core/utilities/keychain_helper.dart'; +import 'package:runanywhere_ai/features/settings/tool_settings_view_model.dart'; import 'package:url_launcher/url_launcher.dart'; /// CombinedSettingsView (mirroring iOS CombinedSettingsView.swift) @@ -381,6 +383,11 @@ class _CombinedSettingsViewState extends State { body: ListView( padding: const EdgeInsets.all(AppSpacing.large), children: [ + // Tool Calling Section (matches iOS) + _buildSectionHeader('Tool Calling'), + _buildToolCallingCard(), + const SizedBox(height: AppSpacing.large), + // API Configuration Section _buildSectionHeader('API Configuration (Testing)'), _buildApiConfigurationCard(), @@ -449,6 +456,96 @@ class _CombinedSettingsViewState extends State { ); } + Widget _buildToolCallingCard() { + return ListenableBuilder( + listenable: ToolSettingsViewModel.shared, + builder: (context, _) { + final viewModel = ToolSettingsViewModel.shared; + return Card( + child: Padding( + padding: const EdgeInsets.all(AppSpacing.large), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Enable toggle + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Enable Tool Calling'), + subtitle: const Text( + 'Allow the LLM to use registered tools', + ), + value: viewModel.toolCallingEnabled, + onChanged: (value) { + viewModel.toolCallingEnabled = value; + }, + ), + + if (viewModel.toolCallingEnabled) ...[ + const Divider(), + + // Registered tools count + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Registered Tools', + style: AppTypography.subheadline(context), + ), + Text( + '${viewModel.registeredTools.length}', + style: AppTypography.subheadlineSemibold(context) + .copyWith( + color: AppColors.primaryAccent, + ), + ), + ], + ), + + const SizedBox(height: AppSpacing.mediumLarge), + + // Add/Clear tools buttons + if (viewModel.registeredTools.isEmpty) + OutlinedButton.icon( + onPressed: () async { + await viewModel.registerDemoTools(); + }, + icon: const Icon(Icons.add), + label: const Text('Add Demo Tools'), + ) + else ...[ + // Show registered tools + ...viewModel.registeredTools.map( + (tool) => _ToolRow(tool: tool), + ), + const SizedBox(height: AppSpacing.mediumLarge), + OutlinedButton.icon( + onPressed: () async { + await viewModel.clearAllTools(); + }, + icon: const Icon(Icons.delete_outline), + label: const Text('Clear All Tools'), + style: OutlinedButton.styleFrom( + foregroundColor: AppColors.primaryRed, + ), + ), + ], + ], + + const SizedBox(height: AppSpacing.mediumLarge), + Text( + 'Allow the LLM to use registered tools to perform actions like getting weather, time, or calculations.', + style: AppTypography.caption(context).copyWith( + color: AppColors.textSecondary(context), + ), + ), + ], + ), + ), + ); + }, + ); + } + Widget _buildApiConfigurationCard() { return Card( child: Padding( @@ -898,3 +995,76 @@ class _StoredModelRowState extends State<_StoredModelRow> { } } } + +/// Tool row widget (mirroring iOS ToolRow) +class _ToolRow extends StatelessWidget { + final ToolDefinition tool; + + const _ToolRow({required this.tool}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: AppSpacing.xSmall), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.build_outlined, + size: 12, + color: AppColors.primaryAccent, + ), + const SizedBox(width: 8), + Text( + tool.name, + style: AppTypography.subheadlineSemibold(context), + ), + ], + ), + const SizedBox(height: 4), + Text( + tool.description, + style: AppTypography.caption(context).copyWith( + color: AppColors.textSecondary(context), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (tool.parameters.isNotEmpty) ...[ + const SizedBox(height: 4), + Wrap( + spacing: 4, + runSpacing: 4, + children: [ + Text( + 'Params:', + style: AppTypography.caption2(context).copyWith( + color: AppColors.textSecondary(context), + ), + ), + ...tool.parameters.map( + (param) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: AppColors.backgroundTertiary(context), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + param.name, + style: AppTypography.caption2(context), + ), + ), + ), + ], + ), + ], + ], + ), + ); + } +} diff --git a/examples/flutter/RunAnywhereAI/lib/features/settings/tool_settings_view_model.dart b/examples/flutter/RunAnywhereAI/lib/features/settings/tool_settings_view_model.dart new file mode 100644 index 000000000..0660ca47b --- /dev/null +++ b/examples/flutter/RunAnywhereAI/lib/features/settings/tool_settings_view_model.dart @@ -0,0 +1,376 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flutter/foundation.dart'; +import 'package:runanywhere/public/runanywhere_tool_calling.dart'; +import 'package:runanywhere/public/types/tool_calling_types.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Tool Settings ViewModel (mirroring iOS ToolSettingsViewModel) +/// +/// Manages tool calling state and registered tools. +class ToolSettingsViewModel extends ChangeNotifier { + // Singleton pattern (matches iOS) + static final ToolSettingsViewModel shared = ToolSettingsViewModel._internal(); + + factory ToolSettingsViewModel() => shared; + + ToolSettingsViewModel._internal() { + unawaited(_loadSettings()); + } + + // State + List _registeredTools = []; + bool _toolCallingEnabled = false; + + // Getters + List get registeredTools => _registeredTools; + bool get toolCallingEnabled => _toolCallingEnabled; + + set toolCallingEnabled(bool value) { + _toolCallingEnabled = value; + unawaited(_saveSettings()); + notifyListeners(); + } + + Future _loadSettings() async { + final prefs = await SharedPreferences.getInstance(); + _toolCallingEnabled = prefs.getBool('tool_calling_enabled') ?? false; + await refreshRegisteredTools(); + notifyListeners(); + } + + Future _saveSettings() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool('tool_calling_enabled', _toolCallingEnabled); + } + + Future refreshRegisteredTools() async { + _registeredTools = RunAnywhereTools.getRegisteredTools(); + notifyListeners(); + } + + /// Register demo tools (matches iOS implementation) + Future registerDemoTools() async { + // 1. Weather Tool - Uses Open-Meteo API (free, no API key required) + RunAnywhereTools.registerTool( + const ToolDefinition( + name: 'get_weather', + description: + 'Gets the current weather for a given location using Open-Meteo API', + parameters: [ + ToolParameter( + name: 'location', + type: ToolParameterType.string, + description: "City name (e.g., 'San Francisco', 'London', 'Tokyo')", + ), + ], + ), + _fetchWeather, + ); + + // 2. Time Tool - Real system time with timezone + RunAnywhereTools.registerTool( + const ToolDefinition( + name: 'get_current_time', + description: 'Gets the current date, time, and timezone information', + parameters: [], + ), + _getCurrentTime, + ); + + // 3. Calculator Tool - Real math evaluation + RunAnywhereTools.registerTool( + const ToolDefinition( + name: 'calculate', + description: + 'Performs math calculations. Supports +, -, *, /, and parentheses', + parameters: [ + ToolParameter( + name: 'expression', + type: ToolParameterType.string, + description: "Math expression (e.g., '2 + 2 * 3', '(10 + 5) / 3')", + ), + ], + ), + _calculate, + ); + + await refreshRegisteredTools(); + } + + Future clearAllTools() async { + RunAnywhereTools.clearTools(); + await refreshRegisteredTools(); + } + + // MARK: - Tool Executors + + /// Weather tool executor - fetches real weather data from Open-Meteo + Future> _fetchWeather( + Map args, + ) async { + final rawLocation = args['location']?.stringValue; + + // Require location argument - no hardcoded defaults + if (rawLocation == null || rawLocation.isEmpty) { + return { + 'error': const StringToolValue('Missing required argument: location'), + }; + } + + // Clean up location string - Open-Meteo works better with just city names + // Remove common suffixes like ", CA", ", US", ", USA", etc. + final location = _cleanLocationString(rawLocation); + + try { + // Step 1: Geocode the location + final geocodeUrl = Uri.parse( + 'https://geocoding-api.open-meteo.com/v1/search?name=${Uri.encodeComponent(location)}&count=5&language=en&format=json', + ); + + final geocodeResponse = await http.get(geocodeUrl); + if (geocodeResponse.statusCode != 200) { + throw Exception('Geocoding failed'); + } + + final geocodeData = + jsonDecode(geocodeResponse.body) as Map; + final results = geocodeData['results'] as List?; + if (results == null || results.isEmpty) { + return { + 'error': StringToolValue('Could not find location: $location'), + 'location': StringToolValue(location), + }; + } + + final firstResult = results[0] as Map; + final lat = firstResult['latitude'] as num; + final lon = firstResult['longitude'] as num; + final cityName = firstResult['name'] as String? ?? location; + + // Step 2: Fetch weather for coordinates + final weatherUrl = Uri.parse( + 'https://api.open-meteo.com/v1/forecast?latitude=$lat&longitude=$lon¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m&temperature_unit=fahrenheit&wind_speed_unit=mph', + ); + + final weatherResponse = await http.get(weatherUrl); + if (weatherResponse.statusCode != 200) { + throw Exception('Weather fetch failed'); + } + + final weatherData = + jsonDecode(weatherResponse.body) as Map; + final current = weatherData['current'] as Map; + final temp = current['temperature_2m'] as num? ?? 0; + final humidity = current['relative_humidity_2m'] as num? ?? 0; + final windSpeed = current['wind_speed_10m'] as num? ?? 0; + final weatherCode = current['weather_code'] as int? ?? 0; + + return { + 'location': StringToolValue(cityName), + 'temperature': NumberToolValue(temp.toDouble()), + 'unit': const StringToolValue('fahrenheit'), + 'humidity': NumberToolValue(humidity.toDouble()), + 'wind_speed_mph': NumberToolValue(windSpeed.toDouble()), + 'condition': StringToolValue(_weatherCodeToCondition(weatherCode)), + }; + } catch (e) { + return { + 'error': StringToolValue('Weather fetch failed: $e'), + 'location': StringToolValue(location), + }; + } + } + + String _weatherCodeToCondition(int code) { + switch (code) { + case 0: + return 'Clear sky'; + case 1: + return 'Mainly clear'; + case 2: + return 'Partly cloudy'; + case 3: + return 'Overcast'; + case 45: + case 48: + return 'Foggy'; + case 51: + case 53: + case 55: + return 'Drizzle'; + case 56: + case 57: + return 'Freezing drizzle'; + case 61: + case 63: + case 65: + return 'Rain'; + case 66: + case 67: + return 'Freezing rain'; + case 71: + case 73: + case 75: + return 'Snow'; + case 77: + return 'Snow grains'; + case 80: + case 81: + case 82: + return 'Rain showers'; + case 85: + case 86: + return 'Snow showers'; + case 95: + return 'Thunderstorm'; + case 96: + case 99: + return 'Thunderstorm with hail'; + default: + return 'Unknown'; + } + } + + /// Clean location string for better geocoding results + /// Removes common suffixes like ", CA", ", US", state abbreviations, etc. + String _cleanLocationString(String location) { + var cleaned = location.trim(); + + // Common patterns to remove: ", CA", ", NY", ", US", ", USA", ", United States" + // Also handle variations like "CA" at the end + final patterns = [ + RegExp(r',\s*(US|USA|United States)$', caseSensitive: false), + RegExp(r',\s*[A-Z]{2}$'), // State abbreviations like ", CA", ", NY" + RegExp(r',\s*[A-Z]{2},\s*(US|USA)$', caseSensitive: false), // ", CA, US" + ]; + + for (final pattern in patterns) { + cleaned = cleaned.replaceAll(pattern, ''); + } + + // Also handle "SF" -> "San Francisco" for common abbreviations + final abbreviations = { + 'SF': 'San Francisco', + 'NYC': 'New York City', + 'LA': 'Los Angeles', + 'DC': 'Washington DC', + }; + + final upperCleaned = cleaned.toUpperCase(); + if (abbreviations.containsKey(upperCleaned)) { + return abbreviations[upperCleaned]!; + } + + return cleaned; + } + + /// Time tool executor + Future> _getCurrentTime( + Map args, + ) async { + final now = DateTime.now(); + + return { + 'datetime': StringToolValue(now.toString()), + 'time': StringToolValue( + '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}', + ), + 'timestamp': StringToolValue(now.toIso8601String()), + 'timezone': StringToolValue(now.timeZoneName), + 'utc_offset': StringToolValue( + '${now.timeZoneOffset.isNegative ? '-' : '+'}${now.timeZoneOffset.inHours.abs().toString().padLeft(2, '0')}:${(now.timeZoneOffset.inMinutes.abs() % 60).toString().padLeft(2, '0')}', + ), + }; + } + + /// Calculator tool executor + Future> _calculate( + Map args, + ) async { + // Try both 'expression' and 'input' keys - no hardcoded defaults + final expression = args['expression']?.stringValue ?? args['input']?.stringValue; + + if (expression == null || expression.isEmpty) { + return { + 'error': const StringToolValue('Missing required argument: expression'), + }; + } + + try { + // Clean the expression + final cleanedExpression = expression + .replaceAll('=', '') + .replaceAll('x', '*') + .replaceAll('ร—', '*') + .replaceAll('รท', '/') + .trim(); + + final result = _evaluateExpression(cleanedExpression); + + return { + 'result': NumberToolValue(result), + 'expression': StringToolValue(expression), + }; + } catch (e) { + return { + 'error': StringToolValue('Could not evaluate expression: $expression'), + 'expression': StringToolValue(expression), + }; + } + } + + double _evaluateExpression(String expr) { + // Remove spaces + expr = expr.replaceAll(' ', ''); + + // Handle power operator first (** or ^) + if (expr.contains('**')) { + final parts = expr.split('**'); + var result = double.parse(parts[0]); + for (var i = 1; i < parts.length; i++) { + result = _pow(result, double.parse(parts[i])); + } + return result; + } else if (expr.contains('^')) { + final parts = expr.split('^'); + var result = double.parse(parts[0]); + for (var i = 1; i < parts.length; i++) { + result = _pow(result, double.parse(parts[i])); + } + return result; + } + + // Handle simple operations + if (expr.contains('+')) { + final parts = expr.split('+'); + return parts.map((p) => double.parse(p)).reduce((a, b) => a + b); + } else if (expr.contains('-') && !expr.startsWith('-')) { + final parts = expr.split('-'); + var result = double.parse(parts[0]); + for (var i = 1; i < parts.length; i++) { + result -= double.parse(parts[i]); + } + return result; + } else if (expr.contains('*')) { + final parts = expr.split('*'); + return parts.map((p) => double.parse(p)).reduce((a, b) => a * b); + } else if (expr.contains('/')) { + final parts = expr.split('/'); + var result = double.parse(parts[0]); + for (var i = 1; i < parts.length; i++) { + result /= double.parse(parts[i]); + } + return result; + } + + return double.parse(expr); + } + + double _pow(double base, double exponent) { + return math.pow(base, exponent).toDouble(); + } +} diff --git a/examples/flutter/RunAnywhereAI/lib/features/tools/tools_view.dart b/examples/flutter/RunAnywhereAI/lib/features/tools/tools_view.dart new file mode 100644 index 000000000..cf2d23875 --- /dev/null +++ b/examples/flutter/RunAnywhereAI/lib/features/tools/tools_view.dart @@ -0,0 +1,782 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:runanywhere/runanywhere.dart' as sdk; +import 'package:runanywhere/public/runanywhere_tool_calling.dart'; +import 'package:runanywhere/public/types/tool_calling_types.dart'; +import 'package:runanywhere_ai/core/design_system/app_colors.dart'; +import 'package:runanywhere_ai/core/design_system/app_spacing.dart'; +import 'package:runanywhere_ai/core/design_system/typography.dart'; +import 'package:runanywhere_ai/features/models/model_selection_sheet.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// ToolsView - Demonstrates tool calling functionality +/// +/// Matches iOS ToolsScreen and Android ToolSettingsScreen +class ToolsView extends StatefulWidget { + const ToolsView({super.key}); + + @override + State createState() => _ToolsViewState(); +} + +class _ToolsViewState extends State { + final TextEditingController _promptController = TextEditingController(); + final ScrollController _scrollController = ScrollController(); + + // State + bool _toolCallingEnabled = true; + bool _isGenerating = false; + String? _errorMessage; + List _registeredTools = []; + + // Results + String _toolExecutionLog = ''; + String _finalResponse = ''; + + // Model state + String? _loadedModelName; + + @override + void initState() { + super.initState(); + unawaited(_loadSettings()); + unawaited(_syncModelState()); + _registerDemoTools(); + } + + @override + void dispose() { + _promptController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + Future _loadSettings() async { + final prefs = await SharedPreferences.getInstance(); + setState(() { + _toolCallingEnabled = prefs.getBool('tool_calling_enabled') ?? true; + }); + } + + Future _saveSettings() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool('tool_calling_enabled', _toolCallingEnabled); + } + + Future _syncModelState() async { + final model = await sdk.RunAnywhere.currentLLMModel(); + if (mounted) { + setState(() { + _loadedModelName = model?.name; + }); + } + } + + /// Register demo tools matching iOS/Android examples + void _registerDemoTools() { + // Clear any existing tools + RunAnywhereTools.clearTools(); + + // 1. Weather tool + RunAnywhereTools.registerTool( + const ToolDefinition( + name: 'get_weather', + description: 'Get current weather for a location', + parameters: [ + ToolParameter( + name: 'location', + type: ToolParameterType.string, + description: 'City name or coordinates (e.g., "San Francisco, CA")', + ), + ], + ), + _fetchWeather, + ); + + // 2. Calculator tool + RunAnywhereTools.registerTool( + const ToolDefinition( + name: 'calculate', + description: 'Perform basic arithmetic calculations', + parameters: [ + ToolParameter( + name: 'expression', + type: ToolParameterType.string, + description: 'Math expression (e.g., "2 + 2", "10 * 5")', + ), + ], + ), + _calculate, + ); + + // 3. Time tool + RunAnywhereTools.registerTool( + const ToolDefinition( + name: 'get_current_time', + description: 'Get the current date and time', + parameters: [], + ), + _getCurrentTime, + ); + + setState(() { + _registeredTools = RunAnywhereTools.getRegisteredTools(); + }); + } + + /// Weather tool executor - fetches real weather data + Future> _fetchWeather( + Map args, + ) async { + final rawLocation = args['location']?.stringValue; + + // Require location argument - no hardcoded defaults + if (rawLocation == null || rawLocation.isEmpty) { + return { + 'error': const StringToolValue('Missing required argument: location'), + }; + } + + // Clean up location string for better geocoding + final location = _cleanLocationString(rawLocation); + + try { + // Use Open-Meteo API (no key required) + final geocodeUrl = Uri.parse( + 'https://geocoding-api.open-meteo.com/v1/search?name=${Uri.encodeComponent(location)}&count=5&language=en&format=json', + ); + + final geocodeResponse = await http.get(geocodeUrl); + if (geocodeResponse.statusCode != 200) { + throw Exception('Geocoding failed'); + } + + final geocodeData = jsonDecode(geocodeResponse.body) as Map; + final results = geocodeData['results'] as List?; + if (results == null || results.isEmpty) { + return { + 'error': StringToolValue('Could not find location: $location'), + 'location': StringToolValue(location), + }; + } + + final firstResult = results[0] as Map; + final lat = firstResult['latitude'] as num; + final lon = firstResult['longitude'] as num; + final cityName = firstResult['name'] as String? ?? location; + + // Fetch weather + final weatherUrl = Uri.parse( + 'https://api.open-meteo.com/v1/forecast?latitude=$lat&longitude=$lon¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m&temperature_unit=fahrenheit&wind_speed_unit=mph', + ); + + final weatherResponse = await http.get(weatherUrl); + if (weatherResponse.statusCode != 200) { + throw Exception('Weather fetch failed'); + } + + final weatherData = jsonDecode(weatherResponse.body) as Map; + final current = weatherData['current'] as Map; + final temp = current['temperature_2m'] as num? ?? 0; + final humidity = current['relative_humidity_2m'] as num? ?? 0; + final windSpeed = current['wind_speed_10m'] as num? ?? 0; + final weatherCode = current['weather_code'] as int? ?? 0; + + return { + 'location': StringToolValue(cityName), + 'temperature': NumberToolValue(temp.toDouble()), + 'unit': const StringToolValue('fahrenheit'), + 'humidity': NumberToolValue(humidity.toDouble()), + 'wind_speed_mph': NumberToolValue(windSpeed.toDouble()), + 'condition': StringToolValue(_weatherCodeToCondition(weatherCode)), + }; + } catch (e) { + return { + 'error': StringToolValue('Weather fetch failed: $e'), + 'location': StringToolValue(location), + }; + } + } + + /// Clean location string for better geocoding results + String _cleanLocationString(String location) { + var cleaned = location.trim(); + + // Common patterns to remove: ", CA", ", NY", ", US", ", USA" + final patterns = [ + RegExp(r',\s*(US|USA|United States)$', caseSensitive: false), + RegExp(r',\s*[A-Z]{2}$'), // State abbreviations like ", CA", ", NY" + RegExp(r',\s*[A-Z]{2},\s*(US|USA)$', caseSensitive: false), + ]; + + for (final pattern in patterns) { + cleaned = cleaned.replaceAll(pattern, ''); + } + + // Handle common abbreviations + final abbreviations = { + 'SF': 'San Francisco', + 'NYC': 'New York City', + 'LA': 'Los Angeles', + 'DC': 'Washington DC', + }; + + final upperCleaned = cleaned.toUpperCase(); + if (abbreviations.containsKey(upperCleaned)) { + return abbreviations[upperCleaned]!; + } + + return cleaned; + } + + String _weatherCodeToCondition(int code) { + switch (code) { + case 0: + return 'Clear sky'; + case 1: + return 'Mainly clear'; + case 2: + return 'Partly cloudy'; + case 3: + return 'Overcast'; + case 45: + case 48: + return 'Foggy'; + case 51: + case 53: + case 55: + return 'Drizzle'; + case 56: + case 57: + return 'Freezing drizzle'; + case 61: + case 63: + case 65: + return 'Rain'; + case 66: + case 67: + return 'Freezing rain'; + case 71: + case 73: + case 75: + return 'Snow'; + case 77: + return 'Snow grains'; + case 80: + case 81: + case 82: + return 'Rain showers'; + case 85: + case 86: + return 'Snow showers'; + case 95: + return 'Thunderstorm'; + case 96: + case 99: + return 'Thunderstorm with hail'; + default: + return 'Unknown'; + } + } + + /// Calculator tool executor + Future> _calculate( + Map args, + ) async { + final expression = args['expression']?.stringValue; + + // Require expression argument - no hardcoded defaults + if (expression == null || expression.isEmpty) { + return { + 'error': const StringToolValue('Missing required argument: expression'), + }; + } + + try { + // Simple expression parser for basic arithmetic + final result = _evaluateExpression(expression); + return { + 'expression': StringToolValue(expression), + 'result': NumberToolValue(result), + }; + } catch (e) { + return { + 'error': StringToolValue('Calculation failed: $e'), + 'expression': StringToolValue(expression), + }; + } + } + + double _evaluateExpression(String expr) { + // Remove spaces + expr = expr.replaceAll(' ', ''); + + // Handle power operator first (** or ^) + if (expr.contains('**')) { + final parts = expr.split('**'); + var result = double.parse(parts[0]); + for (var i = 1; i < parts.length; i++) { + result = _pow(result, double.parse(parts[i])); + } + return result; + } else if (expr.contains('^')) { + final parts = expr.split('^'); + var result = double.parse(parts[0]); + for (var i = 1; i < parts.length; i++) { + result = _pow(result, double.parse(parts[i])); + } + return result; + } + + // Handle simple operations + if (expr.contains('+')) { + final parts = expr.split('+'); + return parts.map((p) => double.parse(p)).reduce((a, b) => a + b); + } else if (expr.contains('-')) { + final parts = expr.split('-'); + var result = double.parse(parts[0]); + for (var i = 1; i < parts.length; i++) { + result -= double.parse(parts[i]); + } + return result; + } else if (expr.contains('*')) { + final parts = expr.split('*'); + return parts.map((p) => double.parse(p)).reduce((a, b) => a * b); + } else if (expr.contains('/')) { + final parts = expr.split('/'); + var result = double.parse(parts[0]); + for (var i = 1; i < parts.length; i++) { + result /= double.parse(parts[i]); + } + return result; + } + + return double.parse(expr); + } + + double _pow(double base, double exponent) { + return math.pow(base, exponent).toDouble(); + } + + /// Time tool executor + Future> _getCurrentTime( + Map args, + ) async { + final now = DateTime.now(); + return { + 'date': StringToolValue( + '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}', + ), + 'time': StringToolValue( + '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}', + ), + 'timezone': StringToolValue(now.timeZoneName), + }; + } + + Future _runToolCalling() async { + if (!sdk.RunAnywhere.isModelLoaded) { + setState(() { + _errorMessage = 'Please load an LLM model first'; + }); + return; + } + + final prompt = _promptController.text.trim(); + if (prompt.isEmpty) { + setState(() { + _errorMessage = 'Please enter a prompt'; + }); + return; + } + + setState(() { + _isGenerating = true; + _errorMessage = null; + _toolExecutionLog = ''; + _finalResponse = ''; + }); + + try { + _addToLog('Starting generation with tools...'); + + final result = await RunAnywhereTools.generateWithTools( + prompt, + options: const ToolCallingOptions( + maxToolCalls: 3, + autoExecute: true, + ), + ); + + // Log tool calls + for (final toolCall in result.toolCalls) { + _addToLog('Tool called: ${toolCall.toolName}'); + _addToLog('Arguments: ${toolCall.arguments}'); + } + + // Log tool results + for (final toolResult in result.toolResults) { + _addToLog('Tool result: ${toolResult.toolName}'); + _addToLog('Success: ${toolResult.success}'); + if (toolResult.result != null) { + _addToLog('Result: ${toolResult.result}'); + } + if (toolResult.error != null) { + _addToLog('Error: ${toolResult.error}'); + } + } + + setState(() { + _finalResponse = result.text; + _isGenerating = false; + }); + } catch (e) { + setState(() { + _errorMessage = 'Tool calling failed: $e'; + _isGenerating = false; + }); + } + } + + void _addToLog(String message) { + setState(() { + final timestamp = DateTime.now().toString().substring(11, 19); + _toolExecutionLog += '[$timestamp] $message\n'; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.backgroundPrimary(context), + body: SafeArea( + child: Column( + children: [ + // Model status header + _buildHeader(context), + + // Main content + Expanded( + child: SingleChildScrollView( + controller: _scrollController, + padding: const EdgeInsets.all(AppSpacing.padding16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Tool calling toggle + _buildToolCallingToggle(context), + + const SizedBox(height: AppSpacing.large), + + // Registered tools + _buildRegisteredToolsSection(context), + + const SizedBox(height: AppSpacing.large), + + // Prompt input + _buildPromptInput(context), + + const SizedBox(height: AppSpacing.medium), + + // Run button + _buildRunButton(context), + + // Error message + if (_errorMessage != null) ...[ + const SizedBox(height: AppSpacing.medium), + _buildErrorMessage(context), + ], + + // Results + if (_toolExecutionLog.isNotEmpty) ...[ + const SizedBox(height: AppSpacing.large), + _buildExecutionLog(context), + ], + + if (_finalResponse.isNotEmpty) ...[ + const SizedBox(height: AppSpacing.large), + _buildFinalResponse(context), + ], + ], + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildHeader(BuildContext context) { + return Container( + padding: const EdgeInsets.all(AppSpacing.padding16), + decoration: BoxDecoration( + color: AppColors.backgroundSecondary(context), + border: Border( + bottom: BorderSide(color: AppColors.separator(context)), + ), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Tool Calling', + style: AppTypography.headline(context), + ), + const SizedBox(height: 4), + if (_loadedModelName != null) + Text( + 'Model: $_loadedModelName', + style: AppTypography.caption(context).copyWith( + color: AppColors.textSecondary(context), + ), + ) + else + Text( + 'No model loaded', + style: AppTypography.caption(context).copyWith( + color: AppColors.primaryOrange, + ), + ), + ], + ), + ), + IconButton( + icon: const Icon(Icons.layers_outlined), + onPressed: () => _showModelSelection(context), + color: AppColors.primaryAccent, + ), + ], + ), + ); + } + + Widget _buildToolCallingToggle(BuildContext context) { + return Card( + child: SwitchListTile( + title: const Text('Enable Tool Calling'), + subtitle: const Text('Allow LLM to use external tools'), + value: _toolCallingEnabled, + onChanged: (value) { + setState(() { + _toolCallingEnabled = value; + }); + unawaited(_saveSettings()); + }, + ), + ); + } + + Widget _buildRegisteredToolsSection(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Registered Tools (${_registeredTools.length})', + style: AppTypography.subheadline(context).copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: AppSpacing.small), + ...(_registeredTools.map((tool) => _buildToolCard(context, tool))), + ], + ); + } + + Widget _buildToolCard(BuildContext context, ToolDefinition tool) { + return Card( + margin: const EdgeInsets.only(bottom: AppSpacing.small), + child: Padding( + padding: const EdgeInsets.all(AppSpacing.padding16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.build_outlined, size: 16, color: AppColors.primaryAccent), + const SizedBox(width: 8), + Text( + tool.name, + style: AppTypography.body(context).copyWith( + fontWeight: FontWeight.w600, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + tool.description, + style: AppTypography.caption(context).copyWith( + color: AppColors.textSecondary(context), + ), + ), + if (tool.parameters.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + 'Parameters: ${tool.parameters.map((p) => p.name).join(", ")}', + style: AppTypography.caption2(context).copyWith( + color: AppColors.textSecondary(context), + ), + ), + ], + ], + ), + ), + ); + } + + Widget _buildPromptInput(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Test Prompt', + style: AppTypography.subheadline(context).copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: AppSpacing.small), + TextField( + controller: _promptController, + maxLines: 3, + decoration: InputDecoration( + hintText: 'Try: "What\'s the weather in San Francisco?"', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + filled: true, + fillColor: AppColors.backgroundSecondary(context), + ), + ), + ], + ); + } + + Widget _buildRunButton(BuildContext context) { + return SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: (_isGenerating || !_toolCallingEnabled) + ? null + : _runToolCalling, + icon: _isGenerating + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.play_arrow), + label: Text(_isGenerating ? 'Generating...' : 'Run with Tools'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ); + } + + Widget _buildErrorMessage(BuildContext context) { + return Container( + padding: const EdgeInsets.all(AppSpacing.padding16), + decoration: BoxDecoration( + color: AppColors.primaryRed.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Icon(Icons.error_outline, color: AppColors.primaryRed), + const SizedBox(width: 8), + Expanded( + child: Text( + _errorMessage!, + style: TextStyle(color: AppColors.primaryRed), + ), + ), + ], + ), + ); + } + + Widget _buildExecutionLog(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Execution Log', + style: AppTypography.subheadline(context).copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: AppSpacing.small), + Container( + width: double.infinity, + padding: const EdgeInsets.all(AppSpacing.padding16), + decoration: BoxDecoration( + color: AppColors.backgroundSecondary(context), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + _toolExecutionLog, + style: AppTypography.caption(context).copyWith( + fontFamily: 'Menlo', + color: AppColors.textSecondary(context), + ), + ), + ), + ], + ); + } + + Widget _buildFinalResponse(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Final Response', + style: AppTypography.subheadline(context).copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: AppSpacing.small), + Container( + width: double.infinity, + padding: const EdgeInsets.all(AppSpacing.padding16), + decoration: BoxDecoration( + color: AppColors.primaryAccent.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.primaryAccent.withOpacity(0.3), + ), + ), + child: Text( + _finalResponse, + style: AppTypography.body(context), + ), + ), + ], + ); + } + + void _showModelSelection(BuildContext context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (sheetContext) => ModelSelectionSheet( + onModelSelected: (model) async { + // ModelSelectionSheet handles closing itself, so just sync state + unawaited(_syncModelState()); + }, + ), + ); + } +} diff --git a/examples/flutter/RunAnywhereAI/pubspec.yaml b/examples/flutter/RunAnywhereAI/pubspec.yaml index e4df5d7df..a678deb36 100644 --- a/examples/flutter/RunAnywhereAI/pubspec.yaml +++ b/examples/flutter/RunAnywhereAI/pubspec.yaml @@ -37,6 +37,8 @@ dependencies: package_info_plus: ^4.2.0 # URL launcher url_launcher: ^6.2.0 + # HTTP client for API calls + http: ^1.2.0 dev_dependencies: flutter_test: diff --git a/examples/ios/RunAnywhereAI/Package.resolved b/examples/ios/RunAnywhereAI/Package.resolved index 2bfe6e6b4..4a2b441a2 100644 --- a/examples/ios/RunAnywhereAI/Package.resolved +++ b/examples/ios/RunAnywhereAI/Package.resolved @@ -36,6 +36,15 @@ "version" : "4.3.0" } }, + { + "identity" : "ml-stable-diffusion", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/ml-stable-diffusion.git", + "state" : { + "revision" : "5a170d29cf38e674b80541d7ce22929c6a11cdde", + "version" : "1.1.1" + } + }, { "identity" : "sentry-cocoa", "kind" : "remoteSourceControl", @@ -54,6 +63,15 @@ "version" : "4.8.6" } }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "c5d11a805e765f52ba34ec7284bd4fcd6ba68615", + "version" : "1.7.0" + } + }, { "identity" : "swift-asn1", "kind" : "remoteSourceControl", diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI.xcodeproj/project.pbxproj b/examples/ios/RunAnywhereAI/RunAnywhereAI.xcodeproj/project.pbxproj index 0011dc4fb..ef57ac16b 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI.xcodeproj/project.pbxproj +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI.xcodeproj/project.pbxproj @@ -475,6 +475,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = RunAnywhere; + INFOPLIST_KEY_NSCameraUsageDescription = "RunAnywhere AI needs access to your camera for vision language model features to analyze images."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "RunAnywhere AI needs access to your microphone to transcribe your voice input and provide voice-based interactions."; INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "RunAnywhere AI uses speech recognition to convert your voice to text for processing."; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; @@ -513,10 +514,11 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = XMP5QMWA2U; + DEVELOPMENT_TEAM = L86FH3K93L; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = RunAnywhere; + INFOPLIST_KEY_NSCameraUsageDescription = "RunAnywhere AI needs access to your camera for vision language model features to analyze images."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "RunAnywhere AI needs access to your microphone to transcribe your voice input and provide voice-based interactions."; INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "RunAnywhere AI uses speech recognition to convert your voice to text for processing."; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/examples/ios/RunAnywhereAI/RunAnywhereAI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ba9a2b63b..1c23aa765 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Alamofire/Alamofire.git", "state" : { - "revision" : "7be73f6c2b5cd90e40798b06ebd5da8f9f79cf88", - "version" : "5.11.0" + "revision" : "3f99050e75bbc6fe71fc323adabb039756680016", + "version" : "5.11.1" } }, { @@ -37,6 +37,15 @@ "version" : "4.3.0" } }, + { + "identity" : "ml-stable-diffusion", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/ml-stable-diffusion.git", + "state" : { + "revision" : "5a170d29cf38e674b80541d7ce22929c6a11cdde", + "version" : "1.1.1" + } + }, { "identity" : "sentry-cocoa", "kind" : "remoteSourceControl", @@ -55,6 +64,15 @@ "version" : "4.8.6" } }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "c5d11a805e765f52ba34ec7284bd4fcd6ba68615", + "version" : "1.7.0" + } + }, { "identity" : "swift-asn1", "kind" : "remoteSourceControl", diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/App/ContentView.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/App/ContentView.swift index f85804305..621dc9477 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI/App/ContentView.swift +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/App/ContentView.swift @@ -2,7 +2,8 @@ // ContentView.swift // RunAnywhereAI // -// Created by Sanchit Monga on 7/21/25. +// Main app navigation with 5 tabs (iOS limit) +// Organized by AI capability: Chat, Vision, Voice, More utilities, Settings // import SwiftUI @@ -12,43 +13,38 @@ struct ContentView: View { var body: some View { TabView(selection: $selectedTab) { - // Tab 0: Chat (LLM) + // Tab 0: Chat - Pure text LLM conversation ChatInterfaceView() - .frame(maxWidth: .infinity, maxHeight: .infinity) .tabItem { Label("Chat", systemImage: "message") } .tag(0) - // Tab 1: Speech-to-Text - SpeechToTextView() - .frame(maxWidth: .infinity, maxHeight: .infinity) + // Tab 1: Vision - Image understanding & generation + VisionHubView() .tabItem { - Label("Transcribe", systemImage: "waveform") + Label("Vision", systemImage: "eye") } .tag(1) - // Tab 2: Text-to-Speech - TextToSpeechView() - .frame(maxWidth: .infinity, maxHeight: .infinity) + // Tab 2: Voice - Voice Assistant (hero feature) + VoiceAssistantView() .tabItem { - Label("Speak", systemImage: "speaker.wave.2") + Label("Voice", systemImage: "mic.circle") } .tag(2) - // Tab 3: Voice Assistant (STT + LLM + TTS) - VoiceAssistantView() - .frame(maxWidth: .infinity, maxHeight: .infinity) + // Tab 3: More - Additional utilities + MoreHubView() .tabItem { - Label("Voice", systemImage: "mic") + Label("More", systemImage: "ellipsis.circle") } .tag(3) - // Tab 4: Combined Settings (includes Storage) + // Tab 4: Settings Group { #if os(macOS) CombinedSettingsView() - .frame(maxWidth: .infinity, maxHeight: .infinity) #else NavigationView { CombinedSettingsView() @@ -65,17 +61,131 @@ struct ContentView: View { .accentColor(AppColors.primaryAccent) #if os(macOS) .frame( - minWidth: 800, - idealWidth: 1200, - maxWidth: .infinity, - minHeight: 600, - idealHeight: 800, - maxHeight: .infinity + minWidth: 800, idealWidth: 1200, maxWidth: .infinity, + minHeight: 600, idealHeight: 800, maxHeight: .infinity ) #endif } } +// MARK: - Vision Hub (VLM + Image Generation) + +struct VisionHubView: View { + var body: some View { + NavigationView { + List { + Section { + NavigationLink { + VLMCameraView() + } label: { + FeatureRow( + icon: "camera.viewfinder", + iconColor: .purple, + title: "Vision Chat", + subtitle: "Chat with images using your camera or photos" + ) + } + + NavigationLink { + ImageGenerationView() + } label: { + FeatureRow( + icon: "photo.on.rectangle.angled", + iconColor: .pink, + title: "Image Generation", + subtitle: "Create images from text prompts" + ) + } + } header: { + Text("Vision AI") + } footer: { + Text("Understand and create visual content with AI") + } + } + .navigationTitle("Vision") + } + } +} + +// MARK: - More Hub (Additional Utilities) + +struct MoreHubView: View { + var body: some View { + NavigationView { + List { + Section { + NavigationLink { + SpeechToTextView() + } label: { + FeatureRow( + icon: "waveform", + iconColor: .blue, + title: "Transcribe", + subtitle: "Convert speech to text" + ) + } + + NavigationLink { + TextToSpeechView() + } label: { + FeatureRow( + icon: "speaker.wave.2", + iconColor: .green, + title: "Speak", + subtitle: "Convert text to speech" + ) + } + + NavigationLink { + StorageView() + } label: { + FeatureRow( + icon: "folder", + iconColor: .orange, + title: "Storage", + subtitle: "Manage models and files" + ) + } + } header: { + Text("Utilities") + } footer: { + Text("Additional tools and utilities") + } + } + .navigationTitle("More") + } + } +} + +// MARK: - Feature Row Component + +struct FeatureRow: View { + let icon: String + let iconColor: Color + let title: String + let subtitle: String + + var body: some View { + HStack(spacing: 16) { + Image(systemName: icon) + .font(.title2) + .foregroundColor(.white) + .frame(width: 44, height: 44) + .background(iconColor) + .cornerRadius(10) + + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.headline) + Text(subtitle) + .font(.caption) + .foregroundColor(.secondary) + } + } + .padding(.vertical, 8) + } +} + #Preview { ContentView() } diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/App/RunAnywhereAIApp.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/App/RunAnywhereAIApp.swift index 4fa568f2c..4d0e05cd3 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI/App/RunAnywhereAIApp.swift +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/App/RunAnywhereAIApp.swift @@ -59,6 +59,12 @@ struct RunAnywhereAIApp: App { private func initializeSDK() async { do { + // Register backends with C++ registry FIRST, before any await. Otherwise we can + // suspend at the next line and another task may run loadModel() โ†’ ensureServicesReady() + // โ†’ only Platform is registered โ†’ -422 "No provider could handle the request". + LlamaCPP.register(priority: 100) + ONNX.register(priority: 100) + // Clear any previous error await MainActor.run { initializationError = nil } @@ -204,7 +210,64 @@ struct RunAnywhereAIApp: App { memoryRequirement: 400_000_000 ) } - logger.info("โœ… LLM models registered") + + // Tool Calling Optimized Models + // LFM2-1.2B-Tool - Designed for concise and precise tool calling (Liquid AI) + if let lfm2ToolQ4URL = URL(string: "https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q4_K_M.gguf") { + RunAnywhere.registerModel( + id: "lfm2-1.2b-tool-q4_k_m", + name: "LiquidAI LFM2 1.2B Tool Q4_K_M", + url: lfm2ToolQ4URL, + framework: .llamaCpp, + memoryRequirement: 800_000_000 + ) + } + if let lfm2ToolQ8URL = URL(string: "https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q8_0.gguf") { + RunAnywhere.registerModel( + id: "lfm2-1.2b-tool-q8_0", + name: "LiquidAI LFM2 1.2B Tool Q8_0", + url: lfm2ToolQ8URL, + framework: .llamaCpp, + memoryRequirement: 1_400_000_000 + ) + } + + logger.info("โœ… LLM models registered (including tool-calling optimized models)") + + // Register VLM (Vision Language) models + // VLM models require 2 files: main model + mmproj (vision projector) + // Bundled as tar.gz archives for easy download/extraction + + // SmolVLM 500M - Ultra-lightweight VLM for mobile (~500MB total) + if let smolVLMURL = URL(string: "https://github.com/RunanywhereAI/sherpa-onnx/releases/download/runanywhere-vlm-models-v1/smolvlm-500m-instruct-q8_0.tar.gz") { + RunAnywhere.registerModel( + id: "smolvlm-500m-instruct-q8_0", + name: "SmolVLM 500M Instruct", + url: smolVLMURL, + framework: .llamaCpp, + modality: .multimodal, + artifactType: .archive(.tarGz, structure: .directoryBased), + memoryRequirement: 600_000_000 + ) + } + // Qwen2-VL 2B - Small but capable VLM (~1.6GB total) + // Uses multi-file download: main model (986MB) + mmproj (710MB) + // Downloaded separately to avoid memory-intensive tar.gz extraction on iOS + if let qwenMainURL = URL(string: "https://huggingface.co/ggml-org/Qwen2-VL-2B-Instruct-GGUF/resolve/main/Qwen2-VL-2B-Instruct-Q4_K_M.gguf"), + let qwenMmprojURL = URL(string: "https://huggingface.co/ggml-org/Qwen2-VL-2B-Instruct-GGUF/resolve/main/mmproj-Qwen2-VL-2B-Instruct-Q8_0.gguf") { + RunAnywhere.registerMultiFileModel( + id: "qwen2-vl-2b-instruct-q4_k_m", + name: "Qwen2-VL 2B Instruct", + files: [ + ModelFileDescriptor(url: qwenMainURL, filename: "Qwen2-VL-2B-Instruct-Q4_K_M.gguf"), + ModelFileDescriptor(url: qwenMmprojURL, filename: "mmproj-Qwen2-VL-2B-Instruct-Q8_0.gguf") + ], + framework: .llamaCpp, + modality: .multimodal, + memoryRequirement: 1_800_000_000 + ) + } + logger.info("โœ… VLM models registered") // Register ONNX STT and TTS models // Using tar.gz format hosted on RunanywhereAI/sherpa-onnx for fast native extraction @@ -242,6 +305,24 @@ struct RunAnywhereAIApp: App { ) } logger.info("โœ… ONNX STT/TTS models registered") + + // Register Diffusion models (Apple Stable Diffusion / CoreML only; no ONNX) + // ============================================================================ + // Apple SD 1.5 CoreML: palettized, split_einsum_v2 for Apple Silicon / ANE (~1.5GB) + if let sd15CoreMLURL = URL(string: "https://huggingface.co/apple/coreml-stable-diffusion-v1-5-palettized/resolve/main/coreml-stable-diffusion-v1-5-palettized_split_einsum_v2_compiled.zip") { + RunAnywhere.registerModel( + id: "sd15-coreml-palettized", + name: "Stable Diffusion 1.5 (CoreML)", + url: sd15CoreMLURL, + framework: .coreml, + modality: .imageGeneration, + artifactType: .archive(.zip, structure: .nestedDirectory), + memoryRequirement: 1_600_000_000 // ~1.6GB + ) + } + + logger.info("โœ… Diffusion models registered (Apple Stable Diffusion / CoreML only)") + logger.info("๐ŸŽ‰ All modules and models registered") } } diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Models/Message.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Models/Message.swift index b4417b4e3..daec2c42b 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Models/Message.swift +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Models/Message.swift @@ -18,6 +18,7 @@ public struct Message: Identifiable, Codable, Sendable { public let timestamp: Date public let analytics: MessageAnalytics? public let modelInfo: MessageModelInfo? + public let toolCallInfo: ToolCallInfo? public enum Role: String, Codable, Sendable { case system @@ -32,7 +33,8 @@ public struct Message: Identifiable, Codable, Sendable { thinkingContent: String? = nil, timestamp: Date = Date(), analytics: MessageAnalytics? = nil, - modelInfo: MessageModelInfo? = nil + modelInfo: MessageModelInfo? = nil, + toolCallInfo: ToolCallInfo? = nil ) { self.id = id self.role = role @@ -41,6 +43,39 @@ public struct Message: Identifiable, Codable, Sendable { self.timestamp = timestamp self.analytics = analytics self.modelInfo = modelInfo + self.toolCallInfo = toolCallInfo + } +} + +// MARK: - Tool Call Info + +public struct ToolCallInfo: Codable, Sendable { + public let toolName: String + public let arguments: String // JSON string for display + public let result: String? // JSON string for display + public let success: Bool + public let error: String? + + public init( + toolName: String, + arguments: [String: ToolValue], + result: [String: ToolValue]? = nil, + success: Bool, + error: String? = nil + ) { + self.toolName = toolName + self.success = success + self.error = error + + // Convert arguments to JSON string using ToolValue + self.arguments = ToolValue.object(arguments).toJSONString(pretty: true) ?? "{}" + + // Convert result to JSON string using ToolValue + if let result = result { + self.result = ToolValue.object(result).toJSONString(pretty: true) + } else { + self.result = nil + } } } diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel+Analytics.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel+Analytics.swift index d65f983ff..874f297ad 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel+Analytics.swift +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel+Analytics.swift @@ -46,8 +46,8 @@ extension LLMViewModel { ) -> MessageAnalytics { let completionStatus: MessageAnalytics.CompletionStatus = wasInterrupted ? .interrupted : .complete let generationParameters = MessageAnalytics.GenerationParameters( - temperature: Double(options.temperature ?? Float(LLMViewModel.defaultTemperatureValue)), - maxTokens: options.maxTokens ?? LLMViewModel.defaultMaxTokensValue, + temperature: Double(options.temperature), + maxTokens: options.maxTokens, topP: nil, topK: nil ) diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel+ToolCalling.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel+ToolCalling.swift new file mode 100644 index 000000000..e376084fa --- /dev/null +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel+ToolCalling.swift @@ -0,0 +1,121 @@ +// +// LLMViewModel+ToolCalling.swift +// RunAnywhereAI +// +// Tool calling generation functionality for LLMViewModel +// + +import Foundation +import RunAnywhere + +extension LLMViewModel { + + // MARK: - Tool Calling Format Detection + + /// Determines the optimal tool calling format based on the model name/ID. + /// Different models are trained on different tool calling formats. + /// Returns format name string (C++ is single source of truth for valid formats). + private func detectToolCallFormat(for modelName: String?) -> String { + guard let name = modelName?.lowercased() else { + return ToolCallFormatName.default + } + + // LFM2-Tool models use Pythonic format: <|tool_call_start|>[func(args)]<|tool_call_end|> + if name.contains("lfm2") && name.contains("tool") { + return ToolCallFormatName.lfm2 + } + + // Default JSON format for general-purpose models + return ToolCallFormatName.default + } + + // MARK: - Tool Calling Generation + + func generateWithToolCalling( + prompt: String, + options: LLMGenerationOptions, + messageIndex: Int + ) async throws { + // Auto-detect the tool calling format based on the loaded model + let format = detectToolCallFormat(for: loadedModelName) + + // Get tool calling options with the appropriate format + let toolOptions = ToolCallingOptions( + maxToolCalls: 3, + autoExecute: true, + temperature: options.temperature, + maxTokens: options.maxTokens, + format: format + ) + + // Log the format being used for debugging + print("Using tool calling with format: \(format) for model: \(loadedModelName ?? "unknown")") + + // Generate with tools + let result = try await RunAnywhere.generateWithTools(prompt, options: toolOptions) + + // Extract tool call info if any tools were called + let toolCallInfo: ToolCallInfo? + if let lastCall = result.toolCalls.last, + let lastResult = result.toolResults.last { + toolCallInfo = ToolCallInfo( + toolName: lastCall.toolName, + arguments: lastCall.arguments, + result: lastResult.result, + success: lastResult.success, + error: lastResult.error + ) + } else { + toolCallInfo = nil + } + + // Update the message with the result + await updateMessageWithToolResult( + at: messageIndex, + text: result.text, + toolCallInfo: toolCallInfo + ) + } + + // MARK: - Message Updates + + func updateMessageWithToolResult( + at index: Int, + text: String, + toolCallInfo: ToolCallInfo? + ) async { + await MainActor.run { + guard index < self.messagesValue.count else { return } + + let currentMessage = self.messagesValue[index] + + let modelInfo: MessageModelInfo? + if let currentModel = ModelListViewModel.shared.currentModel { + modelInfo = MessageModelInfo(from: currentModel) + } else { + modelInfo = nil + } + + let updatedMessage = Message( + id: currentMessage.id, + role: currentMessage.role, + content: text, + thinkingContent: nil, + timestamp: currentMessage.timestamp, + analytics: nil, // Tool calling doesn't use standard analytics + modelInfo: modelInfo, + toolCallInfo: toolCallInfo + ) + + self.updateMessage(at: index, with: updatedMessage) + + // Save conversation + if let conversation = self.currentConversation { + var updatedConversation = conversation + updatedConversation.messages = self.messagesValue + updatedConversation.modelName = self.loadedModelName + self.conversationStore.updateConversation(updatedConversation) + } + } + } +} diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel.swift index 3c234ac06..301bd8cf0 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel.swift +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel.swift @@ -37,6 +37,10 @@ final class LLMViewModel { var currentInput = "" var useStreaming = true + var useToolCalling: Bool { + get { ToolSettingsViewModel.shared.toolCallingEnabled } + set { ToolSettingsViewModel.shared.toolCallingEnabled = newValue } + } // MARK: - Dependencies @@ -241,6 +245,16 @@ final class LLMViewModel { options: LLMGenerationOptions, messageIndex: Int ) async throws { + // Check if tool calling is enabled and we have registered tools + let registeredTools = await RunAnywhere.getRegisteredTools() + let shouldUseToolCalling = useToolCalling && !registeredTools.isEmpty + + if shouldUseToolCalling { + logger.info("Using tool calling with \(registeredTools.count) registered tools") + try await generateWithToolCalling(prompt: prompt, options: options, messageIndex: messageIndex) + return + } + let modelSupportsStreaming = await RunAnywhere.supportsLLMStreaming let effectiveUseStreaming = useStreaming && modelSupportsStreaming diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ChatInterfaceView.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ChatInterfaceView.swift index d3f35eb55..fba92646b 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ChatInterfaceView.swift +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ChatInterfaceView.swift @@ -366,6 +366,12 @@ extension ChatInterfaceView { var inputArea: some View { VStack(spacing: 0) { Divider() + + // Tool calling indicator + if viewModel.useToolCalling { + toolCallingBadge + } + HStack(spacing: AppSpacing.mediumLarge) { TextField("Type a message...", text: $viewModel.currentInput, axis: .vertical) .textFieldStyle(.plain) @@ -397,6 +403,21 @@ extension ChatInterfaceView { .animation(.easeInOut(duration: AppLayout.animationFast), value: isTextFieldFocused) } } + + var toolCallingBadge: some View { + HStack(spacing: 6) { + Image(systemName: "wrench.and.screwdriver") + .font(.system(size: 10)) + Text("Tools enabled") + .font(AppTypography.caption2) + } + .foregroundColor(AppColors.primaryAccent) + .padding(.horizontal, 10) + .padding(.vertical, 4) + .background(AppColors.primaryAccent.opacity(0.1)) + .cornerRadius(6) + .padding(.top, 8) + } } // MARK: - Helper Methods diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ChatMessageComponents.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ChatMessageComponents.swift index 6000f813f..3da5e05b6 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ChatMessageComponents.swift +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ChatMessageComponents.swift @@ -67,11 +67,16 @@ struct MessageBubbleView: View { let message: Message let isGenerating: Bool @State private var isThinkingExpanded = false + @State private var showToolCallSheet = false var hasThinking: Bool { message.thinkingContent != nil && !(message.thinkingContent?.isEmpty ?? true) } + var hasToolCall: Bool { + message.toolCallInfo != nil + } + var body: some View { HStack { if message.role == .user { @@ -83,6 +88,10 @@ struct MessageBubbleView: View { thinkingSection } + if message.role == .assistant && hasToolCall { + toolCallSection + } + if message.role == .assistant && message.content.isEmpty && !(message.thinkingContent ?? "").isEmpty && @@ -99,6 +108,21 @@ struct MessageBubbleView: View { Spacer(minLength: AppSpacing.padding60) } } + .sheet(isPresented: $showToolCallSheet) { + if let toolCallInfo = message.toolCallInfo { + ToolCallDetailSheet(toolCallInfo: toolCallInfo) + .adaptiveSheetFrame() + } + } + } + + @ViewBuilder + var toolCallSection: some View { + if let toolCallInfo = message.toolCallInfo { + ToolCallIndicator(toolCallInfo: toolCallInfo) { + showToolCallSheet = true + } + } } } diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ToolCallViews.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ToolCallViews.swift new file mode 100644 index 000000000..81fd2894d --- /dev/null +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ToolCallViews.swift @@ -0,0 +1,210 @@ +// +// ToolCallViews.swift +// RunAnywhereAI +// +// Minimal UI components for tool calling visualization +// + +import SwiftUI +import RunAnywhere + +// MARK: - Tool Call Indicator + +struct ToolCallIndicator: View { + let toolCallInfo: ToolCallInfo + let onTap: () -> Void + + @State private var isPulsing = false + + var body: some View { + Button { + onTap() + } label: { + HStack(spacing: 6) { + Image(systemName: toolCallInfo.success ? "wrench.and.screwdriver" : "exclamationmark.triangle") + .font(.system(size: 12, weight: .medium)) + .foregroundColor(toolCallInfo.success ? AppColors.primaryAccent : AppColors.primaryOrange) + .scaleEffect(isPulsing ? 1.1 : 1.0) + + Text(toolCallInfo.toolName) + .font(AppTypography.caption2) + .foregroundColor(AppColors.textSecondary) + .lineLimit(1) + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(toolCallInfo.success + ? AppColors.primaryAccent.opacity(0.1) + : AppColors.primaryOrange.opacity(0.1)) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .strokeBorder( + toolCallInfo.success + ? AppColors.primaryAccent.opacity(0.3) + : AppColors.primaryOrange.opacity(0.3), + lineWidth: 0.5 + ) + ) + } + .buttonStyle(PlainButtonStyle()) + } +} + +// MARK: - Tool Call Detail Sheet + +struct ToolCallDetailSheet: View { + let toolCallInfo: ToolCallInfo + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + ScrollView { + VStack(alignment: .leading, spacing: 20) { + // Status + statusSection + + // Tool Name + detailSection(title: "Tool", content: toolCallInfo.toolName) + + // Arguments + codeSection(title: "Arguments", code: toolCallInfo.arguments) + + // Result + if let result = toolCallInfo.result { + codeSection(title: "Result", code: result) + } + + // Error + if let error = toolCallInfo.error { + detailSection(title: "Error", content: error, isError: true) + } + + Spacer() + } + .padding() + } + .background(AppColors.backgroundPrimary) + .navigationTitle("Tool Call") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { dismiss() } + } + } + } + #if os(macOS) + .frame(minWidth: 400, minHeight: 350) + #endif + } + + private var statusSection: some View { + HStack(spacing: 10) { + Image(systemName: toolCallInfo.success ? "checkmark.circle.fill" : "xmark.circle.fill") + .font(.system(size: 24)) + .foregroundColor(toolCallInfo.success ? AppColors.statusGreen : AppColors.primaryRed) + + Text(toolCallInfo.success ? "Success" : "Failed") + .font(AppTypography.headline) + .foregroundColor(AppColors.textPrimary) + + Spacer() + } + .padding() + .background( + RoundedRectangle(cornerRadius: 12) + .fill(toolCallInfo.success + ? AppColors.statusGreen.opacity(0.1) + : AppColors.primaryRed.opacity(0.1)) + ) + } + + private func detailSection(title: String, content: String, isError: Bool = false) -> some View { + VStack(alignment: .leading, spacing: 8) { + Text(title) + .font(AppTypography.caption) + .foregroundColor(AppColors.textSecondary) + + Text(content) + .font(AppTypography.body) + .foregroundColor(isError ? AppColors.primaryRed : AppColors.textPrimary) + } + } + + private func codeSection(title: String, code: String) -> some View { + VStack(alignment: .leading, spacing: 8) { + Text(title) + .font(AppTypography.caption) + .foregroundColor(AppColors.textSecondary) + + Text(code) + .font(AppTypography.monospaced) + .foregroundColor(AppColors.textPrimary) + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(AppColors.backgroundSecondary) + ) + } + } +} + +// MARK: - Tool Calling Active Indicator + +struct ToolCallingActiveIndicator: View { + @State private var rotation: Double = 0 + + var body: some View { + HStack(spacing: 6) { + Image(systemName: "gearshape.2") + .font(.system(size: 12)) + .foregroundColor(AppColors.primaryAccent) + .rotationEffect(.degrees(rotation)) + .onAppear { + withAnimation(.linear(duration: 2).repeatForever(autoreverses: false)) { + rotation = 360 + } + } + + Text("Calling tool...") + .font(AppTypography.caption2) + .foregroundColor(AppColors.textSecondary) + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(AppColors.primaryAccent.opacity(0.1)) + ) + } +} + +#Preview { + VStack(spacing: 20) { + ToolCallIndicator( + toolCallInfo: ToolCallInfo( + toolName: "get_weather", + arguments: ["location": .string("San Francisco")], + result: ["temp": .number(72), "condition": .string("Sunny")], + success: true + ) + ) {} + + ToolCallIndicator( + toolCallInfo: ToolCallInfo( + toolName: "search_web", + arguments: ["query": .string("Swift concurrency")], + success: false, + error: "Network timeout" + ) + ) {} + + ToolCallingActiveIndicator() + } + .padding() +} diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Diffusion/DiffusionViewModel.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Diffusion/DiffusionViewModel.swift new file mode 100644 index 000000000..2256095ac --- /dev/null +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Diffusion/DiffusionViewModel.swift @@ -0,0 +1,259 @@ +import Foundation +import RunAnywhere +import os +import SwiftUI + +// MARK: - Diffusion ViewModel + +/// Minimal ViewModel for Image Generation +@MainActor +class DiffusionViewModel: ObservableObject { + private let logger = Logger(subsystem: "com.runanywhere", category: "Diffusion") + + // MARK: - Published State + + @Published var isModelLoaded = false + @Published var currentModelName: String? + @Published var currentBackend: String = "" // "CoreML" or "ONNX" + @Published var availableModels: [ModelInfo] = [] + @Published var selectedModel: ModelInfo? + + @Published var isDownloading = false + @Published var downloadProgress: Double = 0.0 + @Published var downloadStatus: String = "" + + @Published var isGenerating = false + @Published var progress: Float = 0.0 + @Published var statusMessage: String = "Ready" + @Published var errorMessage: String? + + @Published var generatedImage: Image? + @Published var prompt: String = "A serene mountain landscape at sunset with golden light" + + private var isInitialized = false + + static let samplePrompts = [ + "A serene mountain landscape at sunset with golden light", + "A futuristic city with flying cars and neon lights", + "A cute corgi puppy wearing a tiny astronaut helmet", + "An ancient library filled with magical floating books", + "A cozy coffee shop on a rainy day, warm lighting" + ] + + // MARK: - Computed + + var canGenerate: Bool { + !prompt.isEmpty && !isGenerating && isModelLoaded + } + + // MARK: - Init + + func initialize() async { + guard !isInitialized else { return } + isInitialized = true + await loadAvailableModels() + await checkModelState() + } + + // MARK: - Models + + func loadAvailableModels() async { + do { + let allModels = try await RunAnywhere.availableModels() + availableModels = allModels.filter { + $0.category == ModelCategory.imageGeneration && !$0.isBuiltIn && $0.artifactType.requiresDownload + } + if let downloaded = availableModels.first(where: { $0.isDownloaded }) { + selectedModel = downloaded + } else if let first = availableModels.first { + selectedModel = first + } + } catch { + logger.error("Failed to load models: \(error.localizedDescription)") + } + } + + func checkModelState() async { + do { + isModelLoaded = try await RunAnywhere.isDiffusionModelLoaded + if isModelLoaded { + currentModelName = try await RunAnywhere.currentDiffusionModelId + // Determine backend from selected model + if let model = selectedModel { + currentBackend = model.framework.displayName + } + } + } catch { + logger.error("Failed to check model state: \(error.localizedDescription)") + } + } + + func downloadModel(_ model: ModelInfo) async { + guard !isDownloading, !model.isBuiltIn else { return } + + isDownloading = true + downloadProgress = 0.0 + downloadStatus = "Starting..." + errorMessage = nil + + do { + let stream = try await RunAnywhere.downloadModel(model.id) + for await progress in stream { + downloadProgress = progress.overallProgress + downloadStatus = "Downloading: \(Int(progress.overallProgress * 100))%" + if progress.stage == .completed { break } + } + await loadAvailableModels() + selectedModel = availableModels.first { $0.id == model.id } + } catch { + errorMessage = "Download failed: \(error.localizedDescription)" + } + + isDownloading = false + } + + @Published var currentModelVariant: DiffusionModelVariant = .sd15 + + func loadSelectedModel() async { + guard let model = selectedModel, model.isDownloaded, let path = model.localPath else { + errorMessage = "Model not downloaded" + return + } + + statusMessage = "Loading model..." + errorMessage = nil + + do { + // App only supports Apple SD 1.5 (CoreML); use .sd15 for configuration + let variant: DiffusionModelVariant = .sd15 + currentModelVariant = variant + + let config = DiffusionConfiguration(modelVariant: variant, enableSafetyChecker: true, reduceMemory: true) + try await RunAnywhere.loadDiffusionModel(modelPath: path.path, modelId: model.id, modelName: model.name, configuration: config) + isModelLoaded = true + currentModelName = model.name + currentBackend = model.framework.displayName + + // Show helpful info about the model + let stepsInfo = variant.defaultSteps == 1 ? "1 step (ultra-fast)" : "\(variant.defaultSteps) steps" + statusMessage = "Model loaded (\(currentBackend), \(stepsInfo))" + logger.info("Loaded \(model.name) as \(variant.rawValue) - \(stepsInfo)") + } catch { + errorMessage = "Load failed: \(error.localizedDescription)" + statusMessage = "Failed" + } + } + + // MARK: - Generation + + func generateImage() async { + guard canGenerate else { + errorMessage = "Enter a prompt" + return + } + + isGenerating = true + progress = 0.0 + statusMessage = "Generating..." + errorMessage = nil + generatedImage = nil + + do { + // Use model variant defaults for optimal performance + // - SDXS: 512x512, 1 step, no CFG (ultra-fast ~2-10 sec) + // - LCM: 512x512, 4 steps, low CFG (fast ~15-30 sec) + // - SD 1.5/Turbo: defaults based on variant + let variant = self.currentModelVariant + let resolution = variant.defaultResolution + let steps = variant.defaultSteps + let guidanceScale = variant.defaultGuidanceScale + + // For mobile, cap resolution to avoid memory issues + let maxMobileRes = 512 + let width = min(resolution.width, maxMobileRes) + let height = min(resolution.height, maxMobileRes) + + logger.info("Generating with \(variant.rawValue): \(width)x\(height), \(steps) steps, CFG=\(guidanceScale)") + + let options = DiffusionGenerationOptions( + prompt: prompt, + width: width, + height: height, + steps: steps, + guidanceScale: guidanceScale + ) + // Use the progress-callback overload so the pipeline runs only once. + let result = try await RunAnywhere.generateImage( + prompt: prompt, + options: options + ) { [weak self] update in + Task { @MainActor [weak self] in + self?.progress = update.progress + if steps == 1 { + self?.statusMessage = "Processing (1-step model)..." + } else { + self?.statusMessage = "Step \(update.currentStep)/\(update.totalSteps)" + } + } + return true // continue generation + } + if let uiImage = createImage(from: result.imageData, width: result.width, height: result.height) { + generatedImage = Image(uiImage: uiImage) + statusMessage = "Done in \(result.generationTimeMs)ms" + } else { + errorMessage = "Failed to create image" + } + } catch { + errorMessage = "Generation failed: \(error.localizedDescription)" + statusMessage = "Failed" + } + + isGenerating = false + } + + func cancelGeneration() async { + try? await RunAnywhere.cancelImageGeneration() + statusMessage = "Cancelled" + isGenerating = false + } + + // MARK: - Helpers + + #if os(iOS) + private func createImage(from data: Data, width: Int, height: Int) -> UIImage? { + let size = width * height * 4 + guard data.count >= size else { return nil } + + guard let provider = CGDataProvider(data: data as CFData), + let cgImage = CGImage( + width: width, height: height, + bitsPerComponent: 8, bitsPerPixel: 32, + bytesPerRow: width * 4, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue), + provider: provider, decode: nil, + shouldInterpolate: true, intent: .defaultIntent + ) else { return nil } + + return UIImage(cgImage: cgImage) + } + #elseif os(macOS) + private func createImage(from data: Data, width: Int, height: Int) -> NSImage? { + let size = width * height * 4 + guard data.count >= size else { return nil } + + guard let provider = CGDataProvider(data: data as CFData), + let cgImage = CGImage( + width: width, height: height, + bitsPerComponent: 8, bitsPerPixel: 32, + bytesPerRow: width * 4, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue), + provider: provider, decode: nil, + shouldInterpolate: true, intent: .defaultIntent + ) else { return nil } + + return NSImage(cgImage: cgImage, size: NSSize(width: width, height: height)) + } + #endif +} diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Diffusion/ImageGenerationView.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Diffusion/ImageGenerationView.swift new file mode 100644 index 000000000..14c7c33e3 --- /dev/null +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Diffusion/ImageGenerationView.swift @@ -0,0 +1,310 @@ +import SwiftUI +import RunAnywhere + +// MARK: - Image Generation View + +/// Simple view for text-to-image generation using Diffusion models +struct ImageGenerationView: View { + @StateObject private var viewModel = DiffusionViewModel() + @State private var showModelPicker = false + + var body: some View { + NavigationView { + GeometryReader { geometry in + ScrollView { + VStack(spacing: AppSpacing.large) { + // Model status + modelStatusSection + + // Generated Image Display + imageDisplaySection(geometry: geometry) + + // Prompt Input + promptInputSection + + // Quick Prompts + quickPromptsSection + + // Generate Button + generateButtonSection + } + .padding() + } + } + .navigationTitle("Image Generation") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + } + .task { + await viewModel.initialize() + } + .sheet(isPresented: $showModelPicker) { + DiffusionModelPickerView(viewModel: viewModel, isPresented: $showModelPicker) + } + } + + // MARK: - Model Status + + private var modelStatusSection: some View { + HStack { + Circle() + .fill(viewModel.isModelLoaded ? Color.green : Color.orange) + .frame(width: 10, height: 10) + + VStack(alignment: .leading, spacing: 2) { + Text(viewModel.isModelLoaded ? (viewModel.currentModelName ?? "Model loaded") : "No model loaded") + .font(.subheadline) + .foregroundColor(.secondary) + + if viewModel.isModelLoaded && !viewModel.currentBackend.isEmpty { + HStack(spacing: 4) { + Image(systemName: backendIcon) + .font(.caption2) + Text(viewModel.currentBackend) + .font(.caption2) + } + .foregroundColor(backendColor) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(backendColor.opacity(0.15)) + .cornerRadius(4) + } + } + + Spacer() + + Button(viewModel.isModelLoaded ? "Change" : "Load Model") { showModelPicker = true } + .buttonStyle(.bordered) + .tint(AppColors.primaryAccent) + } + .padding() + .background(AppColors.backgroundSecondary) + .cornerRadius(AppSpacing.cornerRadiusLarge) + } + + private var backendIcon: String { + if viewModel.currentBackend.contains("CoreML") { + return "apple.logo" + } else if viewModel.currentBackend.contains("ONNX") { + return "cpu" + } + return "gearshape" + } + + private var backendColor: Color { + if viewModel.currentBackend.contains("CoreML") { + return .blue + } else if viewModel.currentBackend.contains("ONNX") { + return .purple + } + return .secondary + } + + // MARK: - Image Display + + private func imageDisplaySection(geometry: GeometryProxy) -> some View { + let imageSize = min(geometry.size.width - 32, 400.0) + + return ZStack { + RoundedRectangle(cornerRadius: AppSpacing.cornerRadiusLarge) + .fill(AppColors.backgroundSecondary) + .frame(width: imageSize, height: imageSize) + + if let image = viewModel.generatedImage { + image + .resizable() + .scaledToFit() + .frame(width: imageSize, height: imageSize) + .cornerRadius(AppSpacing.cornerRadiusLarge) + } else if viewModel.isGenerating { + VStack(spacing: AppSpacing.medium) { + ProgressView() + .scaleEffect(1.5) + Text(viewModel.statusMessage) + .font(.subheadline) + .foregroundColor(.secondary) + ProgressView(value: viewModel.progress) + .frame(width: 150) + } + } else { + VStack(spacing: AppSpacing.small) { + Image(systemName: "photo.artframe") + .font(.system(size: 50)) + .foregroundColor(.secondary) + Text("Enter a prompt to generate") + .font(.subheadline) + .foregroundColor(.secondary) + } + } + } + } + + // MARK: - Prompt Input + + private var promptInputSection: some View { + VStack(alignment: .leading, spacing: AppSpacing.small) { + Text("Prompt") + .font(.headline) + + TextEditor(text: $viewModel.prompt) + .frame(minHeight: 80) + .padding(8) + .background(AppColors.backgroundSecondary) + .cornerRadius(AppSpacing.cornerRadiusLarge) + .overlay( + RoundedRectangle(cornerRadius: AppSpacing.cornerRadiusLarge) + .stroke(Color.secondary.opacity(0.2), lineWidth: 1) + ) + } + } + + // MARK: - Quick Prompts + + private var quickPromptsSection: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: AppSpacing.small) { + ForEach(DiffusionViewModel.samplePrompts, id: \.self) { prompt in + Button { + viewModel.prompt = prompt + } label: { + Text(prompt.prefix(25) + "...") + .font(.caption) + .lineLimit(1) + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(AppColors.backgroundSecondary) + .cornerRadius(16) + } + .buttonStyle(.plain) + } + } + } + } + + // MARK: - Generate Button + + private var generateButtonSection: some View { + VStack(spacing: AppSpacing.small) { + if viewModel.isGenerating { + Button { + Task { await viewModel.cancelGeneration() } + } label: { + HStack { + Image(systemName: "xmark.circle.fill") + Text("Cancel") + } + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.bordered) + .tint(.red) + } else { + Button { + Task { await viewModel.generateImage() } + } label: { + HStack { + Image(systemName: "wand.and.stars") + Text("Generate") + } + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.borderedProminent) + .tint(AppColors.primaryAccent) + .disabled(!viewModel.canGenerate) + } + + if let error = viewModel.errorMessage { + Text(error) + .font(.caption) + .foregroundColor(.red) + .multilineTextAlignment(.center) + } + } + } +} + +// MARK: - Model Picker + +struct DiffusionModelPickerView: View { + @ObservedObject var viewModel: DiffusionViewModel + @Binding var isPresented: Bool + + var body: some View { + NavigationView { + VStack(spacing: 0) { + if viewModel.availableModels.isEmpty { + VStack(spacing: AppSpacing.large) { + Image(systemName: "photo.artframe") + .font(.system(size: 60)) + .foregroundColor(.secondary) + Text("No Diffusion Models") + .font(.headline) + Text("No image generation models are registered.") + .font(.subheadline) + .foregroundColor(.secondary) + } + .padding() + } else { + List { + ForEach(viewModel.availableModels, id: \.id) { model in + HStack { + VStack(alignment: .leading, spacing: 4) { + Text(model.name).font(.headline) + Text(model.framework.displayName) + .font(.caption) + .foregroundColor(.secondary) + } + Spacer() + if model.isDownloaded { + Button("Load") { + viewModel.selectedModel = model + Task { + await viewModel.loadSelectedModel() + if viewModel.isModelLoaded { isPresented = false } + } + } + .buttonStyle(.borderedProminent) + .disabled(viewModel.isDownloading) + } else { + Button("Download") { + viewModel.selectedModel = model + Task { await viewModel.downloadModel(model) } + } + .buttonStyle(.bordered) + .disabled(viewModel.isDownloading) + } + } + .padding(.vertical, 4) + } + } + } + + if viewModel.isDownloading { + VStack(spacing: AppSpacing.small) { + ProgressView(value: viewModel.downloadProgress) + Text(viewModel.downloadStatus) + .font(.caption) + .foregroundColor(.secondary) + } + .padding() + .background(AppColors.backgroundSecondary) + } + } + .navigationTitle("Diffusion Models") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { isPresented = false } + } + } + } + } +} + +#Preview { + ImageGenerationView() +} diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Models/ModelSelectionSheet.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Models/ModelSelectionSheet.swift index 25b609c12..791779b37 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Models/ModelSelectionSheet.swift +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Models/ModelSelectionSheet.swift @@ -16,6 +16,7 @@ enum ModelSelectionContext { case stt // Speech-to-Text - show STT frameworks (ONNX STT) case tts // Text-to-Speech - show TTS frameworks (ONNX TTS/Piper, System TTS) case voice // Voice Assistant - show all voice-related (LLM + STT + TTS) + case vlm // Vision Language Model - show VLM frameworks var title: String { switch self { @@ -23,19 +24,22 @@ enum ModelSelectionContext { case .stt: return "Select STT Model" case .tts: return "Select TTS Model" case .voice: return "Select Model" + case .vlm: return "Select Vision Model" } } var relevantCategories: Set { switch self { case .llm: - return [.language, .multimodal] + return [.language] case .stt: return [.speechRecognition] case .tts: return [.speechSynthesis] case .voice: - return [.language, .multimodal, .speechRecognition, .speechSynthesis] + return [.language, .speechRecognition, .speechSynthesis] + case .vlm: + return [.multimodal, .vision] } } } @@ -242,6 +246,7 @@ extension ModelSelectionSheet { case .stt: try await RunAnywhere.loadSTTModel(model.id) case .tts: try await RunAnywhere.loadTTSModel(model.id) case .voice: try await loadModelForVoiceContext(model) + case .vlm: try await RunAnywhere.loadVLMModel(model) } } @@ -258,14 +263,24 @@ extension ModelSelectionSheet { (context == .voice && [.language, .multimodal].contains(model.category)) if isLLM { - await viewModel.setCurrentModel(model) await MainActor.run { + viewModel.setCurrentModel(model) NotificationCenter.default.post( name: Notification.Name("ModelLoaded"), object: model ) } } + + if context == .vlm { + await MainActor.run { + NotificationCenter.default.post( + name: Notification.Name("VLMModelLoaded"), + object: model + ) + } + } + await onModelSelected(model) } } diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Models/ModelStatusComponents.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Models/ModelStatusComponents.swift index 5bb479785..4f9c0be90 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Models/ModelStatusComponents.swift +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Models/ModelStatusComponents.swift @@ -314,6 +314,7 @@ struct ModelRequiredOverlay: View { case .stt: return "waveform" case .tts: return "speaker.wave.2.fill" case .voice: return "mic.circle.fill" + case .vlm: return "camera.viewfinder" } } @@ -323,6 +324,7 @@ struct ModelRequiredOverlay: View { case .stt: return .green case .tts: return AppColors.primaryPurple case .voice: return AppColors.primaryAccent + case .vlm: return .orange } } @@ -332,6 +334,7 @@ struct ModelRequiredOverlay: View { case .stt: return "Voice to Text" case .tts: return "Read Aloud" case .voice: return "Voice Assistant" + case .vlm: return "Vision AI" } } @@ -341,6 +344,7 @@ struct ModelRequiredOverlay: View { case .stt: return "Transcribe your speech to text with powerful on-device voice recognition." case .tts: return "Have any text read aloud with natural-sounding voices." case .voice: return "Talk naturally with your AI assistant. Let's set up the components together." + case .vlm: return "Point your camera at anything and get AI-powered descriptions in real-time." } } } diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Settings/CombinedSettingsView.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Settings/CombinedSettingsView.swift index 26c0f17f9..9c1787eae 100644 --- a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Settings/CombinedSettingsView.swift +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Settings/CombinedSettingsView.swift @@ -13,13 +13,14 @@ import Combine struct CombinedSettingsView: View { // ViewModel - all business logic is here @StateObject private var viewModel = SettingsViewModel() + @StateObject private var toolViewModel = ToolSettingsViewModel.shared var body: some View { Group { #if os(macOS) - MacOSSettingsContent(viewModel: viewModel) + MacOSSettingsContent(viewModel: viewModel, toolViewModel: toolViewModel) #else - IOSSettingsContent(viewModel: viewModel) + IOSSettingsContent(viewModel: viewModel, toolViewModel: toolViewModel) #endif } .sheet(isPresented: $viewModel.showApiKeyEntry) { @@ -27,6 +28,7 @@ struct CombinedSettingsView: View { } .task { await viewModel.loadStorageData() + await toolViewModel.refreshRegisteredTools() } .alert("Error", isPresented: .constant(viewModel.errorMessage != nil)) { Button("OK") { @@ -51,6 +53,7 @@ struct CombinedSettingsView: View { private struct IOSSettingsContent: View { @ObservedObject var viewModel: SettingsViewModel + @ObservedObject var toolViewModel: ToolSettingsViewModel var body: some View { Form { @@ -71,6 +74,9 @@ private struct IOSSettingsContent: View { ) } + // Tool Calling Settings + ToolSettingsSection(viewModel: toolViewModel) + // API Configuration (for testing custom backend) Section { Button( @@ -126,74 +132,6 @@ private struct IOSSettingsContent: View { .font(AppTypography.caption) } - // Storage Overview Section - Section { - StorageOverviewRows(viewModel: viewModel) - } header: { - HStack { - Text("Storage Overview") - Spacer() - Button("Refresh") { - Task { - await viewModel.refreshStorageData() - } - } - .font(AppTypography.caption) - } - } - - // Downloaded Models Section - Section("Downloaded Models") { - if viewModel.storedModels.isEmpty { - Text("No models downloaded yet") - .foregroundColor(AppColors.textSecondary) - .font(AppTypography.caption) - } else { - ForEach(viewModel.storedModels, id: \.id) { model in - StoredModelRow(model: model) { - await viewModel.deleteModel(model) - } - } - } - } - - // Storage Management - Section("Storage Management") { - Button( - action: { - Task { - await viewModel.clearCache() - } - }, - label: { - HStack { - Image(systemName: "trash") - .foregroundColor(AppColors.primaryRed) - Text("Clear Cache") - .foregroundColor(AppColors.primaryRed) - Spacer() - } - } - ) - - Button( - action: { - Task { - await viewModel.cleanTempFiles() - } - }, - label: { - HStack { - Image(systemName: "trash") - .foregroundColor(AppColors.primaryOrange) - Text("Clean Temporary Files") - .foregroundColor(AppColors.primaryOrange) - Spacer() - } - } - ) - } - // Logging Configuration Section("Logging Configuration") { Toggle("Log Analytics Locally", isOn: $viewModel.analyticsLogToLocal) @@ -230,6 +168,7 @@ private struct IOSSettingsContent: View { private struct MacOSSettingsContent: View { @ObservedObject var viewModel: SettingsViewModel + @ObservedObject var toolViewModel: ToolSettingsViewModel var body: some View { ScrollView { @@ -239,10 +178,8 @@ private struct MacOSSettingsContent: View { .padding(.bottom, AppSpacing.medium) GenerationSettingsCard(viewModel: viewModel) + ToolSettingsCard(viewModel: toolViewModel) APIConfigurationCard(viewModel: viewModel) - StorageCard(viewModel: viewModel) - DownloadedModelsCard(viewModel: viewModel) - StorageManagementCard(viewModel: viewModel) LoggingConfigurationCard(viewModel: viewModel) AboutCard() @@ -728,6 +665,12 @@ private struct StoredModelRow: View { @State private var showingDeleteConfirmation = false @State private var isDeleting = false + private var isDeletable: Bool { + // Platform models (built-in) can't be deleted + guard let framework = model.framework else { return false } + return framework != .foundationModels && framework != .systemTTS + } + var body: some View { VStack(alignment: .leading, spacing: AppSpacing.smallMedium) { HStack { @@ -757,20 +700,23 @@ private struct StoredModelRow: View { .tint(AppColors.primaryAccent) .controlSize(.mini) - Button( - action: { - showingDeleteConfirmation = true - }, - label: { - Image(systemName: "trash") - .foregroundColor(AppColors.primaryRed) - } - ) - .font(AppTypography.caption2) - .buttonStyle(.bordered) - .tint(AppColors.primaryRed) - .controlSize(.mini) - .disabled(isDeleting) + // ONLY show delete button if deletable + if isDeletable { + Button( + action: { + showingDeleteConfirmation = true + }, + label: { + Image(systemName: "trash") + .foregroundColor(AppColors.primaryRed) + } + ) + .font(AppTypography.caption2) + .buttonStyle(.bordered) + .tint(AppColors.primaryRed) + .controlSize(.mini) + .disabled(isDeleting) + } } } } diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Settings/ToolSettingsView.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Settings/ToolSettingsView.swift new file mode 100644 index 000000000..dec41a8ef --- /dev/null +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Settings/ToolSettingsView.swift @@ -0,0 +1,403 @@ +// +// ToolSettingsView.swift +// RunAnywhereAI +// +// Tool registration and management settings +// + +import SwiftUI +import RunAnywhere + +// MARK: - Tool Settings View Model + +@MainActor +class ToolSettingsViewModel: ObservableObject { + static let shared = ToolSettingsViewModel() + + @Published var registeredTools: [ToolDefinition] = [] + @Published var toolCallingEnabled: Bool = false { + didSet { + UserDefaults.standard.set(toolCallingEnabled, forKey: "toolCallingEnabled") + } + } + + // Built-in demo tools with REAL API implementations + private var demoTools: [(definition: ToolDefinition, executor: ToolExecutor)] { + [ + // Weather Tool - Uses Open-Meteo API (free, no API key required) + ( + definition: ToolDefinition( + name: "get_weather", + description: "Gets the current weather for a given location using Open-Meteo API", + parameters: [ + ToolParameter( + name: "location", + type: .string, + description: "City name (e.g., 'San Francisco', 'London', 'Tokyo')" + ) + ], + category: "Utility" + ), + executor: { args in + try await WeatherService.fetchWeather(for: args["location"]?.stringValue ?? "San Francisco") + } + ), + // Time Tool - Real system time with timezone + ( + definition: ToolDefinition( + name: "get_current_time", + description: "Gets the current date, time, and timezone information", + parameters: [], + category: "Utility" + ), + executor: { _ in + let now = Date() + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .full + dateFormatter.timeStyle = .medium + + let timeZone = TimeZone.current + let timeFormatter = DateFormatter() + timeFormatter.dateFormat = "HH:mm:ss" + + return [ + "datetime": .string(dateFormatter.string(from: now)), + "time": .string(timeFormatter.string(from: now)), + "timestamp": .string(ISO8601DateFormatter().string(from: now)), + "timezone": .string(timeZone.identifier), + "utc_offset": .string(timeZone.abbreviation() ?? "UTC") + ] + } + ), + // Calculator Tool - Real math evaluation + ( + definition: ToolDefinition( + name: "calculate", + description: "Performs math calculations. Supports +, -, *, /, and parentheses", + parameters: [ + ToolParameter( + name: "expression", + type: .string, + description: "Math expression (e.g., '2 + 2 * 3', '(10 + 5) / 3')" + ) + ], + category: "Utility" + ), + executor: { args in + let expression = args["expression"]?.stringValue ?? args["input"]?.stringValue ?? "0" + // Clean the expression - remove any non-math characters + let cleanedExpression = expression + .replacingOccurrences(of: "=", with: "") + .replacingOccurrences(of: "x", with: "*") + .replacingOccurrences(of: "ร—", with: "*") + .replacingOccurrences(of: "รท", with: "/") + .trimmingCharacters(in: .whitespacesAndNewlines) + + do { + let exp = NSExpression(format: cleanedExpression) + if let result = exp.expressionValue(with: nil, context: nil) as? NSNumber { + return [ + "result": .number(result.doubleValue), + "expression": .string(expression) + ] + } + } catch { + // Fall through to error + } + return [ + "error": .string("Could not evaluate expression: \(expression)"), + "expression": .string(expression) + ] + } + ) + ] + } + + init() { + toolCallingEnabled = UserDefaults.standard.bool(forKey: "toolCallingEnabled") + Task { + await refreshRegisteredTools() + } + } + + func refreshRegisteredTools() async { + registeredTools = await RunAnywhere.getRegisteredTools() + } + + func registerDemoTools() async { + for tool in demoTools { + await RunAnywhere.registerTool(tool.definition, executor: tool.executor) + } + await refreshRegisteredTools() + } + + func clearAllTools() async { + await RunAnywhere.clearTools() + await refreshRegisteredTools() + } +} + +// MARK: - Tool Settings Section (iOS) + +struct ToolSettingsSection: View { + @ObservedObject var viewModel: ToolSettingsViewModel + + var body: some View { + Section { + Toggle("Enable Tool Calling", isOn: $viewModel.toolCallingEnabled) + + if viewModel.toolCallingEnabled { + HStack { + Text("Registered Tools") + Spacer() + Text("\(viewModel.registeredTools.count)") + .foregroundColor(AppColors.textSecondary) + } + + if viewModel.registeredTools.isEmpty { + Button("Add Demo Tools") { + Task { + await viewModel.registerDemoTools() + } + } + .foregroundColor(AppColors.primaryAccent) + } else { + ForEach(viewModel.registeredTools, id: \.name) { tool in + ToolRow(tool: tool) + } + + Button("Clear All Tools") { + Task { + await viewModel.clearAllTools() + } + } + .foregroundColor(AppColors.primaryRed) + } + } + } header: { + Text("Tool Calling") + } footer: { + Text("Allow the LLM to use registered tools to perform actions like getting weather, time, or calculations.") + .font(AppTypography.caption) + } + } +} + +// MARK: - Tool Settings Card (macOS) + +struct ToolSettingsCard: View { + @ObservedObject var viewModel: ToolSettingsViewModel + + var body: some View { + VStack(alignment: .leading, spacing: AppSpacing.xLarge) { + Text("Tool Calling") + .font(AppTypography.headline) + .foregroundColor(AppColors.textSecondary) + + VStack(alignment: .leading, spacing: AppSpacing.large) { + HStack { + Text("Enable Tool Calling") + .frame(width: 150, alignment: .leading) + Toggle("", isOn: $viewModel.toolCallingEnabled) + Spacer() + Text(viewModel.toolCallingEnabled ? "Enabled" : "Disabled") + .font(AppTypography.caption) + .foregroundColor(viewModel.toolCallingEnabled ? AppColors.statusGreen : AppColors.textSecondary) + } + + if viewModel.toolCallingEnabled { + Divider() + + HStack { + Text("Registered Tools") + Spacer() + Text("\(viewModel.registeredTools.count)") + .font(AppTypography.monospaced) + .foregroundColor(AppColors.primaryAccent) + } + + if viewModel.registeredTools.isEmpty { + Button("Add Demo Tools") { + Task { + await viewModel.registerDemoTools() + } + } + .buttonStyle(.bordered) + .tint(AppColors.primaryAccent) + } else { + ForEach(viewModel.registeredTools, id: \.name) { tool in + ToolRow(tool: tool) + } + + Button("Clear All Tools") { + Task { + await viewModel.clearAllTools() + } + } + .buttonStyle(.bordered) + .tint(AppColors.primaryRed) + } + } + + Text("Allow the LLM to use registered tools to perform actions like getting weather, time, or calculations.") + .font(AppTypography.caption) + .foregroundColor(AppColors.textSecondary) + } + .padding(AppSpacing.large) + .background(AppColors.backgroundSecondary) + .cornerRadius(AppSpacing.cornerRadiusLarge) + } + } +} + +// MARK: - Tool Row + +struct ToolRow: View { + let tool: ToolDefinition + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + HStack { + Image(systemName: "wrench.and.screwdriver") + .font(.system(size: 12)) + .foregroundColor(AppColors.primaryAccent) + Text(tool.name) + .font(AppTypography.subheadlineMedium) + } + Text(tool.description) + .font(AppTypography.caption) + .foregroundColor(AppColors.textSecondary) + .lineLimit(2) + + if !tool.parameters.isEmpty { + HStack(spacing: 4) { + Text("Params:") + .font(AppTypography.caption2) + .foregroundColor(AppColors.textSecondary) + ForEach(tool.parameters, id: \.name) { param in + Text(param.name) + .font(AppTypography.caption2) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(AppColors.backgroundTertiary) + .cornerRadius(4) + } + } + } + } + .padding(.vertical, 4) + } +} + +// MARK: - Weather Service (Open-Meteo API) + +/// Real weather service using Open-Meteo API (free, no API key required) +enum WeatherService { + // Open-Meteo Geocoding API + private static let geocodingURL = "https://geocoding-api.open-meteo.com/v1/search" + // Open-Meteo Weather API + private static let weatherURL = "https://api.open-meteo.com/v1/forecast" + + /// Fetch real weather data for a location + static func fetchWeather(for location: String) async throws -> [String: ToolValue] { + // Step 1: Geocode the location to get coordinates + guard let coordinates = try await geocodeLocation(location) else { + return [ + "error": .string("Could not find location: \(location)"), + "location": .string(location) + ] + } + + // Step 2: Fetch weather for coordinates + return try await fetchWeatherForCoordinates( + latitude: coordinates.latitude, + longitude: coordinates.longitude, + locationName: coordinates.name + ) + } + + private static func geocodeLocation(_ location: String) async throws -> (latitude: Double, longitude: Double, name: String)? { + guard let encodedLocation = location.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let url = URL(string: "\(geocodingURL)?name=\(encodedLocation)&count=1&language=en&format=json") else { + return nil + } + + let (data, _) = try await URLSession.shared.data(from: url) + + guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let results = json["results"] as? [[String: Any]], + let first = results.first, + let latitude = first["latitude"] as? Double, + let longitude = first["longitude"] as? Double else { + return nil + } + + let name = first["name"] as? String ?? location + return (latitude, longitude, name) + } + + private static func fetchWeatherForCoordinates( + latitude: Double, + longitude: Double, + locationName: String + ) async throws -> [String: ToolValue] { + let urlString = "\(weatherURL)?latitude=\(latitude)&longitude=\(longitude)" + + "¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m" + + "&temperature_unit=fahrenheit&wind_speed_unit=mph" + + guard let url = URL(string: urlString) else { + return ["error": .string("Invalid weather API URL")] + } + + let (data, _) = try await URLSession.shared.data(from: url) + + guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let current = json["current"] as? [String: Any] else { + return ["error": .string("Could not parse weather data")] + } + + let temperature = current["temperature_2m"] as? Double ?? 0 + let humidity = current["relative_humidity_2m"] as? Double ?? 0 + let windSpeed = current["wind_speed_10m"] as? Double ?? 0 + let weatherCode = current["weather_code"] as? Int ?? 0 + + return [ + "location": .string(locationName), + "temperature": .number(temperature), + "unit": .string("fahrenheit"), + "humidity": .number(humidity), + "wind_speed_mph": .number(windSpeed), + "condition": .string(weatherCodeToCondition(weatherCode)) + ] + } + + /// Convert WMO weather code to human-readable condition + private static func weatherCodeToCondition(_ code: Int) -> String { + switch code { + case 0: return "Clear sky" + case 1: return "Mainly clear" + case 2: return "Partly cloudy" + case 3: return "Overcast" + case 45, 48: return "Foggy" + case 51, 53, 55: return "Drizzle" + case 56, 57: return "Freezing drizzle" + case 61, 63, 65: return "Rain" + case 66, 67: return "Freezing rain" + case 71, 73, 75: return "Snow" + case 77: return "Snow grains" + case 80, 81, 82: return "Rain showers" + case 85, 86: return "Snow showers" + case 95: return "Thunderstorm" + case 96, 99: return "Thunderstorm with hail" + default: return "Unknown" + } + } +} + +#Preview { + NavigationStack { + Form { + ToolSettingsSection(viewModel: ToolSettingsViewModel.shared) + } + } +} diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Vision/VLMCameraView.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Vision/VLMCameraView.swift new file mode 100644 index 000000000..3a730913f --- /dev/null +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Vision/VLMCameraView.swift @@ -0,0 +1,303 @@ +// +// VLMCameraView.swift +// RunAnywhereAI +// +// Simple camera view for Vision Language Model +// + +import SwiftUI +import AVFoundation +import RunAnywhere +import PhotosUI + +// MARK: - VLM Camera View + +struct VLMCameraView: View { + @State private var viewModel = VLMViewModel() + @State private var showingModelSelection = false + @State private var showingPhotos = false + @State private var selectedPhoto: PhotosPickerItem? + + var body: some View { + ZStack { + Color.black.ignoresSafeArea() + + if viewModel.isModelLoaded { + mainContent + } else { + modelRequiredContent + } + } + .navigationTitle("Vision AI") + .navigationBarTitleDisplayMode(.inline) + .toolbar { toolbarContent } + .toolbarBackground(.black, for: .navigationBar) + .toolbarColorScheme(.dark, for: .navigationBar) + .sheet(isPresented: $showingModelSelection) { + ModelSelectionSheet(context: .vlm) { _ in + await viewModel.checkModelStatus() + setupCameraIfNeeded() + } + } + .photosPicker(isPresented: $showingPhotos, selection: $selectedPhoto, matching: .images) + .onChange(of: selectedPhoto) { _, item in + Task { await handlePhoto(item) } + } + .onAppear { setupCameraIfNeeded() } + .onDisappear { + viewModel.stopAutoStreaming() + viewModel.stopCamera() + } + } + + // MARK: - Main Content + + private var mainContent: some View { + VStack(spacing: 0) { + // Camera preview + cameraPreview + + // Description + descriptionPanel + + // Controls + controlBar + } + } + + private var cameraPreview: some View { + GeometryReader { _ in + ZStack { + if viewModel.isCameraAuthorized, let session = viewModel.captureSession { + CameraPreview(session: session) + } else { + cameraPermissionView + } + + // Processing overlay + if viewModel.isProcessing { + VStack { + Spacer() + HStack(spacing: 8) { + ProgressView().tint(.white) + Text("Analyzing...").font(.caption).foregroundColor(.white) + } + .padding(12) + .background(.ultraThinMaterial) + .cornerRadius(20) + .padding(.bottom, 16) + } + } + } + } + .frame(height: UIScreen.main.bounds.height * 0.45) + } + + private var cameraPermissionView: some View { + VStack(spacing: 12) { + Image(systemName: "camera.fill").font(.largeTitle).foregroundColor(.gray) + Text("Camera Access Required").font(.headline).foregroundColor(.white) + Button("Open Settings") { + if let url = URL(string: UIApplication.openSettingsURLString) { + UIApplication.shared.open(url) + } + } + .buttonStyle(.bordered) + } + } + + private var descriptionPanel: some View { + VStack(alignment: .leading, spacing: 12) { + HStack { + HStack(spacing: 6) { + Text("Description") + .font(.headline) + .fontWeight(.semibold) + .foregroundColor(.primary) + if viewModel.isAutoStreamingEnabled { + HStack(spacing: 4) { + Circle() + .fill(Color.green) + .frame(width: 8, height: 8) + Text("LIVE") + .font(.caption2) + .fontWeight(.bold) + .foregroundColor(.green) + } + } + } + Spacer() + if !viewModel.currentDescription.isEmpty { + Button { UIPasteboard.general.string = viewModel.currentDescription } label: { + Image(systemName: "doc.on.doc").font(.subheadline) + }.foregroundColor(.secondary) + } + } + + ScrollView { + Text(viewModel.currentDescription.isEmpty + ? "Tap the button to describe what your camera sees" + : viewModel.currentDescription) + .font(.system(.body, design: .default)) + .fontWeight(.regular) + .foregroundColor(viewModel.currentDescription.isEmpty ? .secondary : .primary) + .lineSpacing(4) + .frame(maxWidth: .infinity, alignment: .leading) + } + .frame(maxHeight: 150) + + if let error = viewModel.error { + Text(error.localizedDescription) + .font(.caption) + .foregroundColor(.red) + } + } + .padding(.horizontal, 16) + .padding(.vertical, 14) + .background(Color(.systemBackground)) + } + + private var controlBar: some View { + HStack(spacing: 32) { + // Photos button + Button { showingPhotos = true } label: { + VStack(spacing: 4) { + Image(systemName: "photo").font(.title2) + Text("Photos").font(.caption2) + } + .foregroundColor(.white) + } + + // Main action button - tap for single, or shows streaming state + Button { + if viewModel.isAutoStreamingEnabled { + viewModel.stopAutoStreaming() + } else { + Task { await viewModel.describeCurrentFrame() } + } + } label: { + ZStack { + Circle() + .fill(buttonColor) + .frame(width: 64, height: 64) + if viewModel.isProcessing { + ProgressView().tint(.white) + } else if viewModel.isAutoStreamingEnabled { + Image(systemName: "stop.fill").font(.title2).foregroundColor(.white) + } else { + Image(systemName: "sparkles").font(.title).foregroundColor(.white) + } + } + } + .disabled(viewModel.isProcessing && !viewModel.isAutoStreamingEnabled) + + // Auto-stream toggle + Button { viewModel.toggleAutoStreaming() } label: { + VStack(spacing: 4) { + Image(systemName: viewModel.isAutoStreamingEnabled ? "livephoto" : "livephoto.slash") + .font(.title2) + .symbolEffect(.pulse, isActive: viewModel.isAutoStreamingEnabled) + Text("Live").font(.caption2) + } + .foregroundColor(viewModel.isAutoStreamingEnabled ? .green : .white) + } + + // Model button + Button { showingModelSelection = true } label: { + VStack(spacing: 4) { + Image(systemName: "cube").font(.title2) + Text("Model").font(.caption2) + } + .foregroundColor(.white) + } + } + .padding(.vertical, 16) + .background(Color.black) + } + + // MARK: - Model Required + + private var modelRequiredContent: some View { + VStack(spacing: 20) { + Spacer() + Image(systemName: "camera.viewfinder").font(.system(size: 60)).foregroundColor(.orange) + Text("Vision AI").font(.title).fontWeight(.bold).foregroundColor(.white) + Text("Select a vision model to describe images").foregroundColor(.gray) + Button { showingModelSelection = true } label: { + HStack { Image(systemName: "sparkles"); Text("Select Model") } + .font(.headline).frame(width: 200).padding(.vertical, 12) + } + .buttonStyle(.borderedProminent).tint(.orange) + Spacer() + } + } + + // MARK: - Toolbar + + @ToolbarContentBuilder + private var toolbarContent: some ToolbarContent { + ToolbarItem(placement: .navigationBarTrailing) { + if let name = viewModel.loadedModelName { + Text(name).font(.caption).foregroundColor(.gray) + } + } + } + + // MARK: - Helpers + + private var buttonColor: Color { + if viewModel.isAutoStreamingEnabled { + return .red + } else if viewModel.isProcessing { + return .gray + } else { + return .orange + } + } + + private func setupCameraIfNeeded() { + Task { + await viewModel.checkCameraAuthorization() + if viewModel.isCameraAuthorized && viewModel.captureSession == nil { + viewModel.setupCamera() + viewModel.startCamera() + } + } + } + + private func handlePhoto(_ item: PhotosPickerItem?) async { + guard let item = item, + let data = try? await item.loadTransferable(type: Data.self), + let image = UIImage(data: data) else { return } + await viewModel.describeImage(image) + } +} + +// MARK: - Camera Preview + +struct CameraPreview: UIViewRepresentable { + let session: AVCaptureSession + + func makeUIView(context: Context) -> PreviewView { + let view = PreviewView() + view.backgroundColor = .black + view.previewLayer.session = session + view.previewLayer.videoGravity = .resizeAspectFill + return view + } + + func updateUIView(_ view: PreviewView, context: Context) { + // PreviewView handles its own layout via layoutSubviews + } + + // Custom UIView that properly sizes AVCaptureVideoPreviewLayer + class PreviewView: UIView { + override class var layerClass: AnyClass { + AVCaptureVideoPreviewLayer.self + } + + var previewLayer: AVCaptureVideoPreviewLayer { + layer as! AVCaptureVideoPreviewLayer // swiftlint:disable:this force_cast + } + } +} diff --git a/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Vision/VLMViewModel.swift b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Vision/VLMViewModel.swift new file mode 100644 index 000000000..32e65d028 --- /dev/null +++ b/examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Vision/VLMViewModel.swift @@ -0,0 +1,262 @@ +// +// VLMViewModel.swift +// RunAnywhereAI +// +// Simple ViewModel for Vision Language Model camera functionality +// + +import Foundation +import SwiftUI +import RunAnywhere +@preconcurrency import AVFoundation +import os.log + +#if canImport(UIKit) +import UIKit +#endif + +// MARK: - VLM View Model + +@MainActor +@Observable +final class VLMViewModel: NSObject { + // MARK: - State + + private(set) var isModelLoaded = false + private(set) var loadedModelName: String? + private(set) var isProcessing = false + private(set) var currentDescription = "" + private(set) var error: Error? + private(set) var isCameraAuthorized = false + + // Auto-streaming mode + var isAutoStreamingEnabled = false + // nonisolated(unsafe) so deinit can cancel the task (deinit is nonisolated in Swift 6) + nonisolated(unsafe) private var autoStreamTask: Task? + private static let autoStreamInterval: TimeInterval = 2.5 // seconds between auto-captures + + // Camera + private(set) var captureSession: AVCaptureSession? + private var currentFrame: CVPixelBuffer? + + private let logger = Logger(subsystem: "com.runanywhere.RunAnywhereAI", category: "VLM") + + // MARK: - Init + + override init() { + super.init() + NotificationCenter.default.addObserver( + self, + selector: #selector(vlmModelLoaded(_:)), + name: Notification.Name("VLMModelLoaded"), + object: nil + ) + Task { await checkModelStatus() } + } + + deinit { + autoStreamTask?.cancel() + NotificationCenter.default.removeObserver(self) + // Note: Camera cleanup is handled by onDisappear in VLMCameraView + } + + // MARK: - Model + + func checkModelStatus() async { + isModelLoaded = await RunAnywhere.isVLMModelLoaded + } + + @objc private func vlmModelLoaded(_ notification: Notification) { + Task { + if let model = notification.object as? ModelInfo { + isModelLoaded = true + loadedModelName = model.name + } else { + await checkModelStatus() + } + } + } + + // MARK: - Camera + + func checkCameraAuthorization() async { + let status = AVCaptureDevice.authorizationStatus(for: .video) + switch status { + case .authorized: + isCameraAuthorized = true + case .notDetermined: + isCameraAuthorized = await AVCaptureDevice.requestAccess(for: .video) + default: + isCameraAuthorized = false + } + } + + func setupCamera() { + guard isCameraAuthorized else { return } + + let session = AVCaptureSession() + session.sessionPreset = .medium + + guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back), + let input = try? AVCaptureDeviceInput(device: device) else { return } + + if session.canAddInput(input) { session.addInput(input) } + + let output = AVCaptureVideoDataOutput() + // CRITICAL: Request BGRA format explicitly! + // Default iOS camera output is YUV, which our pixel conversion code doesn't handle. + // The SDK's VLMTypes.swift assumes BGRA (offset+2=R, offset+1=G, offset+0=B) + output.videoSettings = [ + kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA + ] + output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "camera.queue")) + output.alwaysDiscardsLateVideoFrames = true + + if session.canAddOutput(output) { session.addOutput(output) } + + captureSession = session + } + + func startCamera() { + guard let session = captureSession, !session.isRunning else { return } + DispatchQueue.global(qos: .userInitiated).async { session.startRunning() } + } + + func stopCamera() { + guard let session = captureSession, session.isRunning else { return } + DispatchQueue.global(qos: .userInitiated).async { session.stopRunning() } + } + + // MARK: - Describe + + func describeCurrentFrame() async { + guard let pixelBuffer = currentFrame, !isProcessing else { return } + + isProcessing = true + error = nil + currentDescription = "" + + do { + let image = VLMImage(pixelBuffer: pixelBuffer) + let result = try await RunAnywhere.processImageStream( + image, + prompt: "Describe what you see briefly.", + maxTokens: 200 + ) + + for try await token in result.stream { + currentDescription += token + } + } catch { + self.error = error + logger.error("VLM error: \(error.localizedDescription)") + } + + isProcessing = false + } + + #if canImport(UIKit) + func describeImage(_ uiImage: UIImage) async { + isProcessing = true + error = nil + currentDescription = "" + + do { + let image = VLMImage(image: uiImage) + let result = try await RunAnywhere.processImageStream( + image, + prompt: "Describe this image in detail.", + maxTokens: 300 + ) + + for try await token in result.stream { + currentDescription += token + } + } catch { + self.error = error + } + + isProcessing = false + } + #endif + + func cancel() { + Task { await RunAnywhere.cancelVLMGeneration() } + } + + // MARK: - Auto Streaming + + func toggleAutoStreaming() { + isAutoStreamingEnabled.toggle() + if isAutoStreamingEnabled { + startAutoStreaming() + } else { + stopAutoStreaming() + } + } + + func startAutoStreaming() { + guard autoStreamTask == nil else { return } + + autoStreamTask = Task { + while !Task.isCancelled && isAutoStreamingEnabled { + // Wait for any current processing to finish + while isProcessing { + try? await Task.sleep(nanoseconds: 100_000_000) // 0.1s + if Task.isCancelled { return } + } + + // Capture and describe + await describeCurrentFrameForAutoStream() + + // Wait before next capture + try? await Task.sleep(nanoseconds: UInt64(Self.autoStreamInterval * 1_000_000_000)) + } + } + } + + func stopAutoStreaming() { + autoStreamTask?.cancel() + autoStreamTask = nil + isAutoStreamingEnabled = false + } + + private func describeCurrentFrameForAutoStream() async { + guard let pixelBuffer = currentFrame, !isProcessing else { return } + + isProcessing = true + error = nil + + // For auto-stream, we replace the description instead of clearing first + // This gives a smoother visual transition + var newDescription = "" + + do { + let image = VLMImage(pixelBuffer: pixelBuffer) + let result = try await RunAnywhere.processImageStream( + image, + prompt: "Describe what you see in one sentence.", + maxTokens: 100 + ) + + for try await token in result.stream { + newDescription += token + currentDescription = newDescription + } + } catch { + // Don't show errors during auto-stream, just log + logger.error("Auto-stream VLM error: \(error.localizedDescription)") + } + + isProcessing = false + } +} + +// MARK: - Camera Delegate + +extension VLMViewModel: AVCaptureVideoDataOutputSampleBufferDelegate { + nonisolated func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } + Task { @MainActor in self.currentFrame = pixelBuffer } + } +} diff --git a/examples/react-native/RunAnywhereAI/.gitignore b/examples/react-native/RunAnywhereAI/.gitignore index f481834b8..9c6bb2780 100644 --- a/examples/react-native/RunAnywhereAI/.gitignore +++ b/examples/react-native/RunAnywhereAI/.gitignore @@ -1,6 +1,11 @@ # Dependencies node_modules/ +# Yarn +.yarn/ +.pnp.cjs +.pnp.loader.mjs + # React Native .expo/ *.jks @@ -33,10 +38,12 @@ ios/.xcode.env.local # Android android/.gradle/ android/app/build/ +android/app/.cxx/ android/build/ android/local.properties android/*.iml android/.idea/ +android/app/.idea/ # Testing coverage/ diff --git a/examples/react-native/RunAnywhereAI/.yarn/install-state.gz b/examples/react-native/RunAnywhereAI/.yarn/install-state.gz deleted file mode 100644 index e204cd28d..000000000 Binary files a/examples/react-native/RunAnywhereAI/.yarn/install-state.gz and /dev/null differ diff --git a/examples/react-native/RunAnywhereAI/App.tsx b/examples/react-native/RunAnywhereAI/App.tsx index 9da41b7df..bcdaf0142 100644 --- a/examples/react-native/RunAnywhereAI/App.tsx +++ b/examples/react-native/RunAnywhereAI/App.tsx @@ -5,8 +5,9 @@ * * Architecture Pattern: * - Two-phase SDK initialization (matching iOS pattern) - * - Module registration with models (LlamaCPP, ONNX, FluidAudio) - * - Tab-based navigation with 5 tabs (Chat, STT, TTS, Voice, Settings) + * - Module registration with models (LlamaCPP, ONNX) + * - Tab-based navigation with 5 tabs (Chat, Transcribe, Speak, Voice, Settings) + * - Tool calling settings are in Settings tab (matching iOS) * * Reference: iOS examples/ios/RunAnywhereAI/RunAnywhereAI/App/RunAnywhereAIApp.swift */ @@ -18,6 +19,7 @@ import { StyleSheet, ActivityIndicator, TouchableOpacity, + Platform, } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; import Icon from 'react-native-vector-icons/Ionicons'; @@ -34,7 +36,7 @@ import { } from './src/theme/spacing'; // Import RunAnywhere SDK (Multi-Package Architecture) -import { RunAnywhere, SDKEnvironment, ModelCategory } from '@runanywhere/core'; +import { RunAnywhere, SDKEnvironment, ModelCategory, LLMFramework } from '@runanywhere/core'; import { LlamaCPP } from '@runanywhere/llamacpp'; import { ONNX, ModelArtifactType } from '@runanywhere/onnx'; import { getStoredApiKey, getStoredBaseURL, hasCustomConfiguration } from './src/screens/SettingsScreen'; @@ -132,6 +134,13 @@ const App: React.FC = () => { url: 'https://huggingface.co/Triangle104/Qwen2.5-0.5B-Instruct-Q6_K-GGUF/resolve/main/qwen2.5-0.5b-instruct-q6_k.gguf', memoryRequirement: 600_000_000, }); + // Llama 3.2 3B - Ideal for tool calling on mobile (3B params, ~1.8GB) + await LlamaCPP.addModel({ + id: 'llama-3.2-3b-instruct-q4_k_m', + name: 'Llama 3.2 3B Instruct Q4_K_M (Tool Calling)', + url: 'https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-Q4_K_M.gguf', + memoryRequirement: 2_000_000_000, + }); await LlamaCPP.addModel({ id: 'lfm2-350m-q4_k_m', name: 'LiquidAI LFM2 350M Q4_K_M', @@ -144,6 +153,28 @@ const App: React.FC = () => { url: 'https://huggingface.co/LiquidAI/LFM2-350M-GGUF/resolve/main/LFM2-350M-Q8_0.gguf', memoryRequirement: 400_000_000, }); + // LFM2.5 1.2B - Best-in-class edge model from Liquid AI (1.2B params, ~700MB Q4) + // 239 tok/s on AMD CPU, designed for on-device deployment + await LlamaCPP.addModel({ + id: 'lfm2.5-1.2b-instruct-q4_k_m', + name: 'LiquidAI LFM2.5 1.2B Instruct Q4_K_M', + url: 'https://huggingface.co/LiquidAI/LFM2.5-1.2B-Instruct-GGUF/resolve/main/LFM2.5-1.2B-Instruct-Q4_K_M.gguf', + memoryRequirement: 900_000_000, + }); + // Tool Calling Optimized Models + // LFM2-1.2B-Tool - Designed for concise and precise tool calling (Liquid AI) + await LlamaCPP.addModel({ + id: 'lfm2-1.2b-tool-q4_k_m', + name: 'LiquidAI LFM2 1.2B Tool Q4_K_M', + url: 'https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q4_K_M.gguf', + memoryRequirement: 800_000_000, + }); + await LlamaCPP.addModel({ + id: 'lfm2-1.2b-tool-q8_0', + name: 'LiquidAI LFM2 1.2B Tool Q8_0', + url: 'https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q8_0.gguf', + memoryRequirement: 1_400_000_000, + }); // ONNX module with STT and TTS models // Using tar.gz format hosted on RunanywhereAI/sherpa-onnx for fast native extraction @@ -179,6 +210,10 @@ const App: React.FC = () => { memoryRequirement: 65_000_000, }); + // Diffusion (CoreML) is Swift SDK + Swift example app only. React Native does not + // depend on the Swift SDK, so we do not register diffusion models or Diffusion.register() + // on iOS here. Use the Swift example app for image generation on iOS. + console.warn('[App] All models registered'); }; diff --git a/examples/react-native/RunAnywhereAI/android/build.gradle b/examples/react-native/RunAnywhereAI/android/build.gradle index fd3965f9d..da5e0875a 100644 --- a/examples/react-native/RunAnywhereAI/android/build.gradle +++ b/examples/react-native/RunAnywhereAI/android/build.gradle @@ -19,18 +19,3 @@ buildscript { } apply plugin: "com.facebook.react.rootproject" - -// ============================================================================= -// Fix: Node.js not found when running from Android Studio -// Android Studio doesn't inherit terminal PATH -// ============================================================================= -gradle.taskGraph.whenReady { taskGraph -> - taskGraph.allTasks.each { task -> - if (task.name.contains("generateCodegenSchemaFromJavaScript") || - task.name.contains("Codegen")) { - if (task.hasProperty("environment")) { - task.environment("PATH", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:" + (System.getenv("PATH") ?: "")) - } - } - } -} diff --git a/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI.xcodeproj/project.pbxproj b/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI.xcodeproj/project.pbxproj index c7e0ac532..b05cb9c71 100644 --- a/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI.xcodeproj/project.pbxproj +++ b/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI.xcodeproj/project.pbxproj @@ -476,7 +476,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = AFAL2647U9; + DEVELOPMENT_TEAM = L86FH3K93L; ENABLE_BITCODE = NO; INFOPLIST_FILE = RunAnywhereAI/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -505,7 +505,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = AFAL2647U9; + DEVELOPMENT_TEAM = L86FH3K93L; INFOPLIST_FILE = RunAnywhereAI/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI.xcworkspace/xcshareddata/swiftpm/Package.resolved b/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..1c23aa765 --- /dev/null +++ b/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,105 @@ +{ + "originHash" : "ca4900869f13fe8a468ed365f1f5f1ffef3e0a66f65ea24f176fd326c48ae5ce", + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "3f99050e75bbc6fe71fc323adabb039756680016", + "version" : "5.11.1" + } + }, + { + "identity" : "bitbytedata", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tsolomko/BitByteData", + "state" : { + "revision" : "cdcdc5177ad536cfb11b95c620f926a81014b7fe", + "version" : "2.0.4" + } + }, + { + "identity" : "devicekit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/devicekit/DeviceKit.git", + "state" : { + "revision" : "581df61650bc457ec00373a592a84be3e7468eb1", + "version" : "5.7.0" + } + }, + { + "identity" : "files", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JohnSundell/Files.git", + "state" : { + "revision" : "e85f2b4a8dfa0f242889f45236f3867d16e40480", + "version" : "4.3.0" + } + }, + { + "identity" : "ml-stable-diffusion", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/ml-stable-diffusion.git", + "state" : { + "revision" : "5a170d29cf38e674b80541d7ce22929c6a11cdde", + "version" : "1.1.1" + } + }, + { + "identity" : "sentry-cocoa", + "kind" : "remoteSourceControl", + "location" : "https://github.com/getsentry/sentry-cocoa", + "state" : { + "revision" : "16cd512711375fa73f25ae5e373f596bdf4251ae", + "version" : "8.58.0" + } + }, + { + "identity" : "swcompression", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tsolomko/SWCompression.git", + "state" : { + "revision" : "390e0b0af8dd19a600005a242a89e570ff482e09", + "version" : "4.8.6" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "c5d11a805e765f52ba34ec7284bd4fcd6ba68615", + "version" : "1.7.0" + } + }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "810496cf121e525d660cd0ea89a758740476b85f", + "version" : "1.5.1" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "95ba0316a9b733e92bb6b071255ff46263bbe7dc", + "version" : "3.15.1" + } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation.git", + "state" : { + "revision" : "22787ffb59de99e5dc1fbfe80b19c97a904ad48d", + "version" : "0.9.20" + } + } + ], + "version" : 3 +} diff --git a/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/PrivacyInfo.xcprivacy b/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/PrivacyInfo.xcprivacy index bad327615..41b8317f0 100644 --- a/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/PrivacyInfo.xcprivacy +++ b/examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/PrivacyInfo.xcprivacy @@ -6,18 +6,18 @@ NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPICategoryFileTimestamp NSPrivacyAccessedAPITypeReasons - CA92.1 + C617.1 NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPICategoryUserDefaults NSPrivacyAccessedAPITypeReasons - C617.1 + CA92.1 diff --git a/examples/react-native/RunAnywhereAI/package-lock.json b/examples/react-native/RunAnywhereAI/package-lock.json index f66fde1ca..9d7e95de6 100644 --- a/examples/react-native/RunAnywhereAI/package-lock.json +++ b/examples/react-native/RunAnywhereAI/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "runanywhere-ai-example", "version": "0.1.0", + "hasInstallScript": true, "dependencies": { "@react-native-async-storage/async-storage": "^2.2.0", "@react-navigation/bottom-tabs": "^7.8.11", @@ -95,6 +96,23 @@ } } }, + "../../../sdk/runanywhere-react-native/packages/diffusion": { + "name": "@runanywhere/diffusion", + "version": "0.1.0", + "extraneous": true, + "license": "MIT", + "devDependencies": { + "nitrogen": "^0.31.10", + "react-native-nitro-modules": "^0.31.10", + "typescript": "~5.9.2" + }, + "peerDependencies": { + "@runanywhere/core": ">=0.16.0", + "react": ">=18.0.0", + "react-native": ">=0.74.0", + "react-native-nitro-modules": ">=0.31.3" + } + }, "../../../sdk/runanywhere-react-native/packages/llamacpp": { "name": "@runanywhere/llamacpp", "version": "0.17.6", diff --git a/examples/react-native/RunAnywhereAI/package.json b/examples/react-native/RunAnywhereAI/package.json index 5f1bcb5c7..936e118de 100644 --- a/examples/react-native/RunAnywhereAI/package.json +++ b/examples/react-native/RunAnywhereAI/package.json @@ -14,7 +14,8 @@ "format:fix": "prettier \"src/**/*.{ts,tsx}\" \"App.tsx\" --write", "unused": "knip", "pod-install": "cd ios && pod install", - "clean": "watchman watch-del-all && rm -rf node_modules && rm -rf ios/Pods && npm install && cd ios && pod install" + "clean": "watchman watch-del-all && rm -rf node_modules && rm -rf ios/Pods && npm install && cd ios && pod install", + "postinstall": "node scripts/patch-agp-version.js" }, "dependencies": { "@react-native-async-storage/async-storage": "^2.2.0", diff --git a/examples/react-native/RunAnywhereAI/react-native.config.js b/examples/react-native/RunAnywhereAI/react-native.config.js index 760887e4d..b5969a967 100644 --- a/examples/react-native/RunAnywhereAI/react-native.config.js +++ b/examples/react-native/RunAnywhereAI/react-native.config.js @@ -8,7 +8,10 @@ module.exports = { }, }, dependencies: { - // Disable audio libraries on iOS - they're incompatible with New Architecture + // Disable autolinking for audio libraries that are incompatible with New Architecture. + // iOS: These libraries don't support the New Architecture (TurboModules/Fabric). + // Android: Some libraries also disabled on Android due to build conflicts or + // because we use custom native implementations instead. 'react-native-live-audio-stream': { platforms: { ios: null, @@ -17,12 +20,13 @@ module.exports = { 'react-native-audio-recorder-player': { platforms: { ios: null, - android: null, + android: null, // Disabled on both platforms - using custom audio implementation }, }, 'react-native-sound': { platforms: { - ios: null, + ios: null, // iOS uses NativeAudioModule for playback + // Android: enable so TTS and Audio playback work }, }, 'react-native-tts': { diff --git a/examples/react-native/RunAnywhereAI/scripts/patch-agp-version.js b/examples/react-native/RunAnywhereAI/scripts/patch-agp-version.js new file mode 100644 index 000000000..2fca5a127 --- /dev/null +++ b/examples/react-native/RunAnywhereAI/scripts/patch-agp-version.js @@ -0,0 +1,25 @@ +/** + * Patches React Native gradle plugin to use AGP 8.11.1 so Android Studio + * (max supported 8.11.1) accepts the project. Run automatically after npm install. + */ +const fs = require('fs'); +const path = require('path'); + +const tomlPath = path.join( + __dirname, + '..', + 'node_modules', + '@react-native', + 'gradle-plugin', + 'gradle', + 'libs.versions.toml' +); + +if (!fs.existsSync(tomlPath)) return; + +let content = fs.readFileSync(tomlPath, 'utf8'); +if (content.includes('agp = "8.12.0"')) { + content = content.replace('agp = "8.12.0"', 'agp = "8.11.1"'); + fs.writeFileSync(tomlPath, content); + console.log('[postinstall] Patched AGP to 8.11.1 for Android Studio compatibility.'); +} diff --git a/examples/react-native/RunAnywhereAI/src/components/chat/MessageBubble.tsx b/examples/react-native/RunAnywhereAI/src/components/chat/MessageBubble.tsx index 99657c112..1a011580f 100644 --- a/examples/react-native/RunAnywhereAI/src/components/chat/MessageBubble.tsx +++ b/examples/react-native/RunAnywhereAI/src/components/chat/MessageBubble.tsx @@ -20,6 +20,7 @@ import { Typography } from '../../theme/typography'; import { Spacing, BorderRadius, Padding, Layout } from '../../theme/spacing'; import type { Message } from '../../types/chat'; import { MessageRole } from '../../types/chat'; +import { ToolCallIndicator } from './ToolCallIndicator'; interface MessageBubbleProps { message: Message; @@ -90,6 +91,11 @@ export const MessageBubble: React.FC = ({ )} + {/* Tool Call Indicator (for messages that used tools) */} + {isAssistant && message.toolCallInfo && ( + + )} + {/* Thinking Section (expandable) */} {hasThinking && ( = ({ + toolCallInfo, +}) => { + const [showSheet, setShowSheet] = useState(false); + + const backgroundColor = toolCallInfo.success + ? Colors.primaryBlue + '1A' // 10% opacity + : Colors.primaryOrange + '1A'; + + const borderColor = toolCallInfo.success + ? Colors.primaryBlue + '4D' // 30% opacity + : Colors.primaryOrange + '4D'; + + const iconColor = toolCallInfo.success + ? Colors.primaryBlue + : Colors.primaryOrange; + + return ( + <> + setShowSheet(true)} + activeOpacity={0.7} + > + + {toolCallInfo.toolName} + + + setShowSheet(false)} + /> + + ); +}; + +interface ToolCallDetailSheetProps { + visible: boolean; + toolCallInfo: ToolCallInfo; + onClose: () => void; +} + +/** + * Full detail sheet showing tool call arguments and results as JSON + */ +const ToolCallDetailSheet: React.FC = ({ + visible, + toolCallInfo, + onClose, +}) => { + return ( + + + {/* Header */} + + Tool Call + + Done + + + + + {/* Status Section */} + + + + {toolCallInfo.success ? 'Success' : 'Failed'} + + + + {/* Tool Name */} + + + {/* Arguments */} + + + {/* Result (if available) */} + {toolCallInfo.result && ( + + )} + + {/* Error (if available) */} + {toolCallInfo.error && ( + + )} + + + + ); +}; + +interface DetailSectionProps { + title: string; + content: string; + isError?: boolean; +} + +const DetailSection: React.FC = ({ + title, + content, + isError = false, +}) => ( + + {title} + + {content} + + +); + +interface CodeSectionProps { + title: string; + code: string; +} + +const CodeSection: React.FC = ({ title, code }) => { + // Try to pretty print JSON + let formattedCode = code; + try { + const parsed = JSON.parse(code); + formattedCode = JSON.stringify(parsed, null, 2); + } catch { + // Keep original if not valid JSON + } + + return ( + + {title} + + {formattedCode} + + + ); +}; + +/** + * Badge shown in chat to indicate tool calling is enabled + * Matches iOS toolCallingBadge + */ +interface ToolCallingBadgeProps { + toolCount: number; +} + +export const ToolCallingBadge: React.FC = ({ + toolCount, +}) => { + return ( + + + + Tools enabled ({toolCount}) + + + ); +}; + +const styles = StyleSheet.create({ + // Badge styles + badge: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + paddingHorizontal: 10, + paddingVertical: 6, + borderRadius: 8, + marginBottom: Spacing.small, + alignSelf: 'flex-start', + }, + badgeText: { + ...Typography.caption2, + color: Colors.textSecondary, + }, + + // Sheet styles + sheetContainer: { + flex: 1, + backgroundColor: Colors.backgroundPrimary, + }, + sheetHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: Padding.padding16, + paddingVertical: Padding.padding12, + borderBottomWidth: 1, + borderBottomColor: Colors.borderLight, + }, + sheetTitle: { + ...Typography.headline, + color: Colors.textPrimary, + }, + closeButton: { + paddingHorizontal: Padding.padding12, + paddingVertical: Padding.padding8, + }, + closeButtonText: { + ...Typography.body, + color: Colors.primaryBlue, + fontWeight: '600', + }, + sheetContent: { + flex: 1, + }, + sheetContentContainer: { + padding: Padding.padding16, + gap: 20, + }, + + // Status section + statusSection: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + padding: Padding.padding16, + borderRadius: BorderRadius.regular, + }, + statusText: { + ...Typography.headline, + color: Colors.textPrimary, + }, + + // Detail section + section: { + gap: Spacing.small, + }, + sectionTitle: { + ...Typography.caption, + color: Colors.textSecondary, + }, + sectionContent: { + ...Typography.body, + color: Colors.textPrimary, + }, + errorText: { + color: Colors.statusRed, + }, + + // Code section + codeContainer: { + backgroundColor: Colors.backgroundSecondary, + borderRadius: BorderRadius.regular, + padding: Padding.padding12, + }, + codeText: { + ...Typography.footnote, + fontFamily: 'Menlo', + color: Colors.textPrimary, + }, + + // Tool calling badge (above input) + toolCallingBadge: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 6, + paddingHorizontal: Padding.padding12, + paddingVertical: Padding.padding8, + backgroundColor: Colors.primaryBlue + '1A', + borderTopWidth: 1, + borderTopColor: Colors.borderLight, + }, + toolCallingBadgeText: { + ...Typography.caption, + color: Colors.primaryBlue, + fontWeight: '500', + }, +}); + +export default ToolCallIndicator; diff --git a/examples/react-native/RunAnywhereAI/src/components/chat/index.ts b/examples/react-native/RunAnywhereAI/src/components/chat/index.ts index aa4f443d9..02ba26c88 100644 --- a/examples/react-native/RunAnywhereAI/src/components/chat/index.ts +++ b/examples/react-native/RunAnywhereAI/src/components/chat/index.ts @@ -5,3 +5,4 @@ export { MessageBubble } from './MessageBubble'; export { TypingIndicator } from './TypingIndicator'; export { ChatInput } from './ChatInput'; +export { ToolCallIndicator, ToolCallingBadge } from './ToolCallIndicator'; diff --git a/examples/react-native/RunAnywhereAI/src/navigation/TabNavigator.tsx b/examples/react-native/RunAnywhereAI/src/navigation/TabNavigator.tsx index b4354430b..61a7b4718 100644 --- a/examples/react-native/RunAnywhereAI/src/navigation/TabNavigator.tsx +++ b/examples/react-native/RunAnywhereAI/src/navigation/TabNavigator.tsx @@ -1,29 +1,34 @@ /** * TabNavigator - Bottom Tab Navigation * - * Reference: iOS ContentView.swift with 5 tabs: + * Reference: iOS ContentView.swift with 6 tabs: * - Chat (LLM) * - STT (Speech-to-Text) * - TTS (Text-to-Speech) * - Voice (Voice Assistant - STT + LLM + TTS) - * - Settings + * - Vision (VLM only; image generation is Swift sample app only) + * - Settings (includes Tool Settings) */ import React from 'react'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; import Icon from 'react-native-vector-icons/Ionicons'; import { Colors } from '../theme/colors'; import { Typography } from '../theme/typography'; -import type { RootTabParamList } from '../types'; +import type { RootTabParamList, VisionStackParamList } from '../types'; // Screens import ChatScreen from '../screens/ChatScreen'; import STTScreen from '../screens/STTScreen'; import TTSScreen from '../screens/TTSScreen'; import VoiceAssistantScreen from '../screens/VoiceAssistantScreen'; +import VisionHubScreen from '../screens/VisionHubScreen'; +import VLMScreen from '../screens/VLMScreen'; import SettingsScreen from '../screens/SettingsScreen'; const Tab = createBottomTabNavigator(); +const VisionStack = createNativeStackNavigator(); /** * Tab icon mapping - matching Swift sample app (ContentView.swift) @@ -36,18 +41,20 @@ const tabIcons: Record< STT: { focused: 'pulse', unfocused: 'pulse-outline' }, // waveform equivalent TTS: { focused: 'volume-high', unfocused: 'volume-high-outline' }, // speaker.wave.2 Voice: { focused: 'mic', unfocused: 'mic-outline' }, // mic for voice assistant + Vision: { focused: 'camera', unfocused: 'camera-outline' }, Settings: { focused: 'settings', unfocused: 'settings-outline' }, }; /** * Tab display names - matching iOS Swift sample app (ContentView.swift) - * iOS uses: Chat, Transcribe, Speak, Voice, Settings + * iOS uses: Chat, Vision, Transcribe, Speak, Voice, Settings */ const tabLabels: Record = { Chat: 'Chat', STT: 'Transcribe', TTS: 'Speak', Voice: 'Voice', + Vision: 'Vision', Settings: 'Settings', }; @@ -108,7 +115,13 @@ export const TabNavigator: React.FC = () => { component={VoiceAssistantScreen} options={{ tabBarLabel: tabLabels.Voice }} /> - {/* Tab 4: Settings */} + {/* Tab 4: Vision (hub -> VLM) */} + + {/* Tab 5: Settings (includes Tool Settings) */} { ); }; +/** + * Vision tab: stack with Hub -> VLM + */ +const VisionStackScreen: React.FC = () => { + return ( + + + + + ); +}; + export default TabNavigator; diff --git a/examples/react-native/RunAnywhereAI/src/screens/ChatScreen.tsx b/examples/react-native/RunAnywhereAI/src/screens/ChatScreen.tsx index 0af9c5bc9..55a134a36 100644 --- a/examples/react-native/RunAnywhereAI/src/screens/ChatScreen.tsx +++ b/examples/react-native/RunAnywhereAI/src/screens/ChatScreen.tsx @@ -36,10 +36,10 @@ import { Colors } from '../theme/colors'; import { Typography } from '../theme/typography'; import { Spacing, Padding, IconSize } from '../theme/spacing'; import { ModelStatusBanner, ModelRequiredOverlay } from '../components/common'; -import { MessageBubble, TypingIndicator, ChatInput } from '../components/chat'; +import { MessageBubble, TypingIndicator, ChatInput, ToolCallingBadge } from '../components/chat'; import { ChatAnalyticsScreen } from './ChatAnalyticsScreen'; import { ConversationListScreen } from './ConversationListScreen'; -import type { Message, Conversation } from '../types/chat'; +import type { Message, Conversation, ToolCallInfo } from '../types/chat'; import { MessageRole } from '../types/chat'; import type { ModelInfo } from '../types/model'; import { ModelModality, LLMFramework, ModelCategory } from '../types/model'; @@ -51,10 +51,152 @@ import { // Import RunAnywhere SDK (Multi-Package Architecture) import { RunAnywhere, type ModelInfo as SDKModelInfo } from '@runanywhere/core'; +import { safeEvaluateExpression } from '../utils/mathParser'; // Generate unique ID const generateId = () => Math.random().toString(36).substring(2, 15); +// ============================================================================= +// TOOL CALLING SETUP - Weather API Example +// ============================================================================= + +/** + * Register tools for the chat. This enables the LLM to call external APIs. + * Users just chat normally - tool calls happen transparently. + */ +const registerChatTools = () => { + // Clear any existing tools + RunAnywhere.clearTools(); + + // Weather tool - Real API (wttr.in - no key needed) + RunAnywhere.registerTool( + { + name: 'get_weather', + description: 'Gets the current weather for a city or location', + parameters: [ + { + name: 'location', + type: 'string', + description: 'City name or location (e.g., "Tokyo", "New York", "London")', + required: true, + }, + ], + }, + async (args) => { + // Handle both 'location' and 'city' parameter names (models vary) + const location = (args.location || args.city) as string; + console.log('[Tool] get_weather called for:', location); + + try { + const url = `https://wttr.in/${encodeURIComponent(location)}?format=j1`; + const response = await fetch(url); + + if (!response.ok) { + return { error: `Weather API error: ${response.status}` }; + } + + const data = await response.json(); + const current = data.current_condition[0]; + const area = data.nearest_area?.[0]; + + return { + location: area?.areaName?.[0]?.value || location, + country: area?.country?.[0]?.value || '', + temperature_f: parseInt(current.temp_F, 10), + temperature_c: parseInt(current.temp_C, 10), + condition: current.weatherDesc[0].value, + humidity: `${current.humidity}%`, + wind_mph: `${current.windspeedMiles} mph`, + feels_like_f: parseInt(current.FeelsLikeF, 10), + }; + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + console.error('[Tool] Weather fetch failed:', msg); + return { error: msg }; + } + } + ); + + // Current time tool + RunAnywhere.registerTool( + { + name: 'get_current_time', + description: 'Gets the current date and time', + parameters: [], + }, + async () => { + console.log('[Tool] get_current_time called'); + const now = new Date(); + return { + date: now.toLocaleDateString(), + time: now.toLocaleTimeString(), + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + }; + } + ); + + // Calculator tool - Math evaluation + RunAnywhere.registerTool( + { + name: 'calculate', + description: 'Performs math calculations. Supports +, -, *, /, and parentheses', + parameters: [ + { + name: 'expression', + type: 'string', + description: 'Math expression (e.g., "2 + 2 * 3", "(10 + 5) / 3")', + required: true, + }, + ], + }, + async (args) => { + const expression = (args.expression || args.input) as string; + console.log('[Tool] calculate called for:', expression); + try { + // Safe math evaluation using recursive descent parser + const result = safeEvaluateExpression(expression); + return { + expression: expression, + result: result, + }; + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + return { error: `Failed to calculate: ${msg}` }; + } + } + ); + + console.log('[ChatScreen] Tools registered: get_weather, get_current_time, calculate'); +}; + +/** + * Detect tool call format based on model ID and name + * LFM2-Tool models use Pythonic format, others use JSON format + * + * Matches iOS: LLMViewModel+ToolCalling.swift detectToolCallFormat() + * Checks both ID and name since model might be identified by either + */ +const detectToolCallFormat = (modelId: string | undefined, modelName: string | undefined): string => { + // Check model ID first (more reliable - e.g., "lfm2-1.2b-tool-q4_k_m") + if (modelId) { + const id = modelId.toLowerCase(); + if (id.includes('lfm2') && id.includes('tool')) { + return 'lfm2'; + } + } + + // Also check model name (e.g., "LiquidAI LFM2 1.2B Tool Q4_K_M") + if (modelName) { + const name = modelName.toLowerCase(); + if (name.includes('lfm2') && name.includes('tool')) { + return 'lfm2'; + } + } + + // Default JSON format for general-purpose models + return 'default'; +}; + export const ChatScreen: React.FC = () => { // Conversation store const { @@ -77,6 +219,7 @@ export const ChatScreen: React.FC = () => { const [showConversationList, setShowConversationList] = useState(false); const [showModelSelection, setShowModelSelection] = useState(false); const [isInitialized, setIsInitialized] = useState(false); + const [registeredToolCount, setRegisteredToolCount] = useState(0); // Refs const flatListRef = useRef(null); @@ -143,15 +286,19 @@ export const ChatScreen: React.FC = () => { /** * Check if a model is loaded + * Note: If a model is already loaded from a previous session, we set a placeholder. + * For proper tool calling format detection, the user should select a model through the UI. */ const checkModelStatus = async () => { try { const isLoaded = await RunAnywhere.isModelLoaded(); console.warn('[ChatScreen] Text model loaded:', isLoaded); if (isLoaded) { + // Model is loaded but we don't know which one - set placeholder + // User should select a model through UI for proper format detection setCurrentModel({ id: 'loaded-model', - name: 'Loaded Model', + name: 'Loaded Model (select model for tool calling)', category: ModelCategory.Language, compatibleFrameworks: [LLMFramework.LlamaCpp], preferredFramework: LLMFramework.LlamaCpp, @@ -159,6 +306,11 @@ export const ChatScreen: React.FC = () => { isAvailable: true, supportsThinking: false, }); + // Register tools if model already loaded + registerChatTools(); + const tools = RunAnywhere.getRegisteredTools(); + setRegisteredToolCount(tools.length); + console.warn('[ChatScreen] Model loaded from previous session. For LFM2 tool calling, please select the model again.'); } } catch (error) { console.warn('[ChatScreen] Error checking model status:', error); @@ -203,7 +355,8 @@ export const ChatScreen: React.FC = () => { const success = await RunAnywhere.loadModel(model.localPath); if (success) { - setCurrentModel({ + // Set the model info with actual ID and name for format detection + const modelInfo = { id: model.id, name: model.name, category: ModelCategory.Language, @@ -212,8 +365,18 @@ export const ChatScreen: React.FC = () => { isDownloaded: true, isAvailable: true, supportsThinking: false, - }); - console.warn('[ChatScreen] Model loaded successfully'); + }; + setCurrentModel(modelInfo); + + // Log model info for format detection debugging + const format = detectToolCallFormat(model.id, model.name); + console.warn(`[ChatScreen] Model loaded: id="${model.id}", name="${model.name}", detected format="${format}"`); + + // Register tools when model loads + registerChatTools(); + const tools = RunAnywhere.getRegisteredTools(); + setRegisteredToolCount(tools.length); + console.warn('[ChatScreen] Tools registered:', tools.length, 'tools'); } else { const lastError = await RunAnywhere.getLastError(); Alert.alert( @@ -230,8 +393,11 @@ export const ChatScreen: React.FC = () => { }; /** - * Send a message using the real SDK with streaming (matches Swift SDK) - * Uses RunAnywhere.generateStream() for real-time token display + * Send a message using the real SDK with tool calling support + * Uses RunAnywhere.generateWithTools() for AI that can take actions + * + * Example: "What's the weather in Tokyo?" + * โ†’ LLM calls get_weather tool โ†’ Real API call โ†’ Final response */ const handleSend = useCallback(async () => { if (!inputText.trim() || !currentConversation) return; @@ -249,12 +415,12 @@ export const ChatScreen: React.FC = () => { setInputText(''); setIsLoading(true); - // Create placeholder assistant message for streaming + // Create placeholder assistant message const assistantMessageId = generateId(); const assistantMessage: Message = { id: assistantMessageId, role: MessageRole.Assistant, - content: '', + content: 'Thinking...', timestamp: new Date(), }; await addMessage(assistantMessage, currentConversation.id); @@ -265,55 +431,66 @@ export const ChatScreen: React.FC = () => { }, 100); try { - console.log('[ChatScreen] Starting streaming generation for:', prompt); - - // Use streaming generation (matches Swift SDK: RunAnywhere.generateStream) - const streamingResult = await RunAnywhere.generateStream(prompt, { + // Detect tool call format based on loaded model (matches iOS LLMViewModel+ToolCalling.swift) + const format = detectToolCallFormat(currentModel?.id, currentModel?.name); + console.log('[ChatScreen] Starting generation with tools for:', prompt, 'model:', currentModel?.id, 'format:', format); + + // Use tool-enabled generation + // If the LLM needs to call a tool (like weather API), it happens automatically + const result = await RunAnywhere.generateWithTools(prompt, { + autoExecute: true, + maxToolCalls: 3, maxTokens: 1000, temperature: 0.7, + format: format, }); - let fullResponse = ''; + // Log tool usage for debugging + if (result.toolCalls.length > 0) { + console.log('[ChatScreen] Tools used:', result.toolCalls.map(t => t.toolName)); + console.log('[ChatScreen] Tool results:', result.toolResults); + } + + // Build final message content + let finalContent = result.text || '(No response generated)'; - // Stream tokens in real-time (matches Swift's for await loop) - for await (const token of streamingResult.stream) { - fullResponse += token; + // Extract tool call info from result (matching iOS implementation) + let toolCallInfo: ToolCallInfo | undefined; + if (result.toolCalls.length > 0) { + const lastToolCall = result.toolCalls[result.toolCalls.length - 1]; + const lastToolResult = result.toolResults[result.toolResults.length - 1]; - // Update assistant message content as tokens arrive - updateMessage( - { - ...assistantMessage, - content: fullResponse, - }, - currentConversation.id - ); - - // Scroll to keep up with new content - flatListRef.current?.scrollToEnd({ animated: false }); + toolCallInfo = { + toolName: lastToolCall.toolName, + arguments: JSON.stringify(lastToolCall.arguments, null, 2), + result: lastToolResult?.success + ? JSON.stringify(lastToolResult.result, null, 2) + : undefined, + success: lastToolResult?.success ?? false, + error: lastToolResult?.error, + }; + + console.log('[ChatScreen] Created toolCallInfo:', toolCallInfo.toolName, 'success:', toolCallInfo.success); } - // Get final metrics after streaming completes - const result = await streamingResult.result; - console.log('[ChatScreen] Streaming complete, metrics:', result); - - // Update with final message including analytics + // Update with final message const finalMessage: Message = { id: assistantMessageId, role: MessageRole.Assistant, - content: fullResponse || '(No response generated)', + content: finalContent, timestamp: new Date(), + toolCallInfo, // Attach tool call info to message modelInfo: { - modelId: result.modelUsed || 'unknown', - modelName: result.modelUsed || 'Unknown Model', - framework: result.framework || 'llama.cpp', + modelId: currentModel?.id || 'unknown', + modelName: currentModel?.name || 'Unknown Model', + framework: 'llama.cpp', frameworkDisplayName: 'llama.cpp', }, analytics: { - totalGenerationTime: result.latencyMs, - inputTokens: result.inputTokens || Math.ceil(prompt.length / 4), - outputTokens: result.tokensUsed, - averageTokensPerSecond: result.tokensPerSecond || 0, - timeToFirstToken: result.timeToFirstTokenMs, + totalGenerationTime: 0, + inputTokens: Math.ceil(prompt.length / 4), + outputTokens: Math.ceil(finalContent.length / 4), + averageTokensPerSecond: 0, completionStatus: 'completed', wasThinkingMode: false, wasInterrupted: false, @@ -335,7 +512,7 @@ export const ChatScreen: React.FC = () => { { id: assistantMessageId, role: MessageRole.Assistant, - content: `Error: ${error}\n\nThis likely means no LLM model is loaded. Use demo mode to test the interface, or provide a GGUF model.`, + content: `Error: ${error}\n\nThis likely means no LLM model is loaded. Load a model first.`, timestamp: new Date(), }, currentConversation.id @@ -343,7 +520,7 @@ export const ChatScreen: React.FC = () => { } finally { setIsLoading(false); } - }, [inputText, currentConversation, addMessage, updateMessage]); + }, [inputText, currentConversation, currentModel, addMessage, updateMessage]); /** * Create a new conversation (clears current chat) @@ -508,6 +685,11 @@ export const ChatScreen: React.FC = () => { {/* Typing Indicator */} {isLoading && } + {/* Tool Calling Badge (shows when tools are enabled) */} + {currentModel && registeredToolCount > 0 && ( + + )} + {/* Input Area */} { >({}); const [downloadedModels, setDownloadedModels] = useState([]); + // Tool calling state + const [toolCallingEnabled, setToolCallingEnabled] = useState(false); + const [registeredTools, setRegisteredTools] = useState}>>([]); + // Capability names mapping const capabilityNames: Record = { 0: 'STT (Speech-to-Text)', @@ -168,6 +174,7 @@ export const SettingsScreen: React.FC = () => { useEffect(() => { loadData(); loadApiConfiguration(); + loadToolCallingSettings(); }, []); /** @@ -187,6 +194,158 @@ export const SettingsScreen: React.FC = () => { } }; + /** + * Load tool calling settings from AsyncStorage + */ + const loadToolCallingSettings = async () => { + try { + const enabled = await AsyncStorage.getItem(STORAGE_KEYS.TOOL_CALLING_ENABLED); + setToolCallingEnabled(enabled === 'true'); + refreshRegisteredTools(); + } catch (error) { + console.error('[Settings] Failed to load tool calling settings:', error); + } + }; + + /** + * Refresh the list of registered tools from SDK + */ + const refreshRegisteredTools = () => { + const tools = RunAnywhere.getRegisteredTools(); + setRegisteredTools(tools.map(t => ({ + name: t.name, + description: t.description, + parameters: t.parameters || [], + }))); + }; + + /** + * Toggle tool calling enabled state + */ + const handleToggleToolCalling = async (enabled: boolean) => { + setToolCallingEnabled(enabled); + try { + await AsyncStorage.setItem(STORAGE_KEYS.TOOL_CALLING_ENABLED, enabled ? 'true' : 'false'); + } catch (error) { + console.error('[Settings] Failed to save tool calling setting:', error); + } + }; + + /** + * Register demo tools (weather, time, calculator) + */ + const registerDemoTools = () => { + // Clear existing tools + RunAnywhere.clearTools(); + + // Weather tool - Real API (wttr.in - no key needed) + RunAnywhere.registerTool( + { + name: 'get_weather', + description: 'Gets the current weather for a city or location', + parameters: [ + { + name: 'location', + type: 'string', + description: 'City name or location (e.g., "Tokyo", "New York", "London")', + required: true, + }, + ], + }, + async (args: Record) => { + const location = (args.location as string) || 'San Francisco'; + try { + const response = await fetch( + `https://wttr.in/${encodeURIComponent(location)}?format=j1` + ); + const data = await response.json(); + const current = data.current_condition?.[0]; + return { + location, + temperature_c: current?.temp_C || 'N/A', + temperature_f: current?.temp_F || 'N/A', + condition: current?.weatherDesc?.[0]?.value || 'Unknown', + humidity: current?.humidity || 'N/A', + wind_kph: current?.windspeedKmph || 'N/A', + }; + } catch (error) { + return { error: `Failed to get weather: ${error}` }; + } + } + ); + + // Time tool - Real system time + RunAnywhere.registerTool( + { + name: 'get_current_time', + description: 'Gets the current date, time, and timezone information', + parameters: [], + }, + async () => { + const now = new Date(); + return { + datetime: now.toLocaleString(), + time: now.toLocaleTimeString(), + timestamp: now.toISOString(), + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + }; + } + ); + + // Calculator tool - Math evaluation + RunAnywhere.registerTool( + { + name: 'calculate', + description: 'Performs math calculations. Supports +, -, *, /, and parentheses', + parameters: [ + { + name: 'expression', + type: 'string', + description: 'Math expression (e.g., "2 + 2 * 3", "(10 + 5) / 3")', + required: true, + }, + ], + }, + async (args: Record) => { + const expression = (args.expression as string) || '0'; + try { + // Safe math evaluation using recursive descent parser + const result = safeEvaluateExpression(expression); + return { + expression: expression, + result: result, + }; + } catch (error) { + return { error: `Failed to calculate: ${error}` }; + } + } + ); + + refreshRegisteredTools(); + Alert.alert('Demo Tools Added', '3 demo tools have been registered: get_weather, get_current_time, calculate'); + }; + + /** + * Clear all registered tools + */ + const clearAllTools = () => { + Alert.alert( + 'Clear All Tools', + 'Are you sure you want to remove all registered tools?', + [ + { text: 'Cancel', style: 'cancel' }, + { + text: 'Clear', + style: 'destructive', + onPress: () => { + RunAnywhere.clearTools(); + refreshRegisteredTools(); + }, + }, + ] + ); + }; + /** * Normalize base URL by adding https:// if no scheme is present */ @@ -801,6 +960,102 @@ export const SettingsScreen: React.FC = () => { + {/* Tool Settings - Matches iOS ToolSettingsView */} + {renderSectionHeader('Tool Settings')} + + {/* Enable Tool Calling Toggle */} + + + Enable Tool Calling + + Allow LLMs to call tools (APIs, functions) + + + handleToggleToolCalling(!toolCallingEnabled)} + > + + + + + {toolCallingEnabled && ( + <> + + + {/* Registered Tools Count */} + + Registered Tools + 0 ? Colors.primaryGreen : Colors.textSecondary }]}> + {registeredTools.length} {registeredTools.length === 1 ? 'tool' : 'tools'} + + + + {/* Demo Tools Button */} + {registeredTools.length === 0 && ( + <> + + + + Add Demo Tools + + + )} + + {/* Registered Tools List */} + {registeredTools.length > 0 && ( + <> + + {registeredTools.map((tool, index) => ( + + + + {tool.name} + {tool.description} + {tool.parameters.length > 0 && ( + + {tool.parameters.map((p) => ( + + {p.name} + + ))} + + )} + + {index < registeredTools.length - 1 && } + + ))} + + {/* Clear All Tools Button */} + + + + Clear All Tools + + + )} + + )} + + + Tools allow the LLM to call external APIs and functions to get real-time data. + + + {/* Storage Overview - Matches iOS CombinedSettingsView */} {renderSectionHeader('Storage Overview')} @@ -1520,6 +1775,116 @@ const styles = StyleSheet.create({ modalButtonTextDisabled: { color: Colors.textTertiary, }, + // Tool Settings styles + toolSettingRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: Padding.padding16, + }, + toolSettingInfo: { + flex: 1, + marginRight: Spacing.medium, + }, + toolSettingLabel: { + ...Typography.body, + color: Colors.textPrimary, + fontWeight: '500', + }, + toolSettingDescription: { + ...Typography.footnote, + color: Colors.textSecondary, + marginTop: Spacing.xxSmall, + }, + toolSettingValue: { + ...Typography.body, + fontWeight: '600', + }, + toggleButton: { + width: 51, + height: 31, + borderRadius: 15.5, + backgroundColor: Colors.backgroundGray5, + padding: 2, + justifyContent: 'center', + }, + toggleButtonActive: { + backgroundColor: Colors.primaryBlue, + }, + toggleKnob: { + width: 27, + height: 27, + borderRadius: 13.5, + backgroundColor: Colors.textWhite, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 2, + elevation: 2, + }, + toggleKnobActive: { + alignSelf: 'flex-end', + }, + demoToolsButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: Spacing.small, + padding: Padding.padding16, + }, + demoToolsButtonText: { + ...Typography.body, + color: Colors.primaryBlue, + fontWeight: '600', + }, + toolRow: { + flexDirection: 'row', + alignItems: 'flex-start', + padding: Padding.padding16, + gap: Spacing.medium, + }, + toolInfo: { + flex: 1, + }, + toolName: { + ...Typography.subheadline, + color: Colors.textPrimary, + fontWeight: '600', + }, + toolDescription: { + ...Typography.footnote, + color: Colors.textSecondary, + marginTop: Spacing.xxSmall, + }, + toolParams: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: Spacing.xSmall, + marginTop: Spacing.small, + }, + toolParamChip: { + backgroundColor: Colors.badgeBlue, + paddingHorizontal: Spacing.small, + paddingVertical: Spacing.xxSmall, + borderRadius: BorderRadius.small, + }, + toolParamText: { + ...Typography.caption2, + color: Colors.primaryBlue, + fontWeight: '500', + }, + clearToolsButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: Spacing.small, + padding: Padding.padding16, + }, + clearToolsButtonText: { + ...Typography.body, + color: Colors.statusRed, + fontWeight: '600', + }, }); export default SettingsScreen; diff --git a/examples/react-native/RunAnywhereAI/src/screens/VLMScreen.tsx b/examples/react-native/RunAnywhereAI/src/screens/VLMScreen.tsx new file mode 100644 index 000000000..0c2369e05 --- /dev/null +++ b/examples/react-native/RunAnywhereAI/src/screens/VLMScreen.tsx @@ -0,0 +1,91 @@ +/** + * VLMScreen - Vision Chat (VLM) placeholder + * + * VLM (Vision-Language Model) is not yet exposed in the React Native SDK. + * This screen shows a coming-soon message and will be wired to processImage + * when the core adds VLM APIs. + * + * Reference: iOS Features/Vision/VLMCameraView.swift, VLMViewModel.swift + */ + +import React from 'react'; +import { + View, + Text, + StyleSheet, + SafeAreaView, + ScrollView, +} from 'react-native'; +import Icon from 'react-native-vector-icons/Ionicons'; +import { Colors } from '../theme/colors'; +import { Typography } from '../theme/typography'; +import { Spacing, Padding } from '../theme/spacing'; + +const VLMScreen: React.FC = () => { + return ( + + + + + + Vision Chat (VLM) + + Vision-language models (describe images, visual QA) are coming to the + React Native SDK. On iOS and Android, the native SDKs already support + VLM; the RN bridge will expose processImage and model loading in a + future release. + + + When available, youโ€™ll be able to pick an image (camera or gallery) + and get a text description or answer questions about it. + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: Colors.backgroundPrimary, + }, + scroll: { + padding: Padding.padding24, + alignItems: 'center', + }, + iconWrap: { + width: 80, + height: 80, + borderRadius: 40, + backgroundColor: Colors.badgeBlue, + justifyContent: 'center', + alignItems: 'center', + marginBottom: Spacing.large, + }, + title: { + ...Typography.title2, + color: Colors.textPrimary, + marginBottom: Spacing.medium, + textAlign: 'center', + }, + message: { + ...Typography.body, + color: Colors.textSecondary, + textAlign: 'center', + marginBottom: Spacing.mediumLarge, + }, + hint: { + ...Typography.footnote, + color: Colors.textTertiary, + textAlign: 'center', + }, +}); + +export default VLMScreen; diff --git a/examples/react-native/RunAnywhereAI/src/screens/VisionHubScreen.tsx b/examples/react-native/RunAnywhereAI/src/screens/VisionHubScreen.tsx new file mode 100644 index 000000000..cbe8e8f09 --- /dev/null +++ b/examples/react-native/RunAnywhereAI/src/screens/VisionHubScreen.tsx @@ -0,0 +1,120 @@ +/** + * VisionHubScreen - Vision tab hub + * + * Lists Vision features: Vision Chat (VLM). Image generation is Swift sample app only. + * + * Reference: examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Vision/VisionHubView.swift + */ + +import React from 'react'; +import { + View, + Text, + StyleSheet, + SafeAreaView, + TouchableOpacity, +} from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import Icon from 'react-native-vector-icons/Ionicons'; +import { Colors } from '../theme/colors'; +import { Typography } from '../theme/typography'; +import { Spacing, Padding, BorderRadius } from '../theme/spacing'; +import type { VisionStackParamList } from '../types'; + +type NavigationProp = NativeStackNavigationProp< + VisionStackParamList, + 'VisionHub' +>; + +const VisionHubScreen: React.FC = () => { + const navigation = useNavigation(); + + return ( + + + Vision + + Vision-language (VLM) + + + + navigation.navigate('VLM')} + activeOpacity={0.7} + > + + + + + Vision Chat (VLM) + + Describe images with a vision-language model + + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: Colors.backgroundPrimary, + }, + header: { + paddingHorizontal: Padding.padding16, + paddingTop: Spacing.large, + paddingBottom: Spacing.medium, + }, + title: { + ...Typography.title1, + color: Colors.textPrimary, + }, + subtitle: { + ...Typography.footnote, + color: Colors.textSecondary, + marginTop: Spacing.xSmall, + }, + list: { + paddingHorizontal: Padding.padding16, + }, + row: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: Colors.backgroundSecondary, + borderRadius: BorderRadius.large, + padding: Spacing.mediumLarge, + marginBottom: Spacing.smallMedium, + }, + iconWrap: { + width: 44, + height: 44, + borderRadius: 22, + backgroundColor: Colors.badgeBlue, + justifyContent: 'center', + alignItems: 'center', + marginRight: Spacing.mediumLarge, + }, + rowContent: { + flex: 1, + }, + rowTitle: { + ...Typography.headline, + color: Colors.textPrimary, + }, + rowSubtitle: { + ...Typography.caption1, + color: Colors.textSecondary, + marginTop: 2, + }, +}); + +export default VisionHubScreen; diff --git a/examples/react-native/RunAnywhereAI/src/types/chat.ts b/examples/react-native/RunAnywhereAI/src/types/chat.ts index 9fca6f10b..a0106afe6 100644 --- a/examples/react-native/RunAnywhereAI/src/types/chat.ts +++ b/examples/react-native/RunAnywhereAI/src/types/chat.ts @@ -85,6 +85,27 @@ export interface MessageModelInfo { frameworkDisplayName: string; } +/** + * Tool call information attached to a message + * Matches iOS ToolCallInfo + */ +export interface ToolCallInfo { + /** Name of the tool that was called */ + toolName: string; + + /** Arguments passed to the tool (JSON string) */ + arguments: string; + + /** Result from the tool (JSON string, if successful) */ + result?: string; + + /** Whether the tool call was successful */ + success: boolean; + + /** Error message (if failed) */ + error?: string; +} + /** * Chat message */ @@ -110,6 +131,9 @@ export interface Message { /** Model info */ modelInfo?: MessageModelInfo; + /** Tool call info (for messages that used tools) */ + toolCallInfo?: ToolCallInfo; + /** Whether the message is still streaming */ isStreaming?: boolean; } diff --git a/examples/react-native/RunAnywhereAI/src/types/index.ts b/examples/react-native/RunAnywhereAI/src/types/index.ts index b08583cc5..078a526dc 100644 --- a/examples/react-native/RunAnywhereAI/src/types/index.ts +++ b/examples/react-native/RunAnywhereAI/src/types/index.ts @@ -22,14 +22,22 @@ export * from './settings'; // Tab 1: Speech-to-Text // Tab 2: Text-to-Speech // Tab 3: Voice Assistant (STT + LLM + TTS) -// Tab 4: Settings +// Tab 4: Vision (hub: VLM only; image generation is Swift sample app only) +// Tab 5: Settings (includes Tool Settings) export type RootTabParamList = { Chat: undefined; STT: undefined; TTS: undefined; Voice: undefined; + Vision: undefined; Settings: undefined; }; +/** Vision tab stack: hub list -> VLM */ +export type VisionStackParamList = { + VisionHub: undefined; + VLM: undefined; +}; + // Common utility types export type Optional = Omit & Partial>; diff --git a/examples/react-native/RunAnywhereAI/src/types/model.ts b/examples/react-native/RunAnywhereAI/src/types/model.ts index fcc3c540e..74a4f630e 100644 --- a/examples/react-native/RunAnywhereAI/src/types/model.ts +++ b/examples/react-native/RunAnywhereAI/src/types/model.ts @@ -33,7 +33,6 @@ export enum ModelCategory { SpeechRecognition = 'speech-recognition', SpeechSynthesis = 'speech-synthesis', Vision = 'vision', - ImageGeneration = 'image-generation', Multimodal = 'multimodal', Audio = 'audio', } @@ -246,7 +245,6 @@ export const CategoryDisplayNames: Record = { [ModelCategory.SpeechRecognition]: 'Speech Recognition', [ModelCategory.SpeechSynthesis]: 'Text-to-Speech', [ModelCategory.Vision]: 'Vision Model', - [ModelCategory.ImageGeneration]: 'Image Generation', [ModelCategory.Multimodal]: 'Multimodal', [ModelCategory.Audio]: 'Audio Processing', }; diff --git a/examples/react-native/RunAnywhereAI/src/utils/mathParser.ts b/examples/react-native/RunAnywhereAI/src/utils/mathParser.ts new file mode 100644 index 000000000..0ed8bd231 --- /dev/null +++ b/examples/react-native/RunAnywhereAI/src/utils/mathParser.ts @@ -0,0 +1,182 @@ +/** + * Safe Math Expression Evaluator + * + * A secure alternative to eval() for math expression evaluation. + * Uses a recursive descent parser with proper operator precedence. + * + * Supports: + * - Numbers (integers and decimals) + * - Operators: +, -, *, / + * - Parentheses for grouping + * - Unary minus (negative numbers) + * + * Security: + * - No eval() or Function() usage + * - Tokenizes and validates input + * - Rejects invalid characters/expressions + * + * Example usage: + * safeEvaluateExpression("2 + 3 * 4") // Returns 14 + * safeEvaluateExpression("(2 + 3) * 4") // Returns 20 + * safeEvaluateExpression("-5 + 3") // Returns -2 + */ + +/** + * Tokenize the expression into numbers, operators, and parentheses + */ +const tokenize = (input: string): string[] => { + // Remove all whitespace + const cleaned = input.replace(/\s+/g, ''); + + // Match numbers (including decimals) and operators/parentheses + const tokens = cleaned.match(/(\d+\.?\d*|[()+\-*/])/g); + + if (!tokens) { + throw new Error('Invalid expression: no valid tokens found'); + } + + // Validate that we consumed the entire input + const reconstructed = tokens.join(''); + if (reconstructed !== cleaned) { + throw new Error('Invalid expression: contains invalid characters'); + } + + return tokens; +}; + +/** + * Safe math expression evaluator using recursive descent parsing + * + * Grammar: + * expression -> term (('+' | '-') term)* + * term -> factor (('*' | '/') factor)* + * factor -> '-' factor | '(' expression ')' | number + * + * @param input - Math expression string (e.g., "2 + 3 * 4") + * @returns The numeric result + * @throws Error if expression is invalid + */ +export const safeEvaluateExpression = (input: string): number => { + if (!input || typeof input !== 'string') { + throw new Error('Invalid expression: input must be a non-empty string'); + } + + const tokens = tokenize(input); + let pos = 0; + + /** + * Get the current token without advancing + */ + const peek = (): string | undefined => tokens[pos]; + + /** + * Consume and return the current token + */ + const consume = (): string | undefined => tokens[pos++]; + + /** + * Parse a factor: number, parenthesized expression, or unary minus + */ + const parseFactor = (): number => { + const token = peek(); + + if (token === undefined) { + throw new Error('Unexpected end of expression'); + } + + // Handle unary minus + if (token === '-') { + consume(); + return -parseFactor(); + } + + // Handle unary plus (optional, just skip it) + if (token === '+') { + consume(); + return parseFactor(); + } + + // Handle parenthesized expression + if (token === '(') { + consume(); // consume '(' + const value = parseExpression(); + if (peek() !== ')') { + throw new Error('Missing closing parenthesis'); + } + consume(); // consume ')' + return value; + } + + // Handle number + if (/^\d+\.?\d*$/.test(token)) { + consume(); + const value = parseFloat(token); + if (!Number.isFinite(value)) { + throw new Error(`Invalid number: ${token}`); + } + return value; + } + + throw new Error(`Unexpected token: ${token}`); + }; + + /** + * Parse a term: handles * and / with higher precedence + */ + const parseTerm = (): number => { + let value = parseFactor(); + + while (peek() === '*' || peek() === '/') { + const op = consume(); + const rhs = parseFactor(); + + if (op === '*') { + value *= rhs; + } else if (op === '/') { + if (rhs === 0) { + throw new Error('Division by zero'); + } + value /= rhs; + } + } + + return value; + }; + + /** + * Parse an expression: handles + and - with lower precedence + */ + const parseExpression = (): number => { + let value = parseTerm(); + + while (peek() === '+' || peek() === '-') { + const op = consume(); + const rhs = parseTerm(); + + if (op === '+') { + value += rhs; + } else if (op === '-') { + value -= rhs; + } + } + + return value; + }; + + // Parse the entire expression + const result = parseExpression(); + + // Ensure all tokens were consumed + if (pos !== tokens.length) { + throw new Error(`Unexpected token at position ${pos}: ${tokens[pos]}`); + } + + // Check for valid result + if (!Number.isFinite(result)) { + throw new Error('Result is not a finite number'); + } + + return result; +}; + +export default safeEvaluateExpression; diff --git a/sdk/runanywhere-commons/CMakeLists.txt b/sdk/runanywhere-commons/CMakeLists.txt index b958a86dd..021ce8038 100644 --- a/sdk/runanywhere-commons/CMakeLists.txt +++ b/sdk/runanywhere-commons/CMakeLists.txt @@ -163,7 +163,7 @@ set(RAC_INFRASTRUCTURE_SOURCES src/infrastructure/device/rac_device_manager.cpp ) -# Feature sources - LLM, STT, TTS, VAD service interfaces +# Feature sources - LLM, STT, TTS, VAD, VLM, Diffusion (iOS/Apple only) set(RAC_FEATURES_SOURCES # LLM src/features/llm/llm_component.cpp @@ -171,6 +171,7 @@ set(RAC_FEATURES_SOURCES src/features/llm/streaming_metrics.cpp src/features/llm/llm_analytics.cpp src/features/llm/structured_output.cpp + src/features/llm/tool_calling.cpp # STT src/features/stt/stt_component.cpp src/features/stt/rac_stt_service.cpp @@ -183,20 +184,39 @@ set(RAC_FEATURES_SOURCES src/features/vad/vad_component.cpp src/features/vad/energy_vad.cpp src/features/vad/vad_analytics.cpp + # VLM (Vision Language Model) + src/features/vlm/vlm_component.cpp + src/features/vlm/rac_vlm_service.cpp # Voice Agent src/features/voice_agent/voice_agent.cpp # Result memory management src/features/result_free.cpp ) +# Diffusion (Image Generation) - Apple only (CoreML/ANE). Not built for Android. +if(NOT RAC_PLATFORM_ANDROID) + list(APPEND RAC_FEATURES_SOURCES + src/features/diffusion/diffusion_component.cpp + src/features/diffusion/diffusion_json.cpp + src/features/diffusion/diffusion_model_registry.cpp + src/features/diffusion/rac_diffusion_service.cpp + src/features/diffusion/rac_diffusion_tokenizer.cpp + ) +endif() + +# Image utilities (for VLM) +set(RAC_UTILS_SOURCES + src/utils/rac_image_utils.cpp +) -# Platform services (Apple Foundation Models + System TTS) +# Platform services (Apple Foundation Models + System TTS + CoreML Diffusion) if(APPLE AND RAC_BUILD_PLATFORM) set(RAC_PLATFORM_SOURCES src/features/platform/rac_llm_platform.cpp src/features/platform/rac_tts_platform.cpp + src/features/platform/rac_diffusion_platform.cpp src/features/platform/rac_backend_platform_register.cpp ) - message(STATUS "Building platform services: Apple Foundation Models, System TTS") + message(STATUS "Building platform services: Apple Foundation Models, System TTS, CoreML Diffusion") else() set(RAC_PLATFORM_SOURCES "") endif() @@ -206,6 +226,7 @@ set(RAC_COMMONS_SOURCES ${RAC_CORE_SOURCES} ${RAC_INFRASTRUCTURE_SOURCES} ${RAC_FEATURES_SOURCES} + ${RAC_UTILS_SOURCES} ${RAC_PLATFORM_SOURCES} ) @@ -253,6 +274,7 @@ if(APPLE) endif() if(RAC_PLATFORM_ANDROID) + target_compile_definitions(rac_commons PRIVATE RAC_PLATFORM_ANDROID=1) target_link_libraries(rac_commons PUBLIC log) endif() @@ -332,7 +354,7 @@ message(STATUS "Components:") message(STATUS " Core: Logging, errors, events, memory") message(STATUS " Registry: Service & module registration") message(STATUS " Models: Download strategies, storage") -message(STATUS " Services: LLM, STT, TTS, VAD interfaces") +message(STATUS " Services: LLM, STT, TTS, VAD, Diffusion interfaces") if(APPLE AND RAC_BUILD_PLATFORM) message(STATUS " Platform: Apple Foundation Models, System TTS") endif() diff --git a/sdk/runanywhere-commons/VERSIONS b/sdk/runanywhere-commons/VERSIONS index a8cf68d28..f00da375e 100644 --- a/sdk/runanywhere-commons/VERSIONS +++ b/sdk/runanywhere-commons/VERSIONS @@ -56,6 +56,11 @@ ONNX_VERSION_LINUX=1.23.2 SHERPA_ONNX_VERSION_IOS=1.12.18 # Android version (v1.12.20+ has 16KB alignment for Play Store) +# โš ๏ธ IMPORTANT: When changing this version, you MUST re-download BOTH the +# prebuilt .so files AND the C API headers to avoid ABI/struct mismatch: +# rm -rf third_party/sherpa-onnx-android +# ./scripts/android/download-sherpa-onnx.sh +# The download script now auto-validates header vs .so version consistency. SHERPA_ONNX_VERSION_ANDROID=1.12.20 # macOS version diff --git a/sdk/runanywhere-commons/exports/RACommons.exports b/sdk/runanywhere-commons/exports/RACommons.exports index 6620aad34..700234e0b 100644 --- a/sdk/runanywhere-commons/exports/RACommons.exports +++ b/sdk/runanywhere-commons/exports/RACommons.exports @@ -140,6 +140,7 @@ _rac_model_paths_set_base_dir _rac_model_registry_create _rac_model_registry_destroy _rac_model_registry_get +_rac_model_registry_get_by_path _rac_model_registry_get_all _rac_model_registry_get_by_frameworks _rac_model_registry_get_downloaded @@ -148,6 +149,12 @@ _rac_model_registry_save _rac_model_registry_update_download_status _rac_model_registry_update_last_used +# Global Model Registry Convenience Functions +_rac_get_model_registry +_rac_register_model +_rac_get_model +_rac_get_model_by_path + # Model Assignment _rac_model_assignment_set_callbacks _rac_model_assignment_fetch diff --git a/sdk/runanywhere-commons/include/rac/backends/rac_vlm_llamacpp.h b/sdk/runanywhere-commons/include/rac/backends/rac_vlm_llamacpp.h new file mode 100644 index 000000000..b44071845 --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/backends/rac_vlm_llamacpp.h @@ -0,0 +1,216 @@ +/** + * @file rac_vlm_llamacpp.h + * @brief RunAnywhere Commons - LlamaCPP VLM Backend API + * + * Public C API for Vision Language Model inference using llama.cpp's + * multimodal (mtmd) capabilities. Supports 20+ VLM architectures including + * Qwen2-VL, Qwen2.5-VL, SmolVLM, LLaVA, MiniCPM-V, and more. + */ + +#ifndef RAC_VLM_LLAMACPP_H +#define RAC_VLM_LLAMACPP_H + +#include "rac/core/rac_error.h" +#include "rac/core/rac_types.h" +#include "rac/features/vlm/rac_vlm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// EXPORT MACRO +// ============================================================================= + +#if defined(RAC_LLAMACPP_BUILDING) +#if defined(_WIN32) +#define RAC_LLAMACPP_VLM_API __declspec(dllexport) +#elif defined(__GNUC__) || defined(__clang__) +#define RAC_LLAMACPP_VLM_API __attribute__((visibility("default"))) +#else +#define RAC_LLAMACPP_VLM_API +#endif +#else +#define RAC_LLAMACPP_VLM_API +#endif + +// ============================================================================= +// CONFIGURATION +// ============================================================================= + +/** + * LlamaCPP VLM-specific configuration. + */ +typedef struct rac_vlm_llamacpp_config { + /** Context size (0 = auto-detect from model) */ + int32_t context_size; + + /** Number of threads for CPU inference (0 = auto-detect) */ + int32_t num_threads; + + /** Number of layers to offload to GPU (Metal on iOS/macOS, -1 = all) */ + int32_t gpu_layers; + + /** Batch size for prompt processing */ + int32_t batch_size; + + /** Number of threads for vision encoder (0 = same as num_threads) */ + int32_t vision_threads; + + /** Use GPU for vision encoding */ + rac_bool_t use_gpu_vision; +} rac_vlm_llamacpp_config_t; + +/** + * Default LlamaCPP VLM configuration. + */ +static const rac_vlm_llamacpp_config_t RAC_VLM_LLAMACPP_CONFIG_DEFAULT = { + .context_size = 0, // Auto-detect + .num_threads = 0, // Auto-detect + .gpu_layers = -1, // All layers on GPU + .batch_size = 512, // + .vision_threads = 0, // Auto-detect + .use_gpu_vision = 1 // Use GPU for vision +}; + +// ============================================================================= +// LLAMACPP VLM-SPECIFIC API +// ============================================================================= + +/** + * Creates a LlamaCPP VLM service. + * + * @param model_path Path to the GGUF LLM model file + * @param mmproj_path Path to the mmproj vision projector GGUF file + * @param config LlamaCPP-specific configuration (can be NULL for defaults) + * @param out_handle Output: Handle to the created service + * @return RAC_SUCCESS or error code + */ +RAC_LLAMACPP_VLM_API rac_result_t rac_vlm_llamacpp_create(const char* model_path, + const char* mmproj_path, + const rac_vlm_llamacpp_config_t* config, + rac_handle_t* out_handle); + +/** + * Loads a VLM model into an existing service. + * + * @param handle Service handle + * @param model_path Path to the GGUF LLM model file + * @param mmproj_path Path to the mmproj vision projector GGUF file + * @param config LlamaCPP configuration (can be NULL) + * @return RAC_SUCCESS or error code + */ +RAC_LLAMACPP_VLM_API rac_result_t rac_vlm_llamacpp_load_model( + rac_handle_t handle, const char* model_path, const char* mmproj_path, + const rac_vlm_llamacpp_config_t* config); + +/** + * Unloads the current model. + * + * @param handle Service handle + * @return RAC_SUCCESS or error code + */ +RAC_LLAMACPP_VLM_API rac_result_t rac_vlm_llamacpp_unload_model(rac_handle_t handle); + +/** + * Checks if a model is loaded. + * + * @param handle Service handle + * @return RAC_TRUE if model is loaded, RAC_FALSE otherwise + */ +RAC_LLAMACPP_VLM_API rac_bool_t rac_vlm_llamacpp_is_model_loaded(rac_handle_t handle); + +/** + * Processes an image with a text prompt (blocking). + * + * @param handle Service handle + * @param image Image input (file path, RGB pixels, or base64) + * @param prompt Text prompt + * @param options VLM generation options (can be NULL for defaults) + * @param out_result Output: Generation result (caller must free text with rac_free) + * @return RAC_SUCCESS or error code + */ +RAC_LLAMACPP_VLM_API rac_result_t rac_vlm_llamacpp_process(rac_handle_t handle, + const rac_vlm_image_t* image, + const char* prompt, + const rac_vlm_options_t* options, + rac_vlm_result_t* out_result); + +/** + * Streaming callback for VLM generation. + * + * @param token Generated token string + * @param is_final Whether this is the final token + * @param user_data User-provided context + * @return RAC_TRUE to continue, RAC_FALSE to stop + */ +typedef rac_bool_t (*rac_vlm_llamacpp_stream_callback_fn)(const char* token, rac_bool_t is_final, + void* user_data); + +/** + * Processes an image with streaming callback. + * + * @param handle Service handle + * @param image Image input + * @param prompt Text prompt + * @param options VLM generation options + * @param callback Callback for each token + * @param user_data User context passed to callback + * @return RAC_SUCCESS or error code + */ +RAC_LLAMACPP_VLM_API rac_result_t rac_vlm_llamacpp_process_stream( + rac_handle_t handle, const rac_vlm_image_t* image, const char* prompt, + const rac_vlm_options_t* options, rac_vlm_llamacpp_stream_callback_fn callback, void* user_data); + +/** + * Cancels ongoing generation. + * + * @param handle Service handle + */ +RAC_LLAMACPP_VLM_API void rac_vlm_llamacpp_cancel(rac_handle_t handle); + +/** + * Gets model information as JSON. + * + * @param handle Service handle + * @param out_json Output: JSON string (caller must free with rac_free) + * @return RAC_SUCCESS or error code + */ +RAC_LLAMACPP_VLM_API rac_result_t rac_vlm_llamacpp_get_model_info(rac_handle_t handle, + char** out_json); + +/** + * Destroys a LlamaCPP VLM service. + * + * @param handle Service handle to destroy + */ +RAC_LLAMACPP_VLM_API void rac_vlm_llamacpp_destroy(rac_handle_t handle); + +// ============================================================================= +// BACKEND REGISTRATION +// ============================================================================= + +/** + * Registers the LlamaCPP VLM backend with the commons module and service registries. + * + * Should be called once during SDK initialization. + * This registers: + * - Module: "llamacpp_vlm" with VISION_LANGUAGE capability + * - Service provider: LlamaCPP VLM provider (priority 100) + * + * @return RAC_SUCCESS or error code + */ +RAC_LLAMACPP_VLM_API rac_result_t rac_backend_llamacpp_vlm_register(void); + +/** + * Unregisters the LlamaCPP VLM backend. + * + * @return RAC_SUCCESS or error code + */ +RAC_LLAMACPP_VLM_API rac_result_t rac_backend_llamacpp_vlm_unregister(void); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_VLM_LLAMACPP_H */ diff --git a/sdk/runanywhere-commons/include/rac/core/capabilities/rac_lifecycle.h b/sdk/runanywhere-commons/include/rac/core/capabilities/rac_lifecycle.h index 754d691ff..273f45e50 100644 --- a/sdk/runanywhere-commons/include/rac/core/capabilities/rac_lifecycle.h +++ b/sdk/runanywhere-commons/include/rac/core/capabilities/rac_lifecycle.h @@ -45,7 +45,9 @@ typedef enum rac_resource_type { RAC_RESOURCE_TYPE_STT_MODEL = 1, RAC_RESOURCE_TYPE_TTS_VOICE = 2, RAC_RESOURCE_TYPE_VAD_MODEL = 3, - RAC_RESOURCE_TYPE_DIARIZATION_MODEL = 4 + RAC_RESOURCE_TYPE_DIARIZATION_MODEL = 4, + RAC_RESOURCE_TYPE_VLM_MODEL = 5, /**< Vision Language Model */ + RAC_RESOURCE_TYPE_DIFFUSION_MODEL = 6 /**< Diffusion/Image Generation Model */ } rac_resource_type_t; /** diff --git a/sdk/runanywhere-commons/include/rac/core/rac_core.h b/sdk/runanywhere-commons/include/rac/core/rac_core.h index 6f6314df1..854bfe062 100644 --- a/sdk/runanywhere-commons/include/rac/core/rac_core.h +++ b/sdk/runanywhere-commons/include/rac/core/rac_core.h @@ -324,6 +324,17 @@ RAC_API rac_result_t rac_register_model(const struct rac_model_info* model); */ RAC_API rac_result_t rac_get_model(const char* model_id, struct rac_model_info** out_model); +/** + * Gets model info from the global registry by local path. + * Convenience function that calls rac_model_registry_get_by_path on the global registry. + * Useful when loading models by path instead of model_id. + * + * @param local_path Local path to search for + * @param out_model Output: Model info (owned, must be freed with rac_model_info_free) + * @return RAC_SUCCESS on success, RAC_ERROR_NOT_FOUND if not registered + */ +RAC_API rac_result_t rac_get_model_by_path(const char* local_path, struct rac_model_info** out_model); + #ifdef __cplusplus } #endif diff --git a/sdk/runanywhere-commons/include/rac/core/rac_types.h b/sdk/runanywhere-commons/include/rac/core/rac_types.h index bd03a66c4..793371d5d 100644 --- a/sdk/runanywhere-commons/include/rac/core/rac_types.h +++ b/sdk/runanywhere-commons/include/rac/core/rac_types.h @@ -166,12 +166,14 @@ typedef struct rac_memory_info { */ typedef enum rac_capability { RAC_CAPABILITY_UNKNOWN = 0, - RAC_CAPABILITY_TEXT_GENERATION = 1, /**< LLM text generation */ - RAC_CAPABILITY_EMBEDDINGS = 2, /**< Text embeddings */ - RAC_CAPABILITY_STT = 3, /**< Speech-to-text */ - RAC_CAPABILITY_TTS = 4, /**< Text-to-speech */ - RAC_CAPABILITY_VAD = 5, /**< Voice activity detection */ - RAC_CAPABILITY_DIARIZATION = 6, /**< Speaker diarization */ + RAC_CAPABILITY_TEXT_GENERATION = 1, /**< LLM text generation */ + RAC_CAPABILITY_EMBEDDINGS = 2, /**< Text embeddings */ + RAC_CAPABILITY_STT = 3, /**< Speech-to-text */ + RAC_CAPABILITY_TTS = 4, /**< Text-to-speech */ + RAC_CAPABILITY_VAD = 5, /**< Voice activity detection */ + RAC_CAPABILITY_DIARIZATION = 6, /**< Speaker diarization */ + RAC_CAPABILITY_VISION_LANGUAGE = 7, /**< Vision-language model (VLM) */ + RAC_CAPABILITY_DIFFUSION = 8, /**< Image generation (Stable Diffusion) */ } rac_capability_t; /** diff --git a/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion.h b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion.h new file mode 100644 index 000000000..64ae206c7 --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion.h @@ -0,0 +1,22 @@ +/** + * @file rac_diffusion.h + * @brief RunAnywhere Commons - Diffusion Feature Umbrella Header + * + * Include this header to use diffusion (image generation) capabilities. + * This provides text-to-image, image-to-image, and inpainting using + * Stable Diffusion models. + * + * Supported backends: + * - CoreML (Apple platforms) - via Platform backend + * - ONNX Runtime (cross-platform) - via ONNX backend + */ + +#ifndef RAC_DIFFUSION_H +#define RAC_DIFFUSION_H + +#include "rac/features/diffusion/rac_diffusion_component.h" +#include "rac/features/diffusion/rac_diffusion_service.h" +#include "rac/features/diffusion/rac_diffusion_tokenizer.h" +#include "rac/features/diffusion/rac_diffusion_types.h" + +#endif /* RAC_DIFFUSION_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_component.h b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_component.h new file mode 100644 index 000000000..55e004f11 --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_component.h @@ -0,0 +1,263 @@ +/** + * @file rac_diffusion_component.h + * @brief RunAnywhere Commons - Diffusion Capability Component + * + * Actor-based diffusion capability that owns model lifecycle and generation. + * Uses lifecycle manager for unified lifecycle + analytics handling. + * + * Supports: + * - Text-to-image generation + * - Image-to-image transformation + * - Inpainting with mask + * - Progress reporting with optional intermediate images + */ + +#ifndef RAC_DIFFUSION_COMPONENT_H +#define RAC_DIFFUSION_COMPONENT_H + +#include "rac/core/capabilities/rac_lifecycle.h" +#include "rac/core/rac_error.h" +#include "rac/features/diffusion/rac_diffusion_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// DIFFUSION COMPONENT API - Component lifecycle and generation +// ============================================================================= + +/** + * @brief Create a diffusion capability component + * + * @param out_handle Output: Handle to the component + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_create(rac_handle_t* out_handle); + +/** + * @brief Configure the diffusion component + * + * @param handle Component handle + * @param config Configuration + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_configure(rac_handle_t handle, + const rac_diffusion_config_t* config); + +/** + * @brief Check if model is loaded + * + * @param handle Component handle + * @return RAC_TRUE if loaded, RAC_FALSE otherwise + */ +RAC_API rac_bool_t rac_diffusion_component_is_loaded(rac_handle_t handle); + +/** + * @brief Get current model ID + * + * @param handle Component handle + * @return Current model ID (NULL if not loaded) + */ +RAC_API const char* rac_diffusion_component_get_model_id(rac_handle_t handle); + +/** + * @brief Load a diffusion model + * + * @param handle Component handle + * @param model_path Path to the model directory + * @param model_id Model identifier for telemetry + * @param model_name Human-readable model name + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_load_model(rac_handle_t handle, const char* model_path, + const char* model_id, + const char* model_name); + +/** + * @brief Unload the current model + * + * @param handle Component handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_unload(rac_handle_t handle); + +/** + * @brief Cleanup and reset the component + * + * @param handle Component handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_cleanup(rac_handle_t handle); + +/** + * @brief Cancel ongoing generation + * + * Best-effort cancellation. + * + * @param handle Component handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_cancel(rac_handle_t handle); + +/** + * @brief Generate an image (non-streaming) + * + * Blocking call that generates an image from the prompt. + * + * @param handle Component handle + * @param options Generation options + * @param out_result Output: Generation result + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_generate(rac_handle_t handle, + const rac_diffusion_options_t* options, + rac_diffusion_result_t* out_result); + +/** + * @brief Generate an image with progress callbacks + * + * Non-blocking call with progress reporting via callbacks. + * + * @param handle Component handle + * @param options Generation options + * @param progress_callback Called for each progress update + * @param complete_callback Called when generation completes + * @param error_callback Called on error + * @param user_data User context passed to callbacks + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_generate_with_callbacks( + rac_handle_t handle, const rac_diffusion_options_t* options, + rac_diffusion_progress_callback_fn progress_callback, + rac_diffusion_complete_callback_fn complete_callback, + rac_diffusion_error_callback_fn error_callback, void* user_data); + +// ============================================================================= +// JSON CONVENIENCE HELPERS +// ============================================================================= + +/** + * @brief Configure diffusion component from JSON + * + * JSON schema (flat object): + * { + * "model_id": "optional-model-id", + * "model_variant": 0 | "sd15" | "sd21" | "sdxl" | "sdxl_turbo" | "sdxs" | "lcm", + * "enable_safety_checker": true/false, + * "reduce_memory": true/false, + * "tokenizer_source": 0 | 1 | 2 | 99, + * "tokenizer_custom_url": "https://..." + * } + * + * @param handle Component handle + * @param config_json JSON string + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_configure_json(rac_handle_t handle, + const char* config_json); + +/** + * @brief Generate image from JSON options + * + * JSON schema (flat object): + * { + * "prompt": "text prompt", + * "negative_prompt": "optional", + * "width": 512, + * "height": 512, + * "steps": 28, + * "guidance_scale": 7.5, + * "seed": -1, + * "scheduler": 0 | "dpm++_2m_karras" | "dpm++_2m" | "dpm++_2m_sde" | "ddim" | "euler" | "euler_a" | "pndm" | "lms", + * "mode": 0 | "txt2img" | "img2img" | "inpainting", + * "denoise_strength": 0.75, + * "report_intermediate_images": false, + * "progress_stride": 1 + * } + * + * @param handle Component handle + * @param options_json JSON string + * @param input_image_data Optional input image bytes (PNG/JPEG or RGBA) + * @param input_image_size Size of input image bytes + * @param mask_data Optional mask image bytes (PNG/JPEG or grayscale) + * @param mask_size Size of mask bytes + * @param out_json Output JSON (caller must free with rac_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_generate_json( + rac_handle_t handle, const char* options_json, const uint8_t* input_image_data, + size_t input_image_size, const uint8_t* mask_data, size_t mask_size, char** out_json); + +/** + * @brief Get diffusion info as JSON + * + * Output schema: + * { + * "is_ready": true/false, + * "current_model": "id", + * "model_variant": 0, + * "supports_text_to_image": true/false, + * "supports_image_to_image": true/false, + * "supports_inpainting": true/false, + * "safety_checker_enabled": true/false, + * "max_width": 512, + * "max_height": 512 + * } + * + * @param handle Component handle + * @param out_json Output JSON (caller must free with rac_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_get_info_json(rac_handle_t handle, char** out_json); + +/** + * @brief Get supported capabilities + * + * Returns a bitmask of supported capabilities. + * + * @param handle Component handle + * @return Capability bitmask (RAC_DIFFUSION_CAP_* flags) + */ +RAC_API uint32_t rac_diffusion_component_get_capabilities(rac_handle_t handle); + +/** + * @brief Get service information + * + * @param handle Component handle + * @param out_info Output: Service information + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_get_info(rac_handle_t handle, + rac_diffusion_info_t* out_info); + +/** + * @brief Get lifecycle state + * + * @param handle Component handle + * @return Current lifecycle state + */ +RAC_API rac_lifecycle_state_t rac_diffusion_component_get_state(rac_handle_t handle); + +/** + * @brief Get lifecycle metrics + * + * @param handle Component handle + * @param out_metrics Output: Lifecycle metrics + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_component_get_metrics(rac_handle_t handle, + rac_lifecycle_metrics_t* out_metrics); + +/** + * @brief Destroy the diffusion component + * + * @param handle Component handle + */ +RAC_API void rac_diffusion_component_destroy(rac_handle_t handle); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_DIFFUSION_COMPONENT_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_model_registry.h b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_model_registry.h new file mode 100644 index 000000000..6a200a5cb --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_model_registry.h @@ -0,0 +1,358 @@ +/** + * @file rac_diffusion_model_registry.h + * @brief Diffusion Model Registry - CoreML-based model definitions for iOS/macOS + * + * Provides a registry for diffusion models. Currently supports CoreML backend only + * (iOS/macOS with Apple Neural Engine acceleration). + * + * Features: + * - Type-safe model definitions (no magic strings) + * - CoreML backend with ANE โ†’ GPU โ†’ CPU automatic fallback + * - Strategy pattern for extensibility + * - Tokenizer source configuration (SD 1.5, SD 2.x, SDXL) + */ + +#ifndef RAC_DIFFUSION_MODEL_REGISTRY_H +#define RAC_DIFFUSION_MODEL_REGISTRY_H + +#include "rac/core/rac_types.h" +#include "rac/features/diffusion/rac_diffusion_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// BACKEND AND PLATFORM TYPES +// ============================================================================= + +/** + * @brief Supported inference backends for diffusion models + * + * Currently only CoreML is implemented for iOS/macOS. + * Other backends are reserved for future expansion. + */ +typedef enum rac_diffusion_backend { + RAC_DIFFUSION_BACKEND_ONNX = 0, /**< ONNX Runtime (reserved for future) */ + RAC_DIFFUSION_BACKEND_COREML = 1, /**< CoreML (iOS/macOS - currently supported) */ + RAC_DIFFUSION_BACKEND_TFLITE = 2, /**< TensorFlow Lite (reserved for future) */ + RAC_DIFFUSION_BACKEND_AUTO = 99 /**< Auto-select (defaults to CoreML on Apple) */ +} rac_diffusion_backend_t; + +/** + * @brief Platform availability flags (bitmask) + * + * Used to specify which platforms a model supports. + */ +typedef enum rac_diffusion_platform_flags { + RAC_DIFFUSION_PLATFORM_NONE = 0, + RAC_DIFFUSION_PLATFORM_IOS = (1 << 0), + RAC_DIFFUSION_PLATFORM_ANDROID = (1 << 1), + RAC_DIFFUSION_PLATFORM_MACOS = (1 << 2), + RAC_DIFFUSION_PLATFORM_WINDOWS = (1 << 3), + RAC_DIFFUSION_PLATFORM_LINUX = (1 << 4), + RAC_DIFFUSION_PLATFORM_ALL = 0xFFFF +} rac_diffusion_platform_flags_t; + +/** + * @brief Hardware acceleration capabilities + * + * Describes what hardware the model can utilize. + */ +typedef enum rac_diffusion_hardware { + RAC_DIFFUSION_HW_CPU = (1 << 0), /**< CPU (always available) */ + RAC_DIFFUSION_HW_GPU = (1 << 1), /**< GPU acceleration */ + RAC_DIFFUSION_HW_ANE = (1 << 2), /**< Apple Neural Engine */ + RAC_DIFFUSION_HW_NPU = (1 << 3), /**< Android NPU (Hexagon, etc.) */ + RAC_DIFFUSION_HW_DSP = (1 << 4), /**< Android DSP */ +} rac_diffusion_hardware_t; + +// ============================================================================= +// MODEL DEFINITION STRUCTURE +// ============================================================================= + +/** + * @brief Default generation parameters for a model + */ +typedef struct rac_diffusion_model_defaults { + int32_t width; /**< Default output width */ + int32_t height; /**< Default output height */ + int32_t steps; /**< Recommended inference steps */ + float guidance_scale; /**< CFG scale (0.0 for CFG-free models) */ + rac_diffusion_scheduler_t scheduler; /**< Recommended scheduler */ + rac_bool_t requires_cfg; /**< True if model needs CFG (false for SDXS/Turbo) */ +} rac_diffusion_model_defaults_t; + +/** + * @brief Download information for a model + */ +typedef struct rac_diffusion_model_download { + const char* base_url; /**< HuggingFace URL or CDN */ + const char* onnx_path; /**< Path to ONNX files within repo */ + const char* coreml_path; /**< Path to CoreML files (if available) */ + uint64_t size_bytes; /**< Approximate download size */ + const char* checksum; /**< SHA256 checksum (optional) */ +} rac_diffusion_model_download_t; + +/** + * @brief Tokenizer information for a model + */ +typedef struct rac_diffusion_model_tokenizer { + rac_diffusion_tokenizer_source_t source; /**< Tokenizer type */ + const char* custom_url; /**< For custom tokenizers */ +} rac_diffusion_model_tokenizer_t; + +/** + * @brief Complete diffusion model definition + * + * Contains all metadata needed to download, load, and use a model. + * This structure is shared across all SDKs via the C++ commons layer. + * + * ## Adding a New Model + * + * To add a new diffusion model: + * 1. Add a new `rac_diffusion_model_def_t` in `diffusion_model_registry.cpp` + * 2. Include it in the `BUILTIN_MODELS` array + * 3. Set the appropriate tokenizer source (SD15, SD2, SDXL, or CUSTOM) + * + * Example: + * @code + * static const rac_diffusion_model_def_t MY_MODEL = { + * .model_id = "my-model-onnx", + * .display_name = "My Custom Model", + * .description = "Description here", + * .variant = RAC_DIFFUSION_MODEL_SD_1_5, + * .backend = RAC_DIFFUSION_BACKEND_ONNX, + * .platforms = RAC_DIFFUSION_PLATFORM_ALL, + * .hardware = RAC_DIFFUSION_HW_CPU | RAC_DIFFUSION_HW_GPU, + * .defaults = { .width = 512, .height = 512, .steps = 20, ... }, + * .download = { + * .base_url = "https://huggingface.co/my-org/my-model", + * .onnx_path = "onnx", + * .size_bytes = 2000000000ULL, + * }, + * .tokenizer = { + * .source = RAC_DIFFUSION_TOKENIZER_SD_1_5, // Reuse existing tokenizer + * }, + * }; + * @endcode + */ +typedef struct rac_diffusion_model_def { + /** Unique model identifier (e.g., "sdxs-512-0.9-onnx") */ + const char* model_id; + + /** Human-readable name */ + const char* display_name; + + /** Description */ + const char* description; + + /** Model variant (SD 1.5, SDXL, SDXS, LCM, etc.) */ + rac_diffusion_model_variant_t variant; + + /** Preferred backend for this model */ + rac_diffusion_backend_t backend; + + /** Platform availability (bitmask of rac_diffusion_platform_t) */ + uint32_t platforms; + + /** Hardware capabilities (bitmask of rac_diffusion_hardware_t) */ + uint32_t hardware; + + /** Default generation parameters */ + rac_diffusion_model_defaults_t defaults; + + /** Download information */ + rac_diffusion_model_download_t download; + + /** Tokenizer information */ + rac_diffusion_model_tokenizer_t tokenizer; + + /** Model-specific flags */ + rac_bool_t is_recommended; /**< Show as recommended in UI */ + rac_bool_t supports_img2img; /**< Supports image-to-image */ + rac_bool_t supports_inpainting; /**< Supports inpainting */ + +} rac_diffusion_model_def_t; + +// ============================================================================= +// MODEL STRATEGY INTERFACE +// ============================================================================= + +/** + * @brief Model strategy - allows custom model handling + * + * Contributors implement this interface to add support for new model types + * without modifying core SDK code. + * + * Example: + * @code + * static rac_bool_t my_can_handle(const char* model_id, void* user_data) { + * return strcmp(model_id, "my-custom-model") == 0 ? RAC_TRUE : RAC_FALSE; + * } + * + * static rac_result_t my_get_model_def(const char* model_id, + * rac_diffusion_model_def_t* out_def, + * void* user_data) { + * if (strcmp(model_id, "my-custom-model") == 0) { + * *out_def = MY_CUSTOM_MODEL_DEF; + * return RAC_SUCCESS; + * } + * return RAC_ERROR_NOT_FOUND; + * } + * + * void register_my_models(void) { + * static rac_diffusion_model_strategy_t strategy = { + * .name = "MyModels", + * .can_handle = my_can_handle, + * .get_model_def = my_get_model_def, + * // ... + * }; + * rac_diffusion_model_registry_register(&strategy); + * } + * @endcode + */ +typedef struct rac_diffusion_model_strategy { + /** Strategy name (e.g., "SDXS", "LCM", "CustomModel") */ + const char* name; + + /** Check if this strategy can handle a model ID */ + rac_bool_t (*can_handle)(const char* model_id, void* user_data); + + /** Get model definition for a model ID */ + rac_result_t (*get_model_def)(const char* model_id, + rac_diffusion_model_def_t* out_def, + void* user_data); + + /** Get all models supported by this strategy */ + rac_result_t (*list_models)(rac_diffusion_model_def_t** out_models, + size_t* out_count, + void* user_data); + + /** Select best backend for current platform */ + rac_diffusion_backend_t (*select_backend)(const rac_diffusion_model_def_t* model, + void* user_data); + + /** Optional: Custom model loading (if default isn't suitable) */ + rac_result_t (*load_model)(const char* model_path, + const rac_diffusion_model_def_t* model_def, + rac_handle_t* out_service, + void* user_data); + + /** User data passed to callbacks */ + void* user_data; + +} rac_diffusion_model_strategy_t; + +// ============================================================================= +// REGISTRY API +// ============================================================================= + +/** + * @brief Initialize the diffusion model registry + * + * Registers built-in model strategies (SD 1.5, SDXS, LCM, etc.) + * Must be called during SDK initialization. + */ +RAC_API void rac_diffusion_model_registry_init(void); + +/** + * @brief Cleanup the diffusion model registry + */ +RAC_API void rac_diffusion_model_registry_cleanup(void); + +/** + * @brief Register a model strategy + * + * @param strategy Strategy to register (caller retains ownership) + * @return RAC_SUCCESS on success, RAC_ERROR_ALREADY_EXISTS if name taken + */ +RAC_API rac_result_t rac_diffusion_model_registry_register( + const rac_diffusion_model_strategy_t* strategy); + +/** + * @brief Unregister a model strategy + * + * @param name Strategy name to unregister + * @return RAC_SUCCESS on success, RAC_ERROR_NOT_FOUND if not registered + */ +RAC_API rac_result_t rac_diffusion_model_registry_unregister(const char* name); + +/** + * @brief Get model definition by ID + * + * @param model_id Model identifier + * @param out_def Output model definition (filled on success) + * @return RAC_SUCCESS if found, RAC_ERROR_NOT_FOUND otherwise + */ +RAC_API rac_result_t rac_diffusion_model_registry_get( + const char* model_id, + rac_diffusion_model_def_t* out_def); + +/** + * @brief List all available models for current platform + * + * @param out_models Output array (caller must free with free()) + * @param out_count Number of models + * @return RAC_SUCCESS on success + */ +RAC_API rac_result_t rac_diffusion_model_registry_list( + rac_diffusion_model_def_t** out_models, + size_t* out_count); + +/** + * @brief Select best backend for a model on current platform + * + * This function implements the fallback chain: + * - iOS/macOS: CoreML (ANE โ†’ GPU โ†’ CPU automatic via CoreML) + * - Android: ONNX with NNAPI EP (NPU โ†’ DSP โ†’ GPU โ†’ CPU automatic via NNAPI) + * - Desktop: ONNX with CPU EP + * + * @param model_id Model identifier + * @return Best backend, or RAC_DIFFUSION_BACKEND_ONNX as fallback + */ +RAC_API rac_diffusion_backend_t rac_diffusion_model_registry_select_backend( + const char* model_id); + +/** + * @brief Check if a model is available on current platform + * + * @param model_id Model identifier + * @return RAC_TRUE if available, RAC_FALSE otherwise + */ +RAC_API rac_bool_t rac_diffusion_model_registry_is_available(const char* model_id); + +/** + * @brief Get recommended model for current platform + * + * Returns the model marked as is_recommended=true that's available on + * the current platform. + * + * @param out_def Output model definition (filled on success) + * @return RAC_SUCCESS if found, RAC_ERROR_NOT_FOUND if no recommendation + */ +RAC_API rac_result_t rac_diffusion_model_registry_get_recommended( + rac_diffusion_model_def_t* out_def); + +/** + * @brief Get current platform flags + * + * @return Bitmask of current platform (e.g., RAC_DIFFUSION_PLATFORM_IOS) + */ +RAC_API uint32_t rac_diffusion_model_registry_get_current_platform(void); + +/** + * @brief Check if model variant requires CFG (classifier-free guidance) + * + * SDXS, SDXL Turbo, and similar distilled models don't need CFG. + * + * @param variant Model variant + * @return RAC_TRUE if CFG is required, RAC_FALSE for CFG-free models + */ +RAC_API rac_bool_t rac_diffusion_model_requires_cfg(rac_diffusion_model_variant_t variant); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_DIFFUSION_MODEL_REGISTRY_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_service.h b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_service.h new file mode 100644 index 000000000..24ffc1f41 --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_service.h @@ -0,0 +1,187 @@ +/** + * @file rac_diffusion_service.h + * @brief RunAnywhere Commons - Diffusion Service Interface + * + * Defines the generic diffusion service API and vtable for multi-backend dispatch. + * Backends (CoreML, ONNX, Platform) implement the vtable and register + * with the service registry. + */ + +#ifndef RAC_DIFFUSION_SERVICE_H +#define RAC_DIFFUSION_SERVICE_H + +#include "rac/core/rac_error.h" +#include "rac/features/diffusion/rac_diffusion_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// SERVICE VTABLE - Backend implementations provide this +// ============================================================================= + +/** + * Diffusion Service operations vtable. + * Each backend implements these functions and provides a static vtable. + */ +typedef struct rac_diffusion_service_ops { + /** Initialize the service with a model path */ + rac_result_t (*initialize)(void* impl, const char* model_path, + const rac_diffusion_config_t* config); + + /** Generate image (blocking) */ + rac_result_t (*generate)(void* impl, const rac_diffusion_options_t* options, + rac_diffusion_result_t* out_result); + + /** Generate image with progress callback */ + rac_result_t (*generate_with_progress)(void* impl, const rac_diffusion_options_t* options, + rac_diffusion_progress_callback_fn progress_callback, + void* user_data, rac_diffusion_result_t* out_result); + + /** Get service info */ + rac_result_t (*get_info)(void* impl, rac_diffusion_info_t* out_info); + + /** Get supported capabilities as bitmask */ + uint32_t (*get_capabilities)(void* impl); + + /** Cancel ongoing generation */ + rac_result_t (*cancel)(void* impl); + + /** Cleanup/unload model (keeps service alive) */ + rac_result_t (*cleanup)(void* impl); + + /** Destroy the service */ + void (*destroy)(void* impl); +} rac_diffusion_service_ops_t; + +/** + * Diffusion Service instance. + * Contains vtable pointer and backend-specific implementation. + */ +typedef struct rac_diffusion_service { + /** Vtable with backend operations */ + const rac_diffusion_service_ops_t* ops; + + /** Backend-specific implementation handle */ + void* impl; + + /** Model ID for reference */ + const char* model_id; +} rac_diffusion_service_t; + +// ============================================================================= +// PUBLIC API - Generic service functions +// ============================================================================= + +/** + * @brief Create a diffusion service + * + * Routes through service registry to find appropriate backend. + * + * @param model_id Model identifier (registry ID or path to model) + * @param out_handle Output: Handle to the created service + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_create(const char* model_id, rac_handle_t* out_handle); + +/** + * @brief Create a diffusion service with configuration + * + * Routes through service registry to find appropriate backend, honoring + * configuration hints such as preferred framework when provided. + * + * @param model_id Model identifier (registry ID or path to model) + * @param config Optional configuration (can be NULL) + * @param out_handle Output: Handle to the created service + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_create_with_config(const char* model_id, + const rac_diffusion_config_t* config, + rac_handle_t* out_handle); + +/** + * @brief Initialize a diffusion service + * + * @param handle Service handle + * @param model_path Path to the model directory + * @param config Configuration (can be NULL for defaults) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_initialize(rac_handle_t handle, const char* model_path, + const rac_diffusion_config_t* config); + +/** + * @brief Generate an image from prompt + * + * Blocking call that generates an image. + * + * @param handle Service handle + * @param options Generation options + * @param out_result Output: Generation result (caller must free with rac_diffusion_result_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_generate(rac_handle_t handle, + const rac_diffusion_options_t* options, + rac_diffusion_result_t* out_result); + +/** + * @brief Generate an image with progress reporting + * + * @param handle Service handle + * @param options Generation options + * @param progress_callback Callback for progress updates + * @param user_data User context passed to callback + * @param out_result Output: Generation result (caller must free with rac_diffusion_result_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_generate_with_progress( + rac_handle_t handle, const rac_diffusion_options_t* options, + rac_diffusion_progress_callback_fn progress_callback, void* user_data, + rac_diffusion_result_t* out_result); + +/** + * @brief Get service information + * + * @param handle Service handle + * @param out_info Output: Service information + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_get_info(rac_handle_t handle, rac_diffusion_info_t* out_info); + +/** + * @brief Get supported capabilities as bitmask + * + * @param handle Service handle + * @return Capability bitmask (RAC_DIFFUSION_CAP_* flags) + */ +RAC_API uint32_t rac_diffusion_get_capabilities(rac_handle_t handle); + +/** + * @brief Cancel ongoing generation + * + * @param handle Service handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_cancel(rac_handle_t handle); + +/** + * @brief Cleanup and release model resources + * + * @param handle Service handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_cleanup(rac_handle_t handle); + +/** + * @brief Destroy a diffusion service instance + * + * @param handle Service handle to destroy + */ +RAC_API void rac_diffusion_destroy(rac_handle_t handle); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_DIFFUSION_SERVICE_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_tokenizer.h b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_tokenizer.h new file mode 100644 index 000000000..6b405258b --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_tokenizer.h @@ -0,0 +1,167 @@ +/** + * @file rac_diffusion_tokenizer.h + * @brief RunAnywhere Commons - Diffusion Tokenizer Utilities + * + * Utilities for managing diffusion model tokenizer files. + * Apple's compiled CoreML models don't include tokenizer files (vocab.json, merges.txt), + * so they must be downloaded from HuggingFace. + * + * This API provides: + * - URL resolution for predefined tokenizer sources + * - Automatic download of missing tokenizer files + * - Support for custom tokenizer URLs + */ + +#ifndef RAC_DIFFUSION_TOKENIZER_H +#define RAC_DIFFUSION_TOKENIZER_H + +#include "rac/core/rac_types.h" +#include "rac/features/diffusion/rac_diffusion_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// TOKENIZER FILE NAMES +// ============================================================================= + +/** Vocabulary file name */ +#define RAC_DIFFUSION_TOKENIZER_VOCAB_FILE "vocab.json" + +/** Merge rules file name */ +#define RAC_DIFFUSION_TOKENIZER_MERGES_FILE "merges.txt" + +// ============================================================================= +// URL RESOLUTION +// ============================================================================= + +/** + * @brief Get the base URL for a tokenizer source + * + * Returns the HuggingFace URL for the specified tokenizer source. + * For custom sources, returns the custom_base_url from config. + * + * @param source Tokenizer source preset + * @param custom_url Custom URL (only used when source == RAC_DIFFUSION_TOKENIZER_CUSTOM) + * @return Base URL string (static, do not free), or NULL if invalid + * + * @note URLs returned are HuggingFace raw file URLs (resolve/main/tokenizer) + * + * Example return values: + * - SD_1_5: "https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/tokenizer" + * - SD_2_X: "https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/tokenizer" + * - SDXL: "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/tokenizer" + * - CUSTOM: Returns custom_url parameter + */ +RAC_API const char* rac_diffusion_tokenizer_get_base_url(rac_diffusion_tokenizer_source_t source, + const char* custom_url); + +/** + * @brief Get the full URL for a tokenizer file + * + * Constructs the full URL for downloading a specific tokenizer file. + * + * @param source Tokenizer source preset + * @param custom_url Custom URL (only used when source == RAC_DIFFUSION_TOKENIZER_CUSTOM) + * @param filename File name to append (e.g., "vocab.json" or "merges.txt") + * @param out_url Output buffer for the full URL + * @param out_url_size Size of output buffer + * @return RAC_SUCCESS or error code + * + * Example: + * @code + * char url[512]; + * rac_diffusion_tokenizer_get_file_url( + * RAC_DIFFUSION_TOKENIZER_SD_1_5, + * NULL, + * "vocab.json", + * url, + * sizeof(url) + * ); + * // url = "https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/tokenizer/vocab.json" + * @endcode + */ +RAC_API rac_result_t rac_diffusion_tokenizer_get_file_url(rac_diffusion_tokenizer_source_t source, + const char* custom_url, + const char* filename, char* out_url, + size_t out_url_size); + +// ============================================================================= +// FILE MANAGEMENT +// ============================================================================= + +/** + * @brief Check if tokenizer files exist in a directory + * + * @param model_dir Path to the model directory + * @param out_has_vocab Output: true if vocab.json exists + * @param out_has_merges Output: true if merges.txt exists + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_tokenizer_check_files(const char* model_dir, + rac_bool_t* out_has_vocab, + rac_bool_t* out_has_merges); + +/** + * @brief Ensure tokenizer files exist, downloading if necessary + * + * Checks for vocab.json and merges.txt in the model directory. + * If missing and auto_download is enabled, downloads from the configured source. + * + * @param model_dir Path to the model directory + * @param config Tokenizer configuration (source, custom URL, auto_download) + * @return RAC_SUCCESS if files exist or were downloaded successfully + * RAC_ERROR_FILE_NOT_FOUND if files missing and auto_download disabled + * RAC_ERROR_NETWORK if download failed + * + * Example: + * @code + * rac_diffusion_tokenizer_config_t config = { + * .source = RAC_DIFFUSION_TOKENIZER_SD_1_5, + * .custom_base_url = NULL, + * .auto_download = RAC_TRUE + * }; + * rac_result_t result = rac_diffusion_tokenizer_ensure_files("/path/to/model", &config); + * @endcode + */ +RAC_API rac_result_t +rac_diffusion_tokenizer_ensure_files(const char* model_dir, + const rac_diffusion_tokenizer_config_t* config); + +/** + * @brief Download a tokenizer file + * + * Downloads a specific tokenizer file from the configured source. + * + * @param source Tokenizer source preset + * @param custom_url Custom URL (only used when source == RAC_DIFFUSION_TOKENIZER_CUSTOM) + * @param filename File name to download (e.g., "vocab.json") + * @param output_path Full path where the file should be saved + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_diffusion_tokenizer_download_file(rac_diffusion_tokenizer_source_t source, + const char* custom_url, + const char* filename, + const char* output_path); + +// ============================================================================= +// DEFAULT TOKENIZER SOURCE FOR MODEL VARIANT +// ============================================================================= + +/** + * @brief Get the default tokenizer source for a model variant + * + * Returns the recommended tokenizer source for a given model variant. + * + * @param model_variant Model variant (SD 1.5, SD 2.1, SDXL, etc.) + * @return Default tokenizer source + */ +RAC_API rac_diffusion_tokenizer_source_t +rac_diffusion_tokenizer_default_for_variant(rac_diffusion_model_variant_t model_variant); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_DIFFUSION_TOKENIZER_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_types.h b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_types.h new file mode 100644 index 000000000..ed3901d1d --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/diffusion/rac_diffusion_types.h @@ -0,0 +1,454 @@ +/** + * @file rac_diffusion_types.h + * @brief RunAnywhere Commons - Diffusion Types and Data Structures + * + * This header defines data structures for image generation using diffusion models + * (Stable Diffusion). Supports text-to-image, image-to-image, and inpainting. + * + * This header defines data structures only. For the service interface, + * see rac_diffusion_service.h. + */ + +#ifndef RAC_DIFFUSION_TYPES_H +#define RAC_DIFFUSION_TYPES_H + +#include "rac/core/rac_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// SCHEDULER TYPES +// ============================================================================= + +/** + * @brief Diffusion scheduler/sampler types + * + * Different scheduling algorithms for the denoising process. + * DPM++ 2M Karras is recommended for best quality/speed tradeoff. + */ +typedef enum rac_diffusion_scheduler { + RAC_DIFFUSION_SCHEDULER_DPM_PP_2M_KARRAS = 0, /**< DPM++ 2M Karras (recommended) */ + RAC_DIFFUSION_SCHEDULER_DPM_PP_2M = 1, /**< DPM++ 2M */ + RAC_DIFFUSION_SCHEDULER_DPM_PP_2M_SDE = 2, /**< DPM++ 2M SDE */ + RAC_DIFFUSION_SCHEDULER_DDIM = 3, /**< DDIM */ + RAC_DIFFUSION_SCHEDULER_EULER = 4, /**< Euler */ + RAC_DIFFUSION_SCHEDULER_EULER_ANCESTRAL = 5, /**< Euler Ancestral */ + RAC_DIFFUSION_SCHEDULER_PNDM = 6, /**< PNDM */ + RAC_DIFFUSION_SCHEDULER_LMS = 7, /**< LMS */ +} rac_diffusion_scheduler_t; + +/** + * @brief Model variant types + * + * Different Stable Diffusion model variants with different capabilities. + */ +typedef enum rac_diffusion_model_variant { + RAC_DIFFUSION_MODEL_SD_1_5 = 0, /**< Stable Diffusion 1.5 (512x512 default) */ + RAC_DIFFUSION_MODEL_SD_2_1 = 1, /**< Stable Diffusion 2.1 (768x768 default) */ + RAC_DIFFUSION_MODEL_SDXL = 2, /**< SDXL (1024x1024 default, requires 8GB+ RAM) */ + RAC_DIFFUSION_MODEL_SDXL_TURBO = 3, /**< SDXL Turbo (fast, fewer steps, no CFG) */ + RAC_DIFFUSION_MODEL_SDXS = 4, /**< SDXS - Ultra-fast 1-step model (no CFG) */ + RAC_DIFFUSION_MODEL_LCM = 5, /**< LCM - Latent Consistency Model (4 steps) */ +} rac_diffusion_model_variant_t; + +/** + * @brief Generation mode + */ +typedef enum rac_diffusion_mode { + RAC_DIFFUSION_MODE_TEXT_TO_IMAGE = 0, /**< Generate image from text prompt */ + RAC_DIFFUSION_MODE_IMAGE_TO_IMAGE = 1, /**< Transform input image with prompt */ + RAC_DIFFUSION_MODE_INPAINTING = 2, /**< Edit specific regions with mask */ +} rac_diffusion_mode_t; + +// ============================================================================= +// TOKENIZER CONFIGURATION +// ============================================================================= + +/** + * @brief Tokenizer source presets + * + * Predefined HuggingFace repository sources for tokenizer files. + * Apple's compiled CoreML models don't include tokenizer files (vocab.json, merges.txt), + * so they must be downloaded separately from HuggingFace. + * + * Developers can use RAC_DIFFUSION_TOKENIZER_CUSTOM with a custom_base_url + * to specify their own tokenizer source. + */ +typedef enum rac_diffusion_tokenizer_source { + /** Stable Diffusion 1.x tokenizer (CLIP ViT-L/14) + * Source: runwayml/stable-diffusion-v1-5 */ + RAC_DIFFUSION_TOKENIZER_SD_1_5 = 0, + + /** Stable Diffusion 2.x tokenizer (OpenCLIP ViT-H/14) + * Source: stabilityai/stable-diffusion-2-1 */ + RAC_DIFFUSION_TOKENIZER_SD_2_X = 1, + + /** Stable Diffusion XL tokenizer (dual tokenizers) + * Source: stabilityai/stable-diffusion-xl-base-1.0 */ + RAC_DIFFUSION_TOKENIZER_SDXL = 2, + + /** Custom tokenizer from a developer-specified URL + * Requires custom_base_url to be set in rac_diffusion_tokenizer_config_t */ + RAC_DIFFUSION_TOKENIZER_CUSTOM = 99, +} rac_diffusion_tokenizer_source_t; + +/** + * @brief Tokenizer configuration + * + * Configuration for downloading and using tokenizer files. + * The SDK will automatically download missing tokenizer files (vocab.json, merges.txt) + * from the specified source URL. + * + * Example for custom URL: + * @code + * rac_diffusion_tokenizer_config_t tokenizer_config = { + * .source = RAC_DIFFUSION_TOKENIZER_CUSTOM, + * .custom_base_url = "https://huggingface.co/my-org/my-model/resolve/main/tokenizer", + * .auto_download = RAC_TRUE + * }; + * @endcode + */ +typedef struct rac_diffusion_tokenizer_config { + /** Tokenizer source preset (SD15, SD21, SDXL, or CUSTOM) */ + rac_diffusion_tokenizer_source_t source; + + /** Custom base URL for tokenizer files (only used when source == CUSTOM) + * Should be a URL directory containing vocab.json and merges.txt + * Example: "https://huggingface.co/my-org/my-model/resolve/main/tokenizer" + * The SDK will append "/vocab.json" and "/merges.txt" to download files */ + const char* custom_base_url; + + /** Automatically download missing tokenizer files (default: true) */ + rac_bool_t auto_download; +} rac_diffusion_tokenizer_config_t; + +/** + * @brief Default tokenizer configuration + */ +static const rac_diffusion_tokenizer_config_t RAC_DIFFUSION_TOKENIZER_CONFIG_DEFAULT = { + .source = RAC_DIFFUSION_TOKENIZER_SD_1_5, + .custom_base_url = RAC_NULL, + .auto_download = RAC_TRUE}; + +// ============================================================================= +// CONFIGURATION - Component configuration +// ============================================================================= + +/** + * @brief Diffusion component configuration + * + * Configuration for initializing the diffusion component. + */ +typedef struct rac_diffusion_config { + /** Model ID (optional - uses default if NULL) */ + const char* model_id; + + /** Preferred framework (use RAC_FRAMEWORK_UNKNOWN for auto) */ + int32_t preferred_framework; + + /** Model variant (SD 1.5, SD 2.1, SDXL, etc.) */ + rac_diffusion_model_variant_t model_variant; + + /** Enable safety checker for NSFW content filtering (default: true) */ + rac_bool_t enable_safety_checker; + + /** Reduce memory footprint (may reduce quality, default: false) */ + rac_bool_t reduce_memory; + + /** Tokenizer configuration for downloading missing tokenizer files + * Apple's compiled CoreML models don't include tokenizer files */ + rac_diffusion_tokenizer_config_t tokenizer; +} rac_diffusion_config_t; + +/** + * @brief Default diffusion configuration + */ +static const rac_diffusion_config_t RAC_DIFFUSION_CONFIG_DEFAULT = { + .model_id = RAC_NULL, + .preferred_framework = 99, // RAC_FRAMEWORK_UNKNOWN + .model_variant = RAC_DIFFUSION_MODEL_SD_1_5, + .enable_safety_checker = RAC_TRUE, + .reduce_memory = RAC_FALSE, + .tokenizer = {.source = RAC_DIFFUSION_TOKENIZER_SD_1_5, + .custom_base_url = RAC_NULL, + .auto_download = RAC_TRUE}}; + +// ============================================================================= +// OPTIONS - Generation options +// ============================================================================= + +/** + * @brief Diffusion generation options + * + * Options for controlling image generation. + */ +typedef struct rac_diffusion_options { + /** Text prompt describing the desired image */ + const char* prompt; + + /** Negative prompt - things to avoid in the image (can be NULL) */ + const char* negative_prompt; + + /** Output image width in pixels (default: 512 for SD 1.5, 1024 for SDXL) */ + int32_t width; + + /** Output image height in pixels (default: 512 for SD 1.5, 1024 for SDXL) */ + int32_t height; + + /** Number of denoising steps (default: 28, range: 10-50) */ + int32_t steps; + + /** Classifier-free guidance scale (default: 7.5, range: 1.0-20.0) */ + float guidance_scale; + + /** Random seed for reproducibility (-1 for random, default: -1) */ + int64_t seed; + + /** Scheduler/sampler algorithm (default: DPM++ 2M Karras) */ + rac_diffusion_scheduler_t scheduler; + + // --- Image-to-image / Inpainting options --- + + /** Generation mode (text-to-image, img2img, inpainting) */ + rac_diffusion_mode_t mode; + + /** Input image RGBA data for img2img/inpainting (can be NULL) */ + const uint8_t* input_image_data; + + /** Input image data size in bytes */ + size_t input_image_size; + + /** Input image width (required if input_image_data is set) */ + int32_t input_image_width; + + /** Input image height (required if input_image_data is set) */ + int32_t input_image_height; + + /** Mask image data for inpainting - grayscale (can be NULL) */ + const uint8_t* mask_data; + + /** Mask data size in bytes */ + size_t mask_size; + + /** Denoising strength for img2img (0.0-1.0, default: 0.75) */ + float denoise_strength; + + // --- Progress reporting options --- + + /** Report intermediate images during generation (default: false) */ + rac_bool_t report_intermediate_images; + + /** Report progress every N steps (default: 1) */ + int32_t progress_stride; +} rac_diffusion_options_t; + +/** + * @brief Default diffusion generation options + */ +static const rac_diffusion_options_t RAC_DIFFUSION_OPTIONS_DEFAULT = { + .prompt = RAC_NULL, + .negative_prompt = RAC_NULL, + .width = 512, + .height = 512, + .steps = 28, + .guidance_scale = 7.5f, + .seed = -1, + .scheduler = RAC_DIFFUSION_SCHEDULER_DPM_PP_2M_KARRAS, + .mode = RAC_DIFFUSION_MODE_TEXT_TO_IMAGE, + .input_image_data = RAC_NULL, + .input_image_size = 0, + .input_image_width = 0, + .input_image_height = 0, + .mask_data = RAC_NULL, + .mask_size = 0, + .denoise_strength = 0.75f, + .report_intermediate_images = RAC_FALSE, + .progress_stride = 1}; + +// ============================================================================= +// PROGRESS - Generation progress +// ============================================================================= + +/** + * @brief Diffusion generation progress + * + * Reports progress during image generation. + */ +typedef struct rac_diffusion_progress { + /** Progress percentage (0.0 - 1.0) */ + float progress; + + /** Current step number (1-based) */ + int32_t current_step; + + /** Total number of steps */ + int32_t total_steps; + + /** Current stage description (e.g., "Encoding", "Denoising", "Decoding") */ + const char* stage; + + /** Intermediate image RGBA data (can be NULL if not requested) */ + const uint8_t* intermediate_image_data; + + /** Intermediate image data size */ + size_t intermediate_image_size; + + /** Intermediate image width */ + int32_t intermediate_image_width; + + /** Intermediate image height */ + int32_t intermediate_image_height; +} rac_diffusion_progress_t; + +// ============================================================================= +// RESULT - Generation result +// ============================================================================= + +/** + * @brief Diffusion generation result + * + * Contains the generated image and metadata. + */ +typedef struct rac_diffusion_result { + /** Generated image RGBA data (owned, must be freed with rac_diffusion_result_free) */ + uint8_t* image_data; + + /** Image data size in bytes */ + size_t image_size; + + /** Image width in pixels */ + int32_t width; + + /** Image height in pixels */ + int32_t height; + + /** Seed used for generation (useful for reproducibility) */ + int64_t seed_used; + + /** Total generation time in milliseconds */ + int64_t generation_time_ms; + + /** Whether the image was flagged by safety checker */ + rac_bool_t safety_flagged; + + /** Error code if generation failed (RAC_SUCCESS on success) */ + rac_result_t error_code; + + /** Error message if generation failed (can be NULL) */ + char* error_message; +} rac_diffusion_result_t; + +// ============================================================================= +// INFO - Service information +// ============================================================================= + +/** + * @brief Diffusion service information + * + * Information about the loaded diffusion service. + */ +typedef struct rac_diffusion_info { + /** Whether the service is ready for generation */ + rac_bool_t is_ready; + + /** Current model identifier (can be NULL) */ + const char* current_model; + + /** Model variant */ + rac_diffusion_model_variant_t model_variant; + + /** Whether text-to-image is supported */ + rac_bool_t supports_text_to_image; + + /** Whether image-to-image is supported */ + rac_bool_t supports_image_to_image; + + /** Whether inpainting is supported */ + rac_bool_t supports_inpainting; + + /** Whether safety checker is enabled */ + rac_bool_t safety_checker_enabled; + + /** Maximum supported width */ + int32_t max_width; + + /** Maximum supported height */ + int32_t max_height; +} rac_diffusion_info_t; + +// ============================================================================= +// CALLBACKS +// ============================================================================= + +/** + * @brief Diffusion progress callback + * + * Called during generation to report progress. + * + * @param progress Progress information + * @param user_data User-provided context + * @return RAC_TRUE to continue, RAC_FALSE to cancel generation + */ +typedef rac_bool_t (*rac_diffusion_progress_callback_fn)(const rac_diffusion_progress_t* progress, + void* user_data); + +/** + * @brief Diffusion completion callback + * + * Called when generation completes successfully. + * + * @param result Generation result + * @param user_data User-provided context + */ +typedef void (*rac_diffusion_complete_callback_fn)(const rac_diffusion_result_t* result, + void* user_data); + +/** + * @brief Diffusion error callback + * + * Called when generation fails. + * + * @param error_code Error code + * @param error_message Error message + * @param user_data User-provided context + */ +typedef void (*rac_diffusion_error_callback_fn)(rac_result_t error_code, const char* error_message, + void* user_data); + +// ============================================================================= +// CAPABILITY FLAGS +// ============================================================================= + +/** Supports text-to-image generation */ +#define RAC_DIFFUSION_CAP_TEXT_TO_IMAGE (1 << 0) + +/** Supports image-to-image transformation */ +#define RAC_DIFFUSION_CAP_IMAGE_TO_IMAGE (1 << 1) + +/** Supports inpainting with mask */ +#define RAC_DIFFUSION_CAP_INPAINTING (1 << 2) + +/** Supports intermediate image reporting */ +#define RAC_DIFFUSION_CAP_INTERMEDIATE_IMAGES (1 << 3) + +/** Has safety checker */ +#define RAC_DIFFUSION_CAP_SAFETY_CHECKER (1 << 4) + +// ============================================================================= +// MEMORY MANAGEMENT +// ============================================================================= + +/** + * @brief Free diffusion result resources + * + * @param result Result to free (can be NULL) + */ +RAC_API void rac_diffusion_result_free(rac_diffusion_result_t* result); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_DIFFUSION_TYPES_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/llm/rac_tool_calling.h b/sdk/runanywhere-commons/include/rac/features/llm/rac_tool_calling.h new file mode 100644 index 000000000..a6819e069 --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/llm/rac_tool_calling.h @@ -0,0 +1,373 @@ +/** + * @file rac_tool_calling.h + * @brief RunAnywhere Commons - Tool Calling API + * + * *** SINGLE SOURCE OF TRUTH FOR ALL TOOL CALLING LOGIC *** + * + * This header provides ALL tool calling functionality. Platform SDKs should + * ONLY call these functions - no fallback implementations allowed. + * + * Architecture: + * - C++ handles: ALL parsing, prompt formatting, JSON handling, follow-up prompts + * - Platform SDKs handle ONLY: tool registry (closures), tool execution (needs platform APIs) + * + * Supported Tool Calling Formats: + * - DEFAULT: {"tool":"name","arguments":{}} (Most general models) + * - LFM2: <|tool_call_start|>[func(arg="val")]<|tool_call_end|> (Liquid AI models) + * + * Ported from: + * - Swift: ToolCallParser.swift + * - React Native: ToolCallingBridge.cpp + */ + +#ifndef RAC_TOOL_CALLING_H +#define RAC_TOOL_CALLING_H + +#include "rac/core/rac_error.h" +#include "rac/core/rac_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// TOOL CALLING FORMATS - Different models use different formats +// ============================================================================= + +/** + * @brief Tool calling format identifiers + * + * Different LLM models use different tool calling formats. This enum allows + * specifying which format to use for parsing and prompt generation. + */ +typedef enum rac_tool_call_format { + /** + * @brief SDK Default format: JSON + * + * Format: {"tool": "name", "arguments": {...}} + * Used by: Most general-purpose models (Llama, Qwen, Mistral, etc.) + */ + RAC_TOOL_FORMAT_DEFAULT = 0, + + /** + * @brief Liquid AI LFM2-Tool format + * + * Format: <|tool_call_start|>[func_name(arg1="val1", arg2="val2")]<|tool_call_end|> + * Used by: LiquidAI/LFM2-1.2B-Tool, LiquidAI/LFM2-350M-Tool + * Note: Uses Pythonic function call syntax + */ + RAC_TOOL_FORMAT_LFM2 = 1, + + /** Number of formats (for iteration) */ + RAC_TOOL_FORMAT_COUNT +} rac_tool_call_format_t; + +// ============================================================================= +// TYPES - Canonical definitions used by all SDKs +// ============================================================================= + +/** + * @brief Parameter types for tool arguments + */ +typedef enum rac_tool_param_type { + RAC_TOOL_PARAM_STRING = 0, + RAC_TOOL_PARAM_NUMBER = 1, + RAC_TOOL_PARAM_BOOLEAN = 2, + RAC_TOOL_PARAM_OBJECT = 3, + RAC_TOOL_PARAM_ARRAY = 4 +} rac_tool_param_type_t; + +/** + * @brief Tool parameter definition + */ +typedef struct rac_tool_parameter { + const char* name; /**< Parameter name */ + rac_tool_param_type_t type; /**< Data type */ + const char* description; /**< Human-readable description */ + rac_bool_t required; /**< Whether required */ + const char* enum_values; /**< JSON array of allowed values (can be NULL) */ +} rac_tool_parameter_t; + +/** + * @brief Tool definition + */ +typedef struct rac_tool_definition { + const char* name; /**< Unique tool name (e.g., "get_weather") */ + const char* description; /**< What the tool does */ + const rac_tool_parameter_t* parameters; /**< Array of parameters */ + size_t num_parameters; /**< Number of parameters */ + const char* category; /**< Optional category (can be NULL) */ +} rac_tool_definition_t; + +/** + * @brief Parsed tool call from LLM output + */ +typedef struct rac_tool_call { + rac_bool_t has_tool_call; /**< Whether a tool call was found */ + char* tool_name; /**< Name of tool to execute (owned, must free) */ + char* arguments_json; /**< Arguments as JSON string (owned, must free) */ + char* clean_text; /**< Text without tool call tags (owned, must free) */ + int64_t call_id; /**< Unique call ID for tracking */ + rac_tool_call_format_t format; /**< Format that was detected/used for parsing */ +} rac_tool_call_t; + +/** + * @brief Tool calling options + */ +typedef struct rac_tool_calling_options { + int32_t max_tool_calls; /**< Max tool calls per turn (default: 5) */ + rac_bool_t auto_execute; /**< Auto-execute tools (default: true) */ + float temperature; /**< Generation temperature */ + int32_t max_tokens; /**< Max tokens to generate */ + const char* system_prompt; /**< Optional system prompt */ + rac_bool_t replace_system_prompt; /**< Replace vs append tool instructions */ + rac_bool_t keep_tools_available; /**< Keep tools after first call */ + rac_tool_call_format_t format; /**< Tool calling format (default: AUTO) */ +} rac_tool_calling_options_t; + +/** + * @brief Default tool calling options + */ +#define RAC_TOOL_CALLING_OPTIONS_DEFAULT \ + { \ + 5, /* max_tool_calls */ \ + 1, /* auto_execute = true */ \ + 0.7f, /* temperature */ \ + 1024, /* max_tokens */ \ + RAC_NULL, /* system_prompt */ \ + 0, /* replace_system_prompt = false */ \ + 0, /* keep_tools_available = false */ \ + RAC_TOOL_FORMAT_DEFAULT /* format */ \ + } + +// ============================================================================= +// PARSING API - Single Source of Truth (NO FALLBACKS) +// ============================================================================= + +/** + * @brief Parse LLM output for tool calls (auto-detect format) + * + * *** THIS IS THE ONLY PARSING IMPLEMENTATION - ALL SDKS MUST USE THIS *** + * + * Auto-detects the tool calling format by checking for format-specific tags. + * Handles ALL edge cases for each format. + * + * @param llm_output Raw LLM output text + * @param out_result Output: Parsed result (caller must free with rac_tool_call_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_parse(const char* llm_output, rac_tool_call_t* out_result); + +/** + * @brief Parse LLM output for tool calls with specified format + * + * Parses using a specific format. + * + * Supported formats: + * - RAC_TOOL_FORMAT_DEFAULT: JSON + * - RAC_TOOL_FORMAT_LFM2: <|tool_call_start|>[func(args)]<|tool_call_end|> + * + * @param llm_output Raw LLM output text + * @param format Tool calling format to use + * @param out_result Output: Parsed result (caller must free with rac_tool_call_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_parse_with_format(const char* llm_output, + rac_tool_call_format_t format, + rac_tool_call_t* out_result); + +/** + * @brief Free tool call result + * @param result Result to free + */ +RAC_API void rac_tool_call_free(rac_tool_call_t* result); + +/** + * @brief Get the human-readable name of a tool calling format + * + * @param format The format to get the name for + * @return Static string with the format name (do not free) + */ +RAC_API const char* rac_tool_call_format_name(rac_tool_call_format_t format); + +/** + * @brief Detect which format is present in LLM output + * + * Checks for format-specific markers without fully parsing. + * Returns RAC_TOOL_FORMAT_DEFAULT if no recognizable format is found. + * + * @param llm_output Raw LLM output text + * @return Detected format, or RAC_TOOL_FORMAT_DEFAULT if none detected + */ +RAC_API rac_tool_call_format_t rac_tool_call_detect_format(const char* llm_output); + +/** + * @brief Convert format name string to format enum + * + * This is the SINGLE SOURCE OF TRUTH for valid format names. + * SDKs should pass strings and let C++ handle the conversion. + * + * Valid names (case-insensitive): "default", "lfm2" + * + * @param name Format name string + * @return Corresponding format enum, or RAC_TOOL_FORMAT_DEFAULT if unknown + */ +RAC_API rac_tool_call_format_t rac_tool_call_format_from_name(const char* name); + +// ============================================================================= +// PROMPT FORMATTING API - All prompt building happens here +// ============================================================================= + +/** + * @brief Format tool definitions into system prompt (default format) + * + * Creates instruction text describing available tools and expected output format. + * Uses RAC_TOOL_FORMAT_DEFAULT (JSON). + * + * @param definitions Array of tool definitions + * @param num_definitions Number of definitions + * @param out_prompt Output: Allocated prompt string (caller must free with rac_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_format_prompt(const rac_tool_definition_t* definitions, + size_t num_definitions, char** out_prompt); + +/** + * @brief Format tool definitions with specified format + * + * Creates instruction text using the specified tool calling format. + * Each format has different tag patterns and syntax instructions. + * + * @param definitions Array of tool definitions + * @param num_definitions Number of definitions + * @param format Tool calling format to use for instructions + * @param out_prompt Output: Allocated prompt string (caller must free with rac_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_format_prompt_with_format(const rac_tool_definition_t* definitions, + size_t num_definitions, + rac_tool_call_format_t format, + char** out_prompt); + +/** + * @brief Format tools from JSON array string (default format) + * + * Convenience function when tools are provided as JSON. + * + * @param tools_json JSON array of tool definitions + * @param out_prompt Output: Allocated prompt string (caller must free with rac_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_format_prompt_json(const char* tools_json, char** out_prompt); + +/** + * @brief Format tools from JSON array string with specified format + * + * @param tools_json JSON array of tool definitions + * @param format Tool calling format to use for instructions + * @param out_prompt Output: Allocated prompt string (caller must free with rac_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_format_prompt_json_with_format(const char* tools_json, + rac_tool_call_format_t format, + char** out_prompt); + +/** + * @brief Format tools from JSON array string with format specified by name + * + * *** PREFERRED API FOR SDKS - Uses string format name *** + * + * Valid format names (case-insensitive): "default", "lfm2" + * Unknown names default to "default" format. + * + * @param tools_json JSON array of tool definitions + * @param format_name Format name string (e.g., "lfm2", "default") + * @param out_prompt Output: Allocated prompt string (caller must free with rac_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_format_prompt_json_with_format_name(const char* tools_json, + const char* format_name, + char** out_prompt); + +/** + * @brief Build the initial prompt with tools and user query + * + * Combines system prompt, tool instructions, and user prompt. + * + * @param user_prompt The user's question/request + * @param tools_json JSON array of tool definitions + * @param options Tool calling options (can be NULL for defaults) + * @param out_prompt Output: Complete formatted prompt (caller must free with rac_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_build_initial_prompt(const char* user_prompt, + const char* tools_json, + const rac_tool_calling_options_t* options, + char** out_prompt); + +/** + * @brief Build follow-up prompt after tool execution + * + * Creates the prompt to continue generation after a tool was executed. + * Handles both keepToolsAvailable=true and keepToolsAvailable=false cases. + * + * @param original_user_prompt The original user prompt + * @param tools_prompt The formatted tools prompt (can be NULL if not keeping tools) + * @param tool_name Name of the tool that was executed + * @param tool_result_json JSON string of the tool result + * @param keep_tools_available Whether to include tool definitions in follow-up + * @param out_prompt Output: Follow-up prompt (caller must free with rac_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_build_followup_prompt(const char* original_user_prompt, + const char* tools_prompt, + const char* tool_name, + const char* tool_result_json, + rac_bool_t keep_tools_available, + char** out_prompt); + +// ============================================================================= +// JSON UTILITY API - All JSON handling happens here +// ============================================================================= + +/** + * @brief Normalize JSON by adding quotes around unquoted keys + * + * Handles common LLM output patterns: {tool: "name"} โ†’ {"tool": "name"} + * + * @param json_str Input JSON string + * @param out_normalized Output: Normalized JSON (caller must free with rac_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_normalize_json(const char* json_str, char** out_normalized); + +/** + * @brief Serialize tool definitions to JSON array + * + * @param definitions Array of tool definitions + * @param num_definitions Number of definitions + * @param out_json Output: JSON array string (caller must free with rac_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_definitions_to_json(const rac_tool_definition_t* definitions, + size_t num_definitions, char** out_json); + +/** + * @brief Serialize a tool result to JSON + * + * @param tool_name Name of the tool + * @param success Whether execution succeeded + * @param result_json Result data as JSON (can be NULL) + * @param error_message Error message if failed (can be NULL) + * @param out_json Output: JSON string (caller must free with rac_free) + * @return RAC_SUCCESS on success, error code otherwise + */ +RAC_API rac_result_t rac_tool_call_result_to_json(const char* tool_name, rac_bool_t success, + const char* result_json, + const char* error_message, char** out_json); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_TOOL_CALLING_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/platform/rac_diffusion_platform.h b/sdk/runanywhere-commons/include/rac/features/platform/rac_diffusion_platform.h new file mode 100644 index 000000000..075b8fa76 --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/platform/rac_diffusion_platform.h @@ -0,0 +1,305 @@ +/** + * @file rac_diffusion_platform.h + * @brief RunAnywhere Commons - Platform Diffusion Backend (Apple ml-stable-diffusion) + * + * C API for platform-native diffusion services. On Apple platforms, this uses + * ml-stable-diffusion with Core ML. The actual implementation is in Swift, + * with C++ providing the registration and callback infrastructure. + * + * This backend follows the same pattern as LlamaCPP and ONNX backends, + * but delegates to Swift via function pointer callbacks since + * ml-stable-diffusion is a Swift-only framework. + */ + +#ifndef RAC_DIFFUSION_PLATFORM_H +#define RAC_DIFFUSION_PLATFORM_H + +#include "rac/core/rac_types.h" +#include "rac/features/diffusion/rac_diffusion_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// TYPES +// ============================================================================= + +/** Opaque handle to platform diffusion service */ +typedef struct rac_diffusion_platform* rac_diffusion_platform_handle_t; + +/** + * Platform diffusion configuration. + * Passed during initialization. + */ +typedef struct rac_diffusion_platform_config { + /** Model variant (SD 1.5, SDXL, etc.) */ + rac_diffusion_model_variant_t model_variant; + + /** Enable safety checker */ + rac_bool_t enable_safety_checker; + + /** Reduce memory mode */ + rac_bool_t reduce_memory; + + /** Compute units to use (0 = auto, 1 = CPU, 2 = GPU, 3 = Neural Engine) */ + int32_t compute_units; + + /** Reserved for future use */ + void* reserved; +} rac_diffusion_platform_config_t; + +/** + * Generation options for platform diffusion. + */ +typedef struct rac_diffusion_platform_options { + /** Text prompt */ + const char* prompt; + + /** Negative prompt */ + const char* negative_prompt; + + /** Output width */ + int32_t width; + + /** Output height */ + int32_t height; + + /** Number of inference steps */ + int32_t steps; + + /** Guidance scale */ + float guidance_scale; + + /** Random seed (-1 for random) */ + int64_t seed; + + /** Scheduler type */ + rac_diffusion_scheduler_t scheduler; + + /** Reserved for future options */ + void* reserved; +} rac_diffusion_platform_options_t; + +/** + * Platform diffusion result. + */ +typedef struct rac_diffusion_platform_result { + /** Image data (RGBA format, caller must free) */ + uint8_t* image_data; + + /** Image data size in bytes */ + size_t image_size; + + /** Image width */ + int32_t width; + + /** Image height */ + int32_t height; + + /** Seed used for generation */ + int64_t seed_used; + + /** Whether safety check was triggered */ + rac_bool_t safety_triggered; +} rac_diffusion_platform_result_t; + +// ============================================================================= +// SWIFT CALLBACK TYPES +// ============================================================================= + +/** + * Callback to check if platform diffusion can handle a model ID. + * Implemented in Swift. + * + * @param model_id Model identifier to check (can be NULL) + * @param user_data User-provided context + * @return RAC_TRUE if this backend can handle the model + */ +typedef rac_bool_t (*rac_platform_diffusion_can_handle_fn)(const char* model_id, void* user_data); + +/** + * Callback to create platform diffusion service. + * Implemented in Swift. + * + * @param model_path Path to model directory + * @param config Configuration options + * @param user_data User-provided context + * @return Handle to created service (Swift object pointer), or NULL on failure + */ +typedef rac_handle_t (*rac_platform_diffusion_create_fn)( + const char* model_path, const rac_diffusion_platform_config_t* config, void* user_data); + +/** + * Callback to generate image. + * Implemented in Swift. + * + * @param handle Service handle from create + * @param options Generation options + * @param out_result Output: Generated image result + * @param user_data User-provided context + * @return RAC_SUCCESS or error code + */ +typedef rac_result_t (*rac_platform_diffusion_generate_fn)( + rac_handle_t handle, const rac_diffusion_platform_options_t* options, + rac_diffusion_platform_result_t* out_result, void* user_data); + +/** + * Progress callback type for Swift. + * + * @param progress Progress value (0.0-1.0) + * @param step Current step + * @param total_steps Total steps + * @param user_data User-provided context + * @return RAC_TRUE to continue, RAC_FALSE to cancel + */ +typedef rac_bool_t (*rac_platform_diffusion_progress_fn)(float progress, int32_t step, + int32_t total_steps, void* user_data); + +/** + * Callback to generate image with progress. + * Implemented in Swift. + * + * @param handle Service handle from create + * @param options Generation options + * @param progress_callback Progress callback + * @param progress_user_data User data for progress callback + * @param out_result Output: Generated image result + * @param user_data User-provided context + * @return RAC_SUCCESS or error code + */ +typedef rac_result_t (*rac_platform_diffusion_generate_with_progress_fn)( + rac_handle_t handle, const rac_diffusion_platform_options_t* options, + rac_platform_diffusion_progress_fn progress_callback, void* progress_user_data, + rac_diffusion_platform_result_t* out_result, void* user_data); + +/** + * Callback to cancel generation. + * Implemented in Swift. + * + * @param handle Service handle + * @param user_data User-provided context + * @return RAC_SUCCESS or error code + */ +typedef rac_result_t (*rac_platform_diffusion_cancel_fn)(rac_handle_t handle, void* user_data); + +/** + * Callback to destroy platform diffusion service. + * Implemented in Swift. + * + * @param handle Service handle to destroy + * @param user_data User-provided context + */ +typedef void (*rac_platform_diffusion_destroy_fn)(rac_handle_t handle, void* user_data); + +/** + * Swift callbacks for platform diffusion operations. + */ +typedef struct rac_platform_diffusion_callbacks { + rac_platform_diffusion_can_handle_fn can_handle; + rac_platform_diffusion_create_fn create; + rac_platform_diffusion_generate_fn generate; + rac_platform_diffusion_generate_with_progress_fn generate_with_progress; + rac_platform_diffusion_cancel_fn cancel; + rac_platform_diffusion_destroy_fn destroy; + void* user_data; +} rac_platform_diffusion_callbacks_t; + +// ============================================================================= +// CALLBACK REGISTRATION +// ============================================================================= + +/** + * Sets the Swift callbacks for platform diffusion operations. + * Must be called before using platform diffusion services. + * + * @param callbacks Callback functions (copied internally) + * @return RAC_SUCCESS on success + */ +RAC_API rac_result_t rac_platform_diffusion_set_callbacks( + const rac_platform_diffusion_callbacks_t* callbacks); + +/** + * Gets the current Swift callbacks. + * + * @return Pointer to callbacks, or NULL if not set + */ +RAC_API const rac_platform_diffusion_callbacks_t* rac_platform_diffusion_get_callbacks(void); + +/** + * Checks if Swift callbacks are registered. + * + * @return RAC_TRUE if callbacks are available + */ +RAC_API rac_bool_t rac_platform_diffusion_is_available(void); + +// ============================================================================= +// SERVICE API +// ============================================================================= + +/** + * Creates a platform diffusion service. + * + * @param model_path Path to Core ML model directory + * @param config Configuration options (can be NULL for defaults) + * @param out_handle Output: Service handle + * @return RAC_SUCCESS on success, or error code + */ +RAC_API rac_result_t rac_diffusion_platform_create(const char* model_path, + const rac_diffusion_platform_config_t* config, + rac_diffusion_platform_handle_t* out_handle); + +/** + * Destroys a platform diffusion service. + * + * @param handle Service handle to destroy + */ +RAC_API void rac_diffusion_platform_destroy(rac_diffusion_platform_handle_t handle); + +/** + * Generates an image using platform diffusion. + * + * @param handle Service handle + * @param options Generation options + * @param out_result Output: Generated image + * @return RAC_SUCCESS on success, or error code + */ +RAC_API rac_result_t rac_diffusion_platform_generate( + rac_diffusion_platform_handle_t handle, const rac_diffusion_platform_options_t* options, + rac_diffusion_platform_result_t* out_result); + +/** + * Generates an image with progress reporting. + * + * @param handle Service handle + * @param options Generation options + * @param progress_callback Progress callback + * @param progress_user_data User data for progress callback + * @param out_result Output: Generated image + * @return RAC_SUCCESS on success, or error code + */ +RAC_API rac_result_t rac_diffusion_platform_generate_with_progress( + rac_diffusion_platform_handle_t handle, const rac_diffusion_platform_options_t* options, + rac_platform_diffusion_progress_fn progress_callback, void* progress_user_data, + rac_diffusion_platform_result_t* out_result); + +/** + * Cancels ongoing generation. + * + * @param handle Service handle + * @return RAC_SUCCESS on success, or error code + */ +RAC_API rac_result_t rac_diffusion_platform_cancel(rac_diffusion_platform_handle_t handle); + +/** + * Frees a platform diffusion result. + * + * @param result Result to free + */ +RAC_API void rac_diffusion_platform_result_free(rac_diffusion_platform_result_t* result); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_DIFFUSION_PLATFORM_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm.h b/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm.h new file mode 100644 index 000000000..72c9c126f --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm.h @@ -0,0 +1,16 @@ +/** + * @file rac_vlm.h + * @brief RunAnywhere Commons - VLM Public API + * + * Convenience header that includes all VLM-related headers. + * Use this for complete VLM API access. + */ + +#ifndef RAC_VLM_H +#define RAC_VLM_H + +#include "rac/features/vlm/rac_vlm_component.h" +#include "rac/features/vlm/rac_vlm_service.h" +#include "rac/features/vlm/rac_vlm_types.h" + +#endif /* RAC_VLM_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm_component.h b/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm_component.h new file mode 100644 index 000000000..b4c3238bb --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm_component.h @@ -0,0 +1,168 @@ +/** + * @file rac_vlm_component.h + * @brief RunAnywhere Commons - VLM Capability Component + * + * Actor-based VLM capability that owns model lifecycle and generation. + * Uses lifecycle manager for unified lifecycle + analytics handling. + */ + +#ifndef RAC_VLM_COMPONENT_H +#define RAC_VLM_COMPONENT_H + +#include "rac/core/capabilities/rac_lifecycle.h" +#include "rac/core/rac_error.h" +#include "rac/features/vlm/rac_vlm_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// VLM COMPONENT API +// ============================================================================= + +/** + * @brief Create a VLM capability component + * + * @param out_handle Output: Handle to the component + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_component_create(rac_handle_t* out_handle); + +/** + * @brief Configure the VLM component + * + * @param handle Component handle + * @param config Configuration + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_component_configure(rac_handle_t handle, + const rac_vlm_config_t* config); + +/** + * @brief Check if model is loaded + * + * @param handle Component handle + * @return RAC_TRUE if loaded, RAC_FALSE otherwise + */ +RAC_API rac_bool_t rac_vlm_component_is_loaded(rac_handle_t handle); + +/** + * @brief Get current model ID + * + * @param handle Component handle + * @return Current model ID (NULL if not loaded) + */ +RAC_API const char* rac_vlm_component_get_model_id(rac_handle_t handle); + +/** + * @brief Load a VLM model + * + * @param handle Component handle + * @param model_path File path to the main model (LLM weights) - REQUIRED + * @param mmproj_path File path to the vision projector (required for llama.cpp, NULL for MLX) + * @param model_id Model identifier for telemetry (optional: if NULL, defaults to model_path) + * @param model_name Human-readable model name (optional: if NULL, defaults to model_id) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_component_load_model(rac_handle_t handle, const char* model_path, + const char* mmproj_path, const char* model_id, + const char* model_name); + +/** + * @brief Unload the current model + * + * @param handle Component handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_component_unload(rac_handle_t handle); + +/** + * @brief Cleanup and reset the component + * + * @param handle Component handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_component_cleanup(rac_handle_t handle); + +/** + * @brief Cancel ongoing generation + * + * Best-effort cancellation. + * + * @param handle Component handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_component_cancel(rac_handle_t handle); + +/** + * @brief Process an image with text prompt (non-streaming) + * + * @param handle Component handle + * @param image Image input + * @param prompt Text prompt + * @param options Generation options (can be NULL for defaults) + * @param out_result Output: Generation result + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_component_process(rac_handle_t handle, const rac_vlm_image_t* image, + const char* prompt, const rac_vlm_options_t* options, + rac_vlm_result_t* out_result); + +/** + * @brief Check if streaming is supported + * + * @param handle Component handle + * @return RAC_TRUE if streaming supported, RAC_FALSE otherwise + */ +RAC_API rac_bool_t rac_vlm_component_supports_streaming(rac_handle_t handle); + +/** + * @brief Process an image with streaming + * + * @param handle Component handle + * @param image Image input + * @param prompt Text prompt + * @param options Generation options (can be NULL for defaults) + * @param token_callback Called for each generated token + * @param complete_callback Called when generation completes + * @param error_callback Called on error + * @param user_data User context passed to callbacks + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_component_process_stream( + rac_handle_t handle, const rac_vlm_image_t* image, const char* prompt, + const rac_vlm_options_t* options, rac_vlm_component_token_callback_fn token_callback, + rac_vlm_component_complete_callback_fn complete_callback, + rac_vlm_component_error_callback_fn error_callback, void* user_data); + +/** + * @brief Get lifecycle state + * + * @param handle Component handle + * @return Current lifecycle state + */ +RAC_API rac_lifecycle_state_t rac_vlm_component_get_state(rac_handle_t handle); + +/** + * @brief Get lifecycle metrics + * + * @param handle Component handle + * @param out_metrics Output: Lifecycle metrics + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_component_get_metrics(rac_handle_t handle, + rac_lifecycle_metrics_t* out_metrics); + +/** + * @brief Destroy the VLM component + * + * @param handle Component handle + */ +RAC_API void rac_vlm_component_destroy(rac_handle_t handle); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_VLM_COMPONENT_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm_service.h b/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm_service.h new file mode 100644 index 000000000..5d47adeb3 --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm_service.h @@ -0,0 +1,206 @@ +/** + * @file rac_vlm_service.h + * @brief RunAnywhere Commons - VLM Service Interface + * + * Defines the generic VLM service API and vtable for multi-backend dispatch. + * Backends (LlamaCpp VLM, MLX VLM) implement the vtable and register + * with the service registry. + */ + +#ifndef RAC_VLM_SERVICE_H +#define RAC_VLM_SERVICE_H + +#include "rac/core/rac_error.h" +#include "rac/features/vlm/rac_vlm_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// SERVICE VTABLE - Backend implementations provide this +// ============================================================================= + +/** + * VLM Service operations vtable. + * Each backend implements these functions and provides a static vtable. + */ +typedef struct rac_vlm_service_ops { + /** + * Initialize the service with model path(s). + * @param impl Backend implementation handle + * @param model_path Path to the main model file (LLM weights) + * @param mmproj_path Path to vision projector (required for llama.cpp, NULL for MLX) + * @return RAC_SUCCESS or error code + */ + rac_result_t (*initialize)(void* impl, const char* model_path, const char* mmproj_path); + + /** + * Process an image with a text prompt (blocking). + * @param impl Backend implementation handle + * @param image Image input + * @param prompt Text prompt + * @param options Generation options (can be NULL for defaults) + * @param out_result Output result (caller must free with rac_vlm_result_free) + * @return RAC_SUCCESS or error code + */ + rac_result_t (*process)(void* impl, const rac_vlm_image_t* image, const char* prompt, + const rac_vlm_options_t* options, rac_vlm_result_t* out_result); + + /** + * Process an image with streaming callback. + * @param impl Backend implementation handle + * @param image Image input + * @param prompt Text prompt + * @param options Generation options (can be NULL for defaults) + * @param callback Token callback + * @param user_data User context for callback + * @return RAC_SUCCESS or error code + */ + rac_result_t (*process_stream)(void* impl, const rac_vlm_image_t* image, const char* prompt, + const rac_vlm_options_t* options, + rac_vlm_stream_callback_fn callback, void* user_data); + + /** + * Get service information. + * @param impl Backend implementation handle + * @param out_info Output info structure + * @return RAC_SUCCESS or error code + */ + rac_result_t (*get_info)(void* impl, rac_vlm_info_t* out_info); + + /** + * Cancel ongoing generation. + * @param impl Backend implementation handle + * @return RAC_SUCCESS or error code + */ + rac_result_t (*cancel)(void* impl); + + /** + * Cleanup/unload model (keeps service alive). + * @param impl Backend implementation handle + * @return RAC_SUCCESS or error code + */ + rac_result_t (*cleanup)(void* impl); + + /** + * Destroy the service. + * @param impl Backend implementation handle + */ + void (*destroy)(void* impl); +} rac_vlm_service_ops_t; + +/** + * VLM Service instance. + * Contains vtable pointer and backend-specific implementation. + */ +typedef struct rac_vlm_service { + /** Vtable with backend operations */ + const rac_vlm_service_ops_t* ops; + + /** Backend-specific implementation handle */ + void* impl; + + /** Model ID for reference */ + const char* model_id; +} rac_vlm_service_t; + +// ============================================================================= +// PUBLIC API - Generic service functions +// ============================================================================= + +/** + * @brief Create a VLM service + * + * Routes through service registry to find appropriate backend. + * + * @param model_id Model identifier (registry ID or path to model file) + * @param out_handle Output: Handle to the created service + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_create(const char* model_id, rac_handle_t* out_handle); + +/** + * @brief Initialize a VLM service with model paths + * + * @param handle Service handle + * @param model_path Path to the main model file + * @param mmproj_path Path to vision projector (can be NULL for some backends) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_initialize(rac_handle_t handle, const char* model_path, + const char* mmproj_path); + +/** + * @brief Process an image with a text prompt + * + * @param handle Service handle + * @param image Image input + * @param prompt Text prompt describing what to analyze + * @param options Generation options (can be NULL for defaults) + * @param out_result Output: Generation result (caller must free with rac_vlm_result_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_process(rac_handle_t handle, const rac_vlm_image_t* image, + const char* prompt, const rac_vlm_options_t* options, + rac_vlm_result_t* out_result); + +/** + * @brief Process an image with streaming response + * + * @param handle Service handle + * @param image Image input + * @param prompt Text prompt + * @param options Generation options (can be NULL for defaults) + * @param callback Callback for each generated token + * @param user_data User context passed to callback + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_process_stream(rac_handle_t handle, const rac_vlm_image_t* image, + const char* prompt, const rac_vlm_options_t* options, + rac_vlm_stream_callback_fn callback, void* user_data); + +/** + * @brief Get service information + * + * @param handle Service handle + * @param out_info Output: Service information + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_get_info(rac_handle_t handle, rac_vlm_info_t* out_info); + +/** + * @brief Cancel ongoing generation + * + * @param handle Service handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_cancel(rac_handle_t handle); + +/** + * @brief Cleanup and release model resources + * + * @param handle Service handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_vlm_cleanup(rac_handle_t handle); + +/** + * @brief Destroy a VLM service instance + * + * @param handle Service handle to destroy + */ +RAC_API void rac_vlm_destroy(rac_handle_t handle); + +/** + * @brief Free a VLM result + * + * @param result Result to free + */ +RAC_API void rac_vlm_result_free(rac_vlm_result_t* result); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_VLM_SERVICE_H */ diff --git a/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm_types.h b/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm_types.h new file mode 100644 index 000000000..5fcf5995f --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/features/vlm/rac_vlm_types.h @@ -0,0 +1,417 @@ +/** + * @file rac_vlm_types.h + * @brief RunAnywhere Commons - VLM Types and Data Structures + * + * Defines data structures for Vision Language Model (VLM) operations. + * Supports image input (file path, RGB pixels, base64), generation options, + * results, and streaming callbacks. + * + * For the service interface, see rac_vlm_service.h. + */ + +#ifndef RAC_VLM_TYPES_H +#define RAC_VLM_TYPES_H + +#include "rac/core/rac_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// CHAT TEMPLATE - Abstraction for VLM prompt formatting +// ============================================================================= + +/** + * @brief Known VLM model families for chat template selection + * + * Use RAC_VLM_MODEL_FAMILY_AUTO (default) to auto-detect from model metadata. + * Use RAC_VLM_MODEL_FAMILY_CUSTOM with a custom template string for new models. + * + * Verified templates (from official HuggingFace repos): + * - QWEN2_VL: <|im_start|>system\nYou are a helpful assistant.<|im_end|>\n + * <|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>{prompt}<|im_end|>\n + * <|im_start|>assistant\n + * - SMOLVLM: <|im_start|>User: {image}{prompt} \nAssistant: + * - LLAVA: USER: \n{prompt}\nASSISTANT: + */ +typedef enum rac_vlm_model_family { + RAC_VLM_MODEL_FAMILY_AUTO = 0, /**< Auto-detect from model metadata (default) */ + RAC_VLM_MODEL_FAMILY_QWEN2_VL = 1, /**< Qwen2-VL: chatml with <|vision_start|> markers */ + RAC_VLM_MODEL_FAMILY_SMOLVLM = 2, /**< SmolVLM: <|im_start|>User: format */ + RAC_VLM_MODEL_FAMILY_LLAVA = 3, /**< LLaVA/Vicuna: USER:/ASSISTANT: format */ + RAC_VLM_MODEL_FAMILY_CUSTOM = 99, /**< Use custom_chat_template string */ +} rac_vlm_model_family_t; + +/** + * @brief Custom chat template for VLM prompt formatting + * + * A simple template string with placeholders: + * {system} - System prompt (optional, can be empty) + * {image} - Image marker/placeholder + * {prompt} - User's text prompt + * + * Example template string: + * "<|im_start|>user\n{image}{prompt}<|im_end|>\n<|im_start|>assistant\n" + * + * The SDK will replace placeholders at runtime. If {system} is in the template + * but no system prompt is provided, it uses a default or leaves empty. + */ +typedef struct rac_vlm_chat_template { + /** + * Full template string with {system}, {image}, {prompt} placeholders. + * Example: "<|im_start|>user\n{image}{prompt}<|im_end|>\n<|im_start|>assistant\n" + */ + const char* template_str; + + /** + * Image marker to insert at {image} placeholder. + * Examples: "", "<|vision_start|><|image_pad|><|vision_end|>" + * If NULL, uses the backend's default marker. + */ + const char* image_marker; + + /** + * Default system prompt if {system} is in template but none provided. + * Can be NULL for no default. + */ + const char* default_system_prompt; +} rac_vlm_chat_template_t; + +/** + * @brief Get built-in chat template for a model family + * + * @param family Model family enum value + * @return Pointer to static template, or NULL if family not supported + */ +RAC_API const rac_vlm_chat_template_t* rac_vlm_get_builtin_template(rac_vlm_model_family_t family); + +// ============================================================================= +// IMAGE INPUT - Supports multiple input formats +// ============================================================================= + +/** + * @brief VLM image input format enumeration + */ +typedef enum rac_vlm_image_format { + RAC_VLM_IMAGE_FORMAT_FILE_PATH = 0, /**< Path to image file (JPEG, PNG, etc.) */ + RAC_VLM_IMAGE_FORMAT_RGB_PIXELS = 1, /**< Raw RGB pixel buffer (RGBRGBRGB...) */ + RAC_VLM_IMAGE_FORMAT_BASE64 = 2, /**< Base64-encoded image data */ +} rac_vlm_image_format_t; + +/** + * @brief VLM image input structure + * + * Represents an image input for VLM processing. Supports three formats: + * - FILE_PATH: Path to an image file on disk + * - RGB_PIXELS: Raw RGB pixel data with width/height + * - BASE64: Base64-encoded image data + */ +typedef struct rac_vlm_image { + /** Image format type */ + rac_vlm_image_format_t format; + + /** Path to image file (for FILE_PATH format) */ + const char* file_path; + + /** Raw RGB pixel data (for RGB_PIXELS format, layout: RGBRGBRGB...) */ + const uint8_t* pixel_data; + + /** Base64-encoded image data (for BASE64 format) */ + const char* base64_data; + + /** Image width in pixels (required for RGB_PIXELS, 0 otherwise) */ + uint32_t width; + + /** Image height in pixels (required for RGB_PIXELS, 0 otherwise) */ + uint32_t height; + + /** Size of pixel_data or base64_data in bytes */ + size_t data_size; +} rac_vlm_image_t; + +// ============================================================================= +// OPTIONS - VLM Generation Options +// ============================================================================= + +/** + * @brief VLM generation options + * + * Controls text generation behavior for VLM inference. + * Combines standard LLM options with VLM-specific parameters. + */ +typedef struct rac_vlm_options { + // โ”€โ”€ Standard Generation Parameters โ”€โ”€ + /** Maximum number of tokens to generate (default: 2048) */ + int32_t max_tokens; + + /** Temperature for sampling (0.0 - 2.0, default: 0.7) */ + float temperature; + + /** Top-p sampling parameter (default: 0.9) */ + float top_p; + + /** Stop sequences (null-terminated array, can be NULL) */ + const char* const* stop_sequences; + + /** Number of stop sequences */ + size_t num_stop_sequences; + + /** Enable streaming mode (default: true) */ + rac_bool_t streaming_enabled; + + /** System prompt (can be NULL, uses template default if available) */ + const char* system_prompt; + + // โ”€โ”€ VLM-Specific Parameters โ”€โ”€ + /** Max image dimension for resize (0 = model default) */ + int32_t max_image_size; + + /** Number of CPU threads for vision encoder (0 = auto) */ + int32_t n_threads; + + /** Use GPU for vision encoding */ + rac_bool_t use_gpu; + + // โ”€โ”€ Chat Template Configuration โ”€โ”€ + /** + * Model family for automatic chat template selection. + * Set to RAC_VLM_MODEL_FAMILY_AUTO (default) to auto-detect from model metadata. + * Set to RAC_VLM_MODEL_FAMILY_CUSTOM and provide custom_chat_template for custom templates. + */ + rac_vlm_model_family_t model_family; + + /** + * Custom chat template (only used when model_family == RAC_VLM_MODEL_FAMILY_CUSTOM). + * If NULL and model_family is CUSTOM, falls back to GENERIC template. + */ + const rac_vlm_chat_template_t* custom_chat_template; + + /** + * Override image marker (can be NULL to use template default). + * Useful when the default marker doesn't match your model's expectations. + */ + const char* image_marker_override; +} rac_vlm_options_t; + +/** + * @brief Default VLM generation options + */ +#define RAC_VLM_OPTIONS_DEFAULT \ + { \ + .max_tokens = 2048, .temperature = 0.7f, .top_p = 0.9f, .stop_sequences = RAC_NULL, \ + .num_stop_sequences = 0, .streaming_enabled = RAC_TRUE, .system_prompt = RAC_NULL, \ + .max_image_size = 0, .n_threads = 0, .use_gpu = RAC_TRUE, \ + .model_family = RAC_VLM_MODEL_FAMILY_AUTO, .custom_chat_template = RAC_NULL, \ + .image_marker_override = RAC_NULL \ + } + +// ============================================================================= +// CONFIGURATION - VLM Component Configuration +// ============================================================================= + +/** + * @brief VLM component configuration + * + * Configuration for initializing a VLM component. + */ +typedef struct rac_vlm_config { + /** Model ID (optional - uses default if NULL) */ + const char* model_id; + + /** Preferred framework for generation (use RAC_FRAMEWORK_UNKNOWN for auto) */ + int32_t preferred_framework; + + /** Context length - max tokens the model can handle (default: 4096) */ + int32_t context_length; + + /** Temperature for sampling (0.0 - 2.0, default: 0.7) */ + float temperature; + + /** Maximum tokens to generate (default: 2048) */ + int32_t max_tokens; + + /** System prompt for generation (can be NULL) */ + const char* system_prompt; + + /** Enable streaming mode (default: true) */ + rac_bool_t streaming_enabled; +} rac_vlm_config_t; + +/** + * @brief Default VLM configuration + */ +static const rac_vlm_config_t RAC_VLM_CONFIG_DEFAULT = {.model_id = RAC_NULL, + .preferred_framework = + 99, // RAC_FRAMEWORK_UNKNOWN + .context_length = 4096, + .temperature = 0.7f, + .max_tokens = 2048, + .system_prompt = RAC_NULL, + .streaming_enabled = RAC_TRUE}; + +// ============================================================================= +// RESULTS - VLM Generation Results +// ============================================================================= + +/** + * @brief VLM generation result + * + * Contains the generated text and detailed metrics for VLM inference. + */ +typedef struct rac_vlm_result { + /** Generated text (owned, must be freed with rac_vlm_result_free) */ + char* text; + + /** Number of tokens in prompt (including text tokens) */ + int32_t prompt_tokens; + + /** Number of vision/image tokens specifically */ + int32_t image_tokens; + + /** Number of tokens generated */ + int32_t completion_tokens; + + /** Total tokens (prompt + completion) */ + int32_t total_tokens; + + /** Time to first token in milliseconds */ + int64_t time_to_first_token_ms; + + /** Time spent encoding the image in milliseconds */ + int64_t image_encode_time_ms; + + /** Total generation time in milliseconds */ + int64_t total_time_ms; + + /** Tokens generated per second */ + float tokens_per_second; +} rac_vlm_result_t; + +// ============================================================================= +// SERVICE INFO - VLM Service Information +// ============================================================================= + +/** + * @brief VLM service handle info + * + * Provides information about a VLM service instance. + */ +typedef struct rac_vlm_info { + /** Whether the service is ready for generation */ + rac_bool_t is_ready; + + /** Current model identifier (can be NULL if not loaded) */ + const char* current_model; + + /** Context length (0 if unknown) */ + int32_t context_length; + + /** Whether streaming is supported */ + rac_bool_t supports_streaming; + + /** Whether multiple images per request are supported */ + rac_bool_t supports_multiple_images; + + /** Vision encoder type ("clip", "siglip", "fastvithd", etc.) */ + const char* vision_encoder_type; +} rac_vlm_info_t; + +// ============================================================================= +// CALLBACKS - Streaming Callbacks +// ============================================================================= + +/** + * @brief Simple VLM streaming callback + * + * Called for each generated token during streaming. + * + * @param token The generated token string + * @param user_data User-provided context + * @return RAC_TRUE to continue, RAC_FALSE to stop generation + */ +typedef rac_bool_t (*rac_vlm_stream_callback_fn)(const char* token, void* user_data); + +/** + * @brief Extended token event structure + * + * Provides detailed information about each token during streaming. + */ +typedef struct rac_vlm_token_event { + /** The generated token text */ + const char* token; + + /** Token index in the sequence */ + int32_t token_index; + + /** Is this the final token? */ + rac_bool_t is_final; + + /** Tokens generated per second so far */ + float tokens_per_second; +} rac_vlm_token_event_t; + +/** + * @brief Extended streaming callback with token event details + * + * @param event Token event details + * @param user_data User-provided context + * @return RAC_TRUE to continue, RAC_FALSE to stop generation + */ +typedef rac_bool_t (*rac_vlm_token_event_callback_fn)(const rac_vlm_token_event_t* event, + void* user_data); + +// ============================================================================= +// COMPONENT CALLBACKS - For component-level streaming +// ============================================================================= + +/** + * @brief VLM component token callback + * + * @param token The generated token + * @param user_data User-provided context + * @return RAC_TRUE to continue, RAC_FALSE to stop + */ +typedef rac_bool_t (*rac_vlm_component_token_callback_fn)(const char* token, void* user_data); + +/** + * @brief VLM component completion callback + * + * Called when streaming is complete with final result. + * + * @param result Final generation result with metrics + * @param user_data User-provided context + */ +typedef void (*rac_vlm_component_complete_callback_fn)(const rac_vlm_result_t* result, + void* user_data); + +/** + * @brief VLM component error callback + * + * Called if streaming fails. + * + * @param error_code Error code + * @param error_message Error message + * @param user_data User-provided context + */ +typedef void (*rac_vlm_component_error_callback_fn)(rac_result_t error_code, + const char* error_message, void* user_data); + +// ============================================================================= +// MEMORY MANAGEMENT +// ============================================================================= + +/** + * @brief Free VLM result resources + * + * Frees the text and any other owned resources in the result. + * + * @param result Result to free (can be NULL) + */ +RAC_API void rac_vlm_result_free(rac_vlm_result_t* result); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_VLM_TYPES_H */ diff --git a/sdk/runanywhere-commons/include/rac/infrastructure/model_management/rac_model_registry.h b/sdk/runanywhere-commons/include/rac/infrastructure/model_management/rac_model_registry.h index 7def004bd..5531a7302 100644 --- a/sdk/runanywhere-commons/include/rac/infrastructure/model_management/rac_model_registry.h +++ b/sdk/runanywhere-commons/include/rac/infrastructure/model_management/rac_model_registry.h @@ -86,6 +86,21 @@ RAC_API rac_result_t rac_model_registry_save(rac_model_registry_handle_t handle, RAC_API rac_result_t rac_model_registry_get(rac_model_registry_handle_t handle, const char* model_id, rac_model_info_t** out_model); +/** + * @brief Get model metadata by local path. + * + * Searches through all registered models and returns the one with matching local_path. + * This is useful when loading models by path instead of model_id. + * + * @param handle Registry handle + * @param local_path Local path to search for + * @param out_model Output: Model info (owned, must be freed with rac_model_info_free) + * @return RAC_SUCCESS, RAC_ERROR_NOT_FOUND, or other error code + */ +RAC_API rac_result_t rac_model_registry_get_by_path(rac_model_registry_handle_t handle, + const char* local_path, + rac_model_info_t** out_model); + /** * @brief Load all stored models. * diff --git a/sdk/runanywhere-commons/include/rac/infrastructure/model_management/rac_model_types.h b/sdk/runanywhere-commons/include/rac/infrastructure/model_management/rac_model_types.h index e69641fe6..fad099d89 100644 --- a/sdk/runanywhere-commons/include/rac/infrastructure/model_management/rac_model_types.h +++ b/sdk/runanywhere-commons/include/rac/infrastructure/model_management/rac_model_types.h @@ -163,6 +163,7 @@ typedef enum rac_model_format { RAC_MODEL_FORMAT_ORT = 1, /**< ONNX Runtime format */ RAC_MODEL_FORMAT_GGUF = 2, /**< GGUF format (llama.cpp) */ RAC_MODEL_FORMAT_BIN = 3, /**< Binary format */ + RAC_MODEL_FORMAT_COREML = 4, /**< Core ML format (.mlmodelc, .mlpackage) */ RAC_MODEL_FORMAT_UNKNOWN = 99 /**< Unknown format */ } rac_model_format_t; @@ -182,6 +183,8 @@ typedef enum rac_inference_framework { RAC_FRAMEWORK_FLUID_AUDIO = 4, /**< FluidAudio */ RAC_FRAMEWORK_BUILTIN = 5, /**< Built-in (e.g., energy VAD) */ RAC_FRAMEWORK_NONE = 6, /**< No framework needed */ + RAC_FRAMEWORK_MLX = 7, /**< MLX C++ (Apple Silicon VLM) */ + RAC_FRAMEWORK_COREML = 8, /**< Core ML (Apple Neural Engine) */ RAC_FRAMEWORK_UNKNOWN = 99 /**< Unknown framework */ } rac_inference_framework_t; diff --git a/sdk/runanywhere-commons/include/rac/utils/rac_image_utils.h b/sdk/runanywhere-commons/include/rac/utils/rac_image_utils.h new file mode 100644 index 000000000..4ba5e0125 --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/utils/rac_image_utils.h @@ -0,0 +1,215 @@ +/** + * @file rac_image_utils.h + * @brief RunAnywhere Commons - Image Utilities + * + * Image loading and processing utilities for VLM backends. + * Supports loading from file paths, decoding base64, and resizing. + */ + +#ifndef RAC_IMAGE_UTILS_H +#define RAC_IMAGE_UTILS_H + +#include "rac/core/rac_error.h" +#include "rac/core/rac_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// IMAGE DATA STRUCTURES +// ============================================================================= + +/** + * @brief Loaded image data + * + * Contains RGB pixel data after loading an image. + * Must be freed with rac_image_free(). + */ +typedef struct rac_image_data { + /** Raw RGB pixel data (RGBRGBRGB...) */ + uint8_t* pixels; + + /** Image width in pixels */ + int32_t width; + + /** Image height in pixels */ + int32_t height; + + /** Number of channels (3 for RGB) */ + int32_t channels; + + /** Total size in bytes (width * height * channels) */ + size_t size; +} rac_image_data_t; + +/** + * @brief Normalized float image data + * + * Contains normalized float32 pixel data (values in [-1, 1] or [0, 1]). + * Used by vision encoders. + */ +typedef struct rac_image_float { + /** Normalized float pixel data */ + float* pixels; + + /** Image width in pixels */ + int32_t width; + + /** Image height in pixels */ + int32_t height; + + /** Number of channels (3 for RGB) */ + int32_t channels; + + /** Total number of floats (width * height * channels) */ + size_t count; +} rac_image_float_t; + +// ============================================================================= +// IMAGE LOADING +// ============================================================================= + +/** + * @brief Load an image from a file path + * + * Supports JPEG, PNG, BMP, GIF, and other common formats via stb_image. + * Output is always RGB (3 channels). + * + * @param file_path Path to the image file + * @param out_image Output: Loaded image data (must be freed with rac_image_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_image_load_file(const char* file_path, rac_image_data_t* out_image); + +/** + * @brief Decode a base64-encoded image + * + * Decodes base64 data and loads the image. + * Supports the same formats as rac_image_load_file. + * + * @param base64_data Base64-encoded image data + * @param data_size Length of the base64 string + * @param out_image Output: Loaded image data (must be freed with rac_image_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_image_decode_base64(const char* base64_data, size_t data_size, + rac_image_data_t* out_image); + +/** + * @brief Decode image from raw bytes + * + * Decodes an image from raw bytes (e.g., from network response). + * + * @param data Raw image data (JPEG, PNG, etc.) + * @param data_size Size of the data in bytes + * @param out_image Output: Loaded image data (must be freed with rac_image_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_image_decode_bytes(const uint8_t* data, size_t data_size, + rac_image_data_t* out_image); + +// ============================================================================= +// IMAGE PROCESSING +// ============================================================================= + +/** + * @brief Resize an image + * + * Resizes the image to the specified dimensions using bilinear interpolation. + * + * @param image Input image + * @param new_width Target width + * @param new_height Target height + * @param out_image Output: Resized image (must be freed with rac_image_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_image_resize(const rac_image_data_t* image, int32_t new_width, + int32_t new_height, rac_image_data_t* out_image); + +/** + * @brief Resize an image maintaining aspect ratio + * + * Resizes the image so that the longest dimension equals max_size. + * Aspect ratio is preserved. + * + * @param image Input image + * @param max_size Maximum dimension (width or height) + * @param out_image Output: Resized image (must be freed with rac_image_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_image_resize_max(const rac_image_data_t* image, int32_t max_size, + rac_image_data_t* out_image); + +/** + * @brief Normalize image to float values + * + * Converts uint8 pixels to float32 with optional mean/std normalization. + * Commonly used for vision encoders (CLIP, SigLIP, etc.). + * + * Formula: pixel_normalized = (pixel / 255.0 - mean) / std + * + * @param image Input image + * @param mean Per-channel mean values (array of 3 floats, or NULL for [0,0,0]) + * @param std Per-channel std values (array of 3 floats, or NULL for [1,1,1]) + * @param out_float Output: Normalized float image (must be freed with rac_image_float_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_image_normalize(const rac_image_data_t* image, const float* mean, + const float* std, rac_image_float_t* out_float); + +/** + * @brief Convert RGB to CHW format + * + * Converts from HWC (Height, Width, Channels) to CHW format. + * Many neural networks expect CHW format. + * + * @param image Input float image in HWC format + * @param out_chw Output: Float image in CHW format (must be freed with rac_image_float_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_image_to_chw(const rac_image_float_t* image, rac_image_float_t* out_chw); + +// ============================================================================= +// MEMORY MANAGEMENT +// ============================================================================= + +/** + * @brief Free image data + * + * Frees the pixel data allocated by image loading functions. + * + * @param image Image to free (can be NULL) + */ +RAC_API void rac_image_free(rac_image_data_t* image); + +/** + * @brief Free float image data + * + * Frees the pixel data allocated by normalization functions. + * + * @param image Float image to free (can be NULL) + */ +RAC_API void rac_image_float_free(rac_image_float_t* image); + +// ============================================================================= +// UTILITY FUNCTIONS +// ============================================================================= + +/** + * @brief Calculate resized dimensions maintaining aspect ratio + * + * @param width Original width + * @param height Original height + * @param max_size Maximum dimension + * @param out_width Output: New width + * @param out_height Output: New height + */ +RAC_API void rac_image_calc_resize(int32_t width, int32_t height, int32_t max_size, + int32_t* out_width, int32_t* out_height); + +#ifdef __cplusplus +} +#endif + +#endif /* RAC_IMAGE_UTILS_H */ diff --git a/sdk/runanywhere-commons/scripts/android/download-sherpa-onnx.sh b/sdk/runanywhere-commons/scripts/android/download-sherpa-onnx.sh index 670464baf..0bbc79759 100755 --- a/sdk/runanywhere-commons/scripts/android/download-sherpa-onnx.sh +++ b/sdk/runanywhere-commons/scripts/android/download-sherpa-onnx.sh @@ -237,23 +237,41 @@ if [ "${HTTP_CODE}" = "200" ] && [ -f "${TEMP_ARCHIVE}" ] && [ -s "${TEMP_ARCHIV # Download headers if not present (Android release doesn't include them) if [ ! -d "${SHERPA_DIR}/include/sherpa-onnx" ]; then echo "" - echo "Downloading Sherpa-ONNX headers..." - mkdir -p "${SHERPA_DIR}/include" - - # Try to download from iOS since headers are platform-independent - IOS_SHERPA_HEADERS="${ROOT_DIR}/third_party/sherpa-onnx-ios/sherpa-onnx.xcframework/ios-arm64/Headers" - if [ -d "${IOS_SHERPA_HEADERS}/sherpa-onnx" ]; then - echo "Using headers from iOS Sherpa-ONNX..." - cp -R "${IOS_SHERPA_HEADERS}"/* "${SHERPA_DIR}/include/" - else - # Fallback: Download headers from source repo - echo "Downloading headers from Sherpa-ONNX source..." - curl -sL "https://raw.githubusercontent.com/k2-fsa/sherpa-onnx/v${SHERPA_VERSION}/sherpa-onnx/c-api/c-api.h" \ - -o "${SHERPA_DIR}/include/sherpa-onnx/c-api/c-api.h" --create-dirs - curl -sL "https://raw.githubusercontent.com/k2-fsa/sherpa-onnx/v${SHERPA_VERSION}/sherpa-onnx/c-api/cxx-api.h" \ - -o "${SHERPA_DIR}/include/sherpa-onnx/c-api/cxx-api.h" --create-dirs + echo "Downloading Sherpa-ONNX headers (v${SHERPA_VERSION})..." + mkdir -p "${SHERPA_DIR}/include/sherpa-onnx/c-api" + + # โš ๏ธ CRITICAL: Headers MUST come from the EXACT SAME version as the prebuilt .so files. + # DO NOT use iOS headers here โ€” iOS may use a different Sherpa-ONNX version + # (e.g., SHERPA_ONNX_VERSION_IOS=1.12.18 vs SHERPA_ONNX_VERSION_ANDROID=1.12.20). + # A version mismatch causes struct layout differences (ABI mismatch) that result in + # SIGSEGV crashes in SherpaOnnxCreateOfflineRecognizer at runtime. + echo "Downloading headers from Sherpa-ONNX source (v${SHERPA_VERSION})..." + curl -sL "https://raw.githubusercontent.com/k2-fsa/sherpa-onnx/v${SHERPA_VERSION}/sherpa-onnx/c-api/c-api.h" \ + -o "${SHERPA_DIR}/include/sherpa-onnx/c-api/c-api.h" + curl -sL "https://raw.githubusercontent.com/k2-fsa/sherpa-onnx/v${SHERPA_VERSION}/sherpa-onnx/c-api/cxx-api.h" \ + -o "${SHERPA_DIR}/include/sherpa-onnx/c-api/cxx-api.h" + + # Stamp the version so we can detect mismatches later + echo "${SHERPA_VERSION}" > "${SHERPA_DIR}/include/.sherpa-header-version" + echo "โœ… Sherpa-ONNX headers installed (v${SHERPA_VERSION})" + else + # Validate that existing headers match the expected version + if [ -f "${SHERPA_DIR}/include/.sherpa-header-version" ]; then + EXISTING_VER=$(cat "${SHERPA_DIR}/include/.sherpa-header-version") + if [ "${EXISTING_VER}" != "${SHERPA_VERSION}" ]; then + echo -e "${YELLOW}โš ๏ธ Header version mismatch: headers are v${EXISTING_VER} but .so is v${SHERPA_VERSION}${NC}" + echo -e "${YELLOW} Re-downloading headers to match .so version...${NC}" + rm -rf "${SHERPA_DIR}/include/sherpa-onnx" + rm -f "${SHERPA_DIR}/include/.sherpa-header-version" + mkdir -p "${SHERPA_DIR}/include/sherpa-onnx/c-api" + curl -sL "https://raw.githubusercontent.com/k2-fsa/sherpa-onnx/v${SHERPA_VERSION}/sherpa-onnx/c-api/c-api.h" \ + -o "${SHERPA_DIR}/include/sherpa-onnx/c-api/c-api.h" + curl -sL "https://raw.githubusercontent.com/k2-fsa/sherpa-onnx/v${SHERPA_VERSION}/sherpa-onnx/c-api/cxx-api.h" \ + -o "${SHERPA_DIR}/include/sherpa-onnx/c-api/cxx-api.h" + echo "${SHERPA_VERSION}" > "${SHERPA_DIR}/include/.sherpa-header-version" + echo -e "${GREEN}โœ… Headers updated to v${SHERPA_VERSION}${NC}" + fi fi - echo "โœ… Sherpa-ONNX headers installed" fi # Download ONNX Runtime C API header (required for ONNX backend compilation) diff --git a/sdk/runanywhere-commons/src/backends/llamacpp/CMakeLists.txt b/sdk/runanywhere-commons/src/backends/llamacpp/CMakeLists.txt index 70f3204ae..16978ea7e 100644 --- a/sdk/runanywhere-commons/src/backends/llamacpp/CMakeLists.txt +++ b/sdk/runanywhere-commons/src/backends/llamacpp/CMakeLists.txt @@ -84,6 +84,7 @@ FetchContent_MakeAvailable(llamacpp) # ============================================================================= set(LLAMACPP_BACKEND_SOURCES + # LLM Backend llamacpp_backend.cpp rac_llm_llamacpp.cpp rac_backend_llamacpp_register.cpp @@ -93,6 +94,41 @@ set(LLAMACPP_BACKEND_HEADERS llamacpp_backend.h ) +# Option to enable VLM multimodal support (requires mtmd from llama.cpp) +option(RAC_VLM_USE_MTMD "Enable VLM multimodal support via llama.cpp mtmd" ON) +if(RAC_VLM_USE_MTMD) + message(STATUS "VLM multimodal support enabled") + # Add VLM Backend sources (Vision Language Model) + list(APPEND LLAMACPP_BACKEND_SOURCES + rac_vlm_llamacpp.cpp + rac_backend_llamacpp_vlm_register.cpp + ) + # Add mtmd sources from llama.cpp tools directory + list(APPEND LLAMACPP_BACKEND_SOURCES + ${llamacpp_SOURCE_DIR}/tools/mtmd/mtmd.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/mtmd-helper.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/mtmd-audio.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/clip.cpp + ) + # Add mtmd model implementations (clip_graph_* classes) + list(APPEND LLAMACPP_BACKEND_SOURCES + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/llava.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/qwen2vl.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/qwen3vl.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/minicpmv.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/glm4v.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/cogvlm.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/internvl.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/kimivl.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/llama4.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/siglip.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/pixtral.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/youtuvl.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/conformer.cpp + ${llamacpp_SOURCE_DIR}/tools/mtmd/models/whisper-enc.cpp + ) +endif() + if(RAC_BUILD_SHARED) add_library(rac_backend_llamacpp SHARED ${LLAMACPP_BACKEND_SOURCES} @@ -115,8 +151,21 @@ target_include_directories(rac_backend_llamacpp PUBLIC ${llamacpp_SOURCE_DIR}/vendor # nlohmann/json.hpp ) +# VLM multimodal includes (mtmd) +if(RAC_VLM_USE_MTMD) + target_include_directories(rac_backend_llamacpp PRIVATE + ${llamacpp_SOURCE_DIR}/tools/mtmd + ${llamacpp_SOURCE_DIR}/tools/mtmd/models + ) +endif() + target_compile_definitions(rac_backend_llamacpp PRIVATE RAC_LLAMACPP_BUILDING) +# VLM multimodal compile definition +if(RAC_VLM_USE_MTMD) + target_compile_definitions(rac_backend_llamacpp PRIVATE RAC_VLM_USE_MTMD=1) +endif() + target_link_libraries(rac_backend_llamacpp PUBLIC rac_commons llama diff --git a/sdk/runanywhere-commons/src/backends/llamacpp/llamacpp_backend.cpp b/sdk/runanywhere-commons/src/backends/llamacpp/llamacpp_backend.cpp index e645006d0..31f19581f 100644 --- a/sdk/runanywhere-commons/src/backends/llamacpp/llamacpp_backend.cpp +++ b/sdk/runanywhere-commons/src/backends/llamacpp/llamacpp_backend.cpp @@ -207,6 +207,72 @@ bool LlamaCppTextGeneration::load_model(const std::string& model_path, model_path_ = model_path; llama_model_params model_params = llama_model_default_params(); + + // Detect model size from filename to set appropriate GPU layers BEFORE loading + // This prevents OOM crashes on mobile devices with limited GPU memory + // Note: We use filename heuristics here because we can't know param count until after loading + std::string path_lower = model_path; + std::transform(path_lower.begin(), path_lower.end(), path_lower.begin(), ::tolower); + + int gpu_layers = -1; // Default: all layers to GPU + + // Check for large model indicators in filename using word boundary detection + // Patterns like "7b", "8b", "13b" should match at word boundaries to avoid + // false positives like "/backup7b/" or "/2017beta/" + auto is_model_size_marker = [&path_lower](const char* marker) { + size_t pos = path_lower.find(marker); + while (pos != std::string::npos) { + // Check for word boundary before (start of string, or non-alphanumeric) + bool valid_start = (pos == 0) || !std::isalnum(path_lower[pos - 1]); + // Check for word boundary after (end of string, or non-alphanumeric except digits for patterns like "7b-q4") + size_t end_pos = pos + strlen(marker); + bool valid_end = (end_pos >= path_lower.size()) || + (!std::isalpha(path_lower[end_pos]) || path_lower[end_pos] == '-' || path_lower[end_pos] == '_'); + + if (valid_start && valid_end) { + return true; + } + pos = path_lower.find(marker, pos + 1); + } + return false; + }; + + // Detect large models (7B+) that may need GPU layer limiting on mobile + // First check for config-based override (for custom-named models) + bool is_large_model = false; + if (config.contains("expected_params_billions")) { + double expected_params = config["expected_params_billions"].get(); + is_large_model = (expected_params >= 7.0); + if (is_large_model) { + LOGI("Large model detected from config (%.1fB expected params)", expected_params); + } + } + + // Fall back to filename heuristics if no config provided + if (!is_large_model) { + is_large_model = is_model_size_marker("7b") || + is_model_size_marker("8b") || + is_model_size_marker("9b") || + is_model_size_marker("13b") || + is_model_size_marker("70b"); + } + + if (is_large_model) { + // For 7B+ models on mobile: limit GPU layers to prevent OOM + // Most 7B models have 32 layers, offload ~24 to GPU, rest to CPU + gpu_layers = 24; + LOGI("Large model detected, limiting GPU layers to %d to prevent OOM", gpu_layers); + } + + // Allow user override via config + if (config.contains("gpu_layers")) { + gpu_layers = config["gpu_layers"].get(); + LOGI("Using user-provided GPU layers: %d", gpu_layers); + } + + model_params.n_gpu_layers = gpu_layers; + LOGI("Loading model with n_gpu_layers=%d", gpu_layers); + model_ = llama_model_load_from_file(model_path.c_str(), model_params); if (!model_) { @@ -217,14 +283,45 @@ bool LlamaCppTextGeneration::load_model(const std::string& model_path, int model_train_ctx = llama_model_n_ctx_train(model_); LOGI("Model training context size: %d", model_train_ctx); + // Get model parameter count to determine appropriate context size + // Large models (7B+) need smaller context on mobile to fit in memory + uint64_t n_params = llama_model_n_params(model_); + double params_billions = static_cast(n_params) / 1e9; + LOGI("Model parameters: %.2fB", params_billions); + + // Post-load verification: warn if actual param count differs from filename heuristic + bool actual_is_large = (params_billions >= 7.0); + if (actual_is_large && !is_large_model) { + LOGI("WARNING: Model has %.1fB params but filename didn't indicate large model. " + "Consider using gpu_layers config for optimal performance.", params_billions); + } else if (!actual_is_large && is_large_model) { + LOGI("NOTE: Filename suggested large model but actual params are %.1fB. " + "GPU layer limiting may be conservative.", params_billions); + } + + // Adaptive context size based on model size for mobile devices + int adaptive_max_context; + if (params_billions >= 7.0) { + // 7B+ models: use 2048 context to fit in ~6GB GPU memory + adaptive_max_context = 2048; + LOGI("Large model detected (%.1fB params), limiting context to %d for memory", params_billions, adaptive_max_context); + } else if (params_billions >= 3.0) { + // 3-7B models: use 4096 context + adaptive_max_context = 4096; + LOGI("Medium model detected (%.1fB params), limiting context to %d", params_billions, adaptive_max_context); + } else { + // Small models (<3B): can use larger context + adaptive_max_context = max_default_context_; + } + if (user_context_size > 0) { context_size_ = std::min(user_context_size, model_train_ctx); LOGI("Using user-provided context size: %d (requested: %d, model max: %d)", context_size_, user_context_size, model_train_ctx); } else { - context_size_ = std::min(model_train_ctx, max_default_context_); - LOGI("Auto-detected context size: %d (model: %d, cap: %d)", context_size_, model_train_ctx, - max_default_context_); + context_size_ = std::min({model_train_ctx, max_default_context_, adaptive_max_context}); + LOGI("Auto-detected context size: %d (model: %d, cap: %d, adaptive: %d)", context_size_, + model_train_ctx, max_default_context_, adaptive_max_context); } llama_context_params ctx_params = llama_context_default_params(); diff --git a/sdk/runanywhere-commons/src/backends/llamacpp/rac_backend_llamacpp_vlm_register.cpp b/sdk/runanywhere-commons/src/backends/llamacpp/rac_backend_llamacpp_vlm_register.cpp new file mode 100644 index 000000000..c4b63b1fd --- /dev/null +++ b/sdk/runanywhere-commons/src/backends/llamacpp/rac_backend_llamacpp_vlm_register.cpp @@ -0,0 +1,307 @@ +/** + * @file rac_backend_llamacpp_vlm_register.cpp + * @brief RunAnywhere Commons - LlamaCPP VLM Backend Registration + * + * Registers the LlamaCPP VLM backend with the module and service registries. + * Provides vtable implementation for the generic VLM service interface. + */ + +#include "rac/backends/rac_vlm_llamacpp.h" + +#include +#include +#include + +#include "rac/core/rac_core.h" +#include "rac/core/rac_error.h" +#include "rac/core/rac_logger.h" +#include "rac/features/vlm/rac_vlm_service.h" + +static const char* LOG_CAT = "VLM.LlamaCPP"; + +// ============================================================================= +// VTABLE IMPLEMENTATION - Adapters for generic VLM service interface +// ============================================================================= + +namespace { + +// Initialize with model paths +static rac_result_t llamacpp_vlm_vtable_initialize(void* impl, const char* model_path, + const char* mmproj_path) { + return rac_vlm_llamacpp_load_model(impl, model_path, mmproj_path, nullptr); +} + +// Process image (blocking) +static rac_result_t llamacpp_vlm_vtable_process(void* impl, const rac_vlm_image_t* image, + const char* prompt, + const rac_vlm_options_t* options, + rac_vlm_result_t* out_result) { + return rac_vlm_llamacpp_process(impl, image, prompt, options, out_result); +} + +// Streaming callback adapter +struct VLMStreamAdapter { + rac_vlm_stream_callback_fn callback; + void* user_data; +}; + +static rac_bool_t vlm_stream_adapter_callback(const char* token, rac_bool_t is_final, void* ctx) { + auto* adapter = static_cast(ctx); + (void)is_final; + if (adapter && adapter->callback) { + return adapter->callback(token, adapter->user_data); + } + return RAC_TRUE; +} + +// Process stream +static rac_result_t llamacpp_vlm_vtable_process_stream(void* impl, const rac_vlm_image_t* image, + const char* prompt, + const rac_vlm_options_t* options, + rac_vlm_stream_callback_fn callback, + void* user_data) { + VLMStreamAdapter adapter = {callback, user_data}; + return rac_vlm_llamacpp_process_stream(impl, image, prompt, options, vlm_stream_adapter_callback, + &adapter); +} + +// Get info +static rac_result_t llamacpp_vlm_vtable_get_info(void* impl, rac_vlm_info_t* out_info) { + if (!out_info) + return RAC_ERROR_NULL_POINTER; + + out_info->is_ready = rac_vlm_llamacpp_is_model_loaded(impl); + out_info->supports_streaming = RAC_TRUE; + out_info->supports_multiple_images = RAC_FALSE; // Current implementation: single image + out_info->current_model = nullptr; + out_info->context_length = 0; + out_info->vision_encoder_type = "clip"; // Default for llama.cpp VLM + + // Get actual info from model + if (out_info->is_ready) { + char* json_str = nullptr; + if (rac_vlm_llamacpp_get_model_info(impl, &json_str) == RAC_SUCCESS && json_str) { + // Simple parse for context_size + // In production, use proper JSON parsing + const char* ctx_key = "\"context_size\":"; + const char* ctx_pos = strstr(json_str, ctx_key); + if (ctx_pos) { + out_info->context_length = atoi(ctx_pos + strlen(ctx_key)); + } + free(json_str); + } + } + + return RAC_SUCCESS; +} + +// Cancel +static rac_result_t llamacpp_vlm_vtable_cancel(void* impl) { + rac_vlm_llamacpp_cancel(impl); + return RAC_SUCCESS; +} + +// Cleanup +static rac_result_t llamacpp_vlm_vtable_cleanup(void* impl) { + return rac_vlm_llamacpp_unload_model(impl); +} + +// Destroy +static void llamacpp_vlm_vtable_destroy(void* impl) { + rac_vlm_llamacpp_destroy(impl); +} + +// Static vtable for LlamaCpp VLM +static const rac_vlm_service_ops_t g_llamacpp_vlm_ops = { + .initialize = llamacpp_vlm_vtable_initialize, + .process = llamacpp_vlm_vtable_process, + .process_stream = llamacpp_vlm_vtable_process_stream, + .get_info = llamacpp_vlm_vtable_get_info, + .cancel = llamacpp_vlm_vtable_cancel, + .cleanup = llamacpp_vlm_vtable_cleanup, + .destroy = llamacpp_vlm_vtable_destroy, +}; + +// ============================================================================= +// REGISTRY STATE +// ============================================================================= + +struct LlamaCPPVLMRegistryState { + std::mutex mutex; + bool registered = false; + char provider_name[32] = "LlamaCPPVLMService"; + char module_id[16] = "llamacpp_vlm"; +}; + +LlamaCPPVLMRegistryState& get_state() { + static LlamaCPPVLMRegistryState state; + return state; +} + +// ============================================================================= +// SERVICE PROVIDER IMPLEMENTATION +// ============================================================================= + +/** + * Check if this backend can handle the service request. + */ +rac_bool_t llamacpp_vlm_can_handle(const rac_service_request_t* request, void* user_data) { + (void)user_data; + + if (request == nullptr) { + RAC_LOG_DEBUG(LOG_CAT, "can_handle: request is NULL"); + return RAC_FALSE; + } + + // Must be VISION_LANGUAGE capability + if (request->capability != RAC_CAPABILITY_VISION_LANGUAGE) { + return RAC_FALSE; + } + + RAC_LOG_DEBUG(LOG_CAT, "can_handle: framework=%d, model_path=%s, identifier=%s", + static_cast(request->framework), + request->model_path ? request->model_path : "NULL", + request->identifier ? request->identifier : "NULL"); + + // Framework hint from model registry + if (request->framework == RAC_FRAMEWORK_LLAMACPP) { + RAC_LOG_DEBUG(LOG_CAT, "can_handle: YES (framework match)"); + return RAC_TRUE; + } + + // If framework is explicitly set to something else (not unknown), don't handle + if (request->framework != RAC_FRAMEWORK_UNKNOWN) { + RAC_LOG_DEBUG(LOG_CAT, "can_handle: NO (framework mismatch)"); + return RAC_FALSE; + } + + // Framework unknown - check file extension for GGUF + const char* path = request->model_path ? request->model_path : request->identifier; + if (path == nullptr || path[0] == '\0') { + RAC_LOG_DEBUG(LOG_CAT, "can_handle: NO (no path)"); + return RAC_FALSE; + } + + size_t len = strlen(path); + if (len >= 5) { + const char* ext = path + len - 5; + if (strcmp(ext, ".gguf") == 0 || strcmp(ext, ".GGUF") == 0) { + RAC_LOG_DEBUG(LOG_CAT, "can_handle: YES (gguf extension)"); + return RAC_TRUE; + } + } + + RAC_LOG_DEBUG(LOG_CAT, "can_handle: NO (no gguf extension in path: %s)", path); + return RAC_FALSE; +} + +/** + * Create a LlamaCPP VLM service with vtable. + */ +rac_handle_t llamacpp_vlm_create_service(const rac_service_request_t* request, void* user_data) { + (void)user_data; + + if (request == nullptr) { + return nullptr; + } + + const char* model_path = request->model_path ? request->model_path : request->identifier; + if (model_path == nullptr || model_path[0] == '\0') { + RAC_LOG_ERROR(LOG_CAT, "No model path provided"); + return nullptr; + } + + RAC_LOG_INFO(LOG_CAT, "Creating LlamaCPP VLM service for: %s", model_path); + + // Create backend-specific handle + rac_handle_t backend_handle = nullptr; + rac_result_t result = rac_vlm_llamacpp_create(model_path, nullptr, nullptr, &backend_handle); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR(LOG_CAT, "Failed to create LlamaCPP VLM backend: %d", result); + return nullptr; + } + + // Allocate service struct with vtable + auto* service = static_cast(malloc(sizeof(rac_vlm_service_t))); + if (!service) { + rac_vlm_llamacpp_destroy(backend_handle); + return nullptr; + } + + service->ops = &g_llamacpp_vlm_ops; + service->impl = backend_handle; + service->model_id = request->identifier ? strdup(request->identifier) : nullptr; + + RAC_LOG_INFO(LOG_CAT, "LlamaCPP VLM service created successfully"); + return service; +} + +} // namespace + +// ============================================================================= +// REGISTRATION API +// ============================================================================= + +extern "C" { + +rac_result_t rac_backend_llamacpp_vlm_register(void) { + auto& state = get_state(); + std::lock_guard lock(state.mutex); + + if (state.registered) { + return RAC_ERROR_MODULE_ALREADY_REGISTERED; + } + + // Register module + rac_module_info_t module_info = {}; + module_info.id = state.module_id; + module_info.name = "LlamaCPP VLM"; + module_info.version = "1.0.0"; + module_info.description = "VLM backend using llama.cpp for GGUF vision-language models"; + + rac_capability_t capabilities[] = {RAC_CAPABILITY_VISION_LANGUAGE}; + module_info.capabilities = capabilities; + module_info.num_capabilities = 1; + + rac_result_t result = rac_module_register(&module_info); + if (result != RAC_SUCCESS && result != RAC_ERROR_MODULE_ALREADY_REGISTERED) { + return result; + } + + // Register service provider with priority 100 (same as LLM llamacpp) + rac_service_provider_t provider = {}; + provider.name = state.provider_name; + provider.capability = RAC_CAPABILITY_VISION_LANGUAGE; + provider.priority = 100; + provider.can_handle = llamacpp_vlm_can_handle; + provider.create = llamacpp_vlm_create_service; + provider.user_data = nullptr; + + result = rac_service_register_provider(&provider); + if (result != RAC_SUCCESS) { + rac_module_unregister(state.module_id); + return result; + } + + state.registered = true; + RAC_LOG_INFO(LOG_CAT, "VLM backend registered successfully"); + return RAC_SUCCESS; +} + +rac_result_t rac_backend_llamacpp_vlm_unregister(void) { + auto& state = get_state(); + std::lock_guard lock(state.mutex); + + if (!state.registered) { + return RAC_ERROR_MODULE_NOT_FOUND; + } + + rac_service_unregister_provider(state.provider_name, RAC_CAPABILITY_VISION_LANGUAGE); + rac_module_unregister(state.module_id); + + state.registered = false; + RAC_LOG_INFO(LOG_CAT, "VLM backend unregistered"); + return RAC_SUCCESS; +} + +} // extern "C" diff --git a/sdk/runanywhere-commons/src/backends/llamacpp/rac_vlm_llamacpp.cpp b/sdk/runanywhere-commons/src/backends/llamacpp/rac_vlm_llamacpp.cpp new file mode 100644 index 000000000..8e2c9ec52 --- /dev/null +++ b/sdk/runanywhere-commons/src/backends/llamacpp/rac_vlm_llamacpp.cpp @@ -0,0 +1,892 @@ +/** + * @file rac_vlm_llamacpp.cpp + * @brief RunAnywhere Commons - LlamaCPP VLM Backend Implementation + * + * Vision Language Model backend using llama.cpp's multimodal (mtmd) API. + * Supports VLM architectures including Qwen2-VL, SmolVLM, LLaVA, MiniCPM-V, etc. + * + * Updated for llama.cpp b7650+ mtmd API. + */ + +#include "rac/backends/rac_vlm_llamacpp.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// llama.cpp multimodal support (mtmd) +#ifdef RAC_VLM_USE_MTMD +#include "clip.h" +#include "mtmd.h" +#include "mtmd-helper.h" +#endif + +#include "rac/core/rac_logger.h" +#include "rac/utils/rac_image_utils.h" + +static const char* LOG_CAT = "VLM.LlamaCPP"; + +// ============================================================================= +// INTERNAL BACKEND STATE +// ============================================================================= + +namespace { + +/** + * Internal VLM backend state. + */ +// Forward declaration +enum class VLMModelType; + +struct LlamaCppVLMBackend { + // llama.cpp model and context + llama_model* model = nullptr; + llama_context* ctx = nullptr; + llama_sampler* sampler = nullptr; + +#ifdef RAC_VLM_USE_MTMD + // Multimodal context (vision projector) + mtmd_context* mtmd_ctx = nullptr; +#endif + + // Configuration + rac_vlm_llamacpp_config_t config = RAC_VLM_LLAMACPP_CONFIG_DEFAULT; + + // State + bool model_loaded = false; + std::atomic cancel_requested{false}; + + // Model info + std::string model_path; + std::string mmproj_path; + int context_size = 0; + llama_pos n_past = 0; + + // Detected model type for chat template + VLMModelType model_type = static_cast(0); // Unknown + + // Thread safety + mutable std::mutex mutex; +}; + +/** + * Get number of CPU threads to use. + */ +int get_num_threads(int config_threads) { + if (config_threads > 0) + return config_threads; + + // Auto-detect based on hardware + int threads = std::thread::hardware_concurrency(); + if (threads <= 0) + threads = 4; + if (threads > 8) + threads = 8; // Cap for mobile devices + return threads; +} + +// ============================================================================= +// CHAT TEMPLATE HELPERS +// ============================================================================= + +/** + * VLM model type for chat template selection. + */ +enum class VLMModelType { + Unknown, + SmolVLM, // SmolVLM uses "User:" / "Assistant:" format + Qwen2VL, // Qwen2-VL uses chatml with <|im_start|>user format + LLaVA, // LLaVA uses "USER:" / "ASSISTANT:" format + Generic // Generic chatml fallback +}; + +/** + * Detect VLM model type from model name metadata. + */ +VLMModelType detect_vlm_model_type(llama_model* model) { + if (!model) return VLMModelType::Generic; + + // Try to get model name from metadata + char name_buf[256] = {0}; + int32_t len = llama_model_meta_val_str(model, "general.name", name_buf, sizeof(name_buf)); + if (len <= 0) { + len = llama_model_meta_val_str(model, "general.basename", name_buf, sizeof(name_buf)); + } + + if (len > 0) { + std::string name(name_buf); + // Convert to lowercase for comparison + for (auto& c : name) c = static_cast(std::tolower(static_cast(c))); + + RAC_LOG_DEBUG(LOG_CAT, "Model name from metadata: %s", name.c_str()); + + if (name.find("smolvlm") != std::string::npos || + name.find("smol") != std::string::npos) { + RAC_LOG_DEBUG(LOG_CAT, "Detected SmolVLM model type"); + return VLMModelType::SmolVLM; + } + if (name.find("qwen") != std::string::npos) { + RAC_LOG_DEBUG(LOG_CAT, "Detected Qwen2-VL model type"); + return VLMModelType::Qwen2VL; + } + if (name.find("llava") != std::string::npos) { + RAC_LOG_DEBUG(LOG_CAT, "Detected LLaVA model type"); + return VLMModelType::LLaVA; + } + } + + // Check chat template as fallback + const char* chat_template = llama_model_chat_template(model, nullptr); + if (chat_template) { + std::string tmpl(chat_template); + if (tmpl.find("User:") != std::string::npos && + tmpl.find("Assistant:") != std::string::npos) { + RAC_LOG_DEBUG(LOG_CAT, "Detected SmolVLM model type from chat template"); + return VLMModelType::SmolVLM; + } + } + + RAC_LOG_DEBUG(LOG_CAT, "Using generic chat template"); + return VLMModelType::Generic; +} + +/** + * Format prompt using model's built-in chat template via llama_chat_apply_template. + * Falls back to manual formatting if template application fails. + */ +std::string format_vlm_prompt_with_template(llama_model* model, const std::string& user_prompt, + const char* image_marker, bool has_image) { + // Build user content with image marker if present + std::string user_content; + if (has_image) { + user_content = std::string(image_marker) + user_prompt; + } else { + user_content = user_prompt; + } + + // Get the model's chat template + const char* tmpl = llama_model_chat_template(model, nullptr); + + // Try to use llama_chat_apply_template + if (tmpl) { + RAC_LOG_DEBUG(LOG_CAT, "Using model chat template: %.80s...", tmpl); + + llama_chat_message messages[1]; + messages[0].role = "user"; + messages[0].content = user_content.c_str(); + + // First call to get required buffer size + int32_t size = llama_chat_apply_template(tmpl, messages, 1, true, nullptr, 0); + if (size > 0) { + std::vector buf(size + 1); + int32_t result = llama_chat_apply_template(tmpl, messages, 1, true, buf.data(), buf.size()); + if (result > 0) { + std::string formatted(buf.data(), result); + RAC_LOG_DEBUG(LOG_CAT, "Template-formatted prompt (%d chars): %s", + (int)formatted.length(), formatted.c_str()); + return formatted; + } + } + RAC_LOG_WARNING(LOG_CAT, "llama_chat_apply_template failed (size=%d), falling back to manual", size); + } else { + RAC_LOG_DEBUG(LOG_CAT, "No chat template in model, using manual formatting"); + } + + // Fallback: manual chatml format (works for most models) + std::string formatted = "<|im_start|>user\n"; + formatted += user_content; + formatted += "<|im_end|>\n<|im_start|>assistant\n"; + + RAC_LOG_DEBUG(LOG_CAT, "Manual-formatted prompt (%d chars): %s", + (int)formatted.length(), formatted.c_str()); + return formatted; +} + +/** + * Legacy format function for backward compatibility. + * Uses model type detection for manual template selection. + */ +std::string format_vlm_prompt(VLMModelType model_type, const std::string& user_prompt, + const char* image_marker, bool has_image) { + std::string formatted; + + // Build user content with image marker + std::string user_content; + if (has_image) { + user_content = std::string(image_marker) + user_prompt; + } else { + user_content = user_prompt; + } + + switch (model_type) { + case VLMModelType::SmolVLM: + // SmolVLM format: <|im_start|>User: content \nAssistant: + formatted = "<|im_start|>User: "; + formatted += user_content; + formatted += " \nAssistant:"; + break; + + case VLMModelType::Qwen2VL: + // Qwen2-VL chatml format + formatted = "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"; + formatted += "<|im_start|>user\n"; + formatted += user_content; + formatted += "<|im_end|>\n<|im_start|>assistant\n"; + break; + + case VLMModelType::LLaVA: + // LLaVA/Vicuna format + formatted = "USER: "; + formatted += user_content; + formatted += "\nASSISTANT:"; + break; + + case VLMModelType::Generic: + default: + // Generic chatml format + formatted = "<|im_start|>user\n"; + formatted += user_content; + formatted += "<|im_end|>\n<|im_start|>assistant\n"; + break; + } + + RAC_LOG_DEBUG(LOG_CAT, "Formatted prompt (%d chars): %.100s...", + (int)formatted.length(), formatted.c_str()); + return formatted; +} + +/** + * Get the image marker string. + * When mtmd is available, uses the default marker from mtmd. + * Otherwise falls back to a generic "" marker. + */ +const char* get_image_marker() { +#ifdef RAC_VLM_USE_MTMD + return mtmd_default_marker(); +#else + return ""; +#endif +} + +/** + * Configure the sampler chain with the given generation parameters. + * Rebuilds the sampler to apply per-request temperature, top_p, etc. + */ +void configure_sampler(LlamaCppVLMBackend* backend, const rac_vlm_options_t* options) { + // Free existing sampler + if (backend->sampler) { + llama_sampler_free(backend->sampler); + backend->sampler = nullptr; + } + + // Determine parameters from options or use defaults + float temperature = 0.7f; + float top_p = 0.9f; + + if (options) { + if (options->temperature >= 0.0f) { + temperature = options->temperature; + } + if (options->top_p > 0.0f && options->top_p <= 1.0f) { + top_p = options->top_p; + } + } + + // Build new sampler chain + llama_sampler_chain_params sampler_params = llama_sampler_chain_default_params(); + backend->sampler = llama_sampler_chain_init(sampler_params); + llama_sampler_chain_add(backend->sampler, llama_sampler_init_temp(temperature)); + llama_sampler_chain_add(backend->sampler, llama_sampler_init_top_p(top_p, 1)); + llama_sampler_chain_add(backend->sampler, llama_sampler_init_dist(LLAMA_DEFAULT_SEED)); + + RAC_LOG_DEBUG(LOG_CAT, "Sampler configured: temp=%.2f, top_p=%.2f", temperature, top_p); +} + +} // namespace + +// ============================================================================= +// LIFECYCLE MANAGEMENT +// ============================================================================= + +extern "C" { + +rac_result_t rac_vlm_llamacpp_create(const char* model_path, const char* mmproj_path, + const rac_vlm_llamacpp_config_t* config, + rac_handle_t* out_handle) { + if (!out_handle) { + return RAC_ERROR_NULL_POINTER; + } + + auto* backend = new (std::nothrow) LlamaCppVLMBackend(); + if (!backend) { + return RAC_ERROR_OUT_OF_MEMORY; + } + + if (config) { + backend->config = *config; + } + + if (model_path) { + backend->model_path = model_path; + } + if (mmproj_path) { + backend->mmproj_path = mmproj_path; + } + + *out_handle = backend; + RAC_LOG_INFO(LOG_CAT, "Created VLM backend"); + return RAC_SUCCESS; +} + +rac_result_t rac_vlm_llamacpp_load_model(rac_handle_t handle, const char* model_path, + const char* mmproj_path, + const rac_vlm_llamacpp_config_t* config) { + if (!handle || !model_path) { + return RAC_ERROR_NULL_POINTER; + } + + auto* backend = static_cast(handle); + std::lock_guard lock(backend->mutex); + + // Update config if provided + if (config) { + backend->config = *config; + } + + RAC_LOG_INFO(LOG_CAT, "Loading VLM model: %s", model_path); + if (mmproj_path) { + RAC_LOG_INFO(LOG_CAT, "With vision projector: %s", mmproj_path); + } + + // Initialize llama backend + llama_backend_init(); + + // Load model + llama_model_params model_params = llama_model_default_params(); + model_params.n_gpu_layers = backend->config.gpu_layers; + + backend->model = llama_model_load_from_file(model_path, model_params); + if (!backend->model) { + RAC_LOG_ERROR(LOG_CAT, "Failed to load model: %s", model_path); + return RAC_ERROR_MODEL_LOAD_FAILED; + } + + // Determine context size + int ctx_size = backend->config.context_size; + if (ctx_size <= 0) { + ctx_size = llama_model_n_ctx_train(backend->model); + if (ctx_size > 4096) ctx_size = 4096; // Cap for mobile + } + backend->context_size = ctx_size; + + // Create context + int n_threads = get_num_threads(backend->config.num_threads); + llama_context_params ctx_params = llama_context_default_params(); + ctx_params.n_ctx = ctx_size; + ctx_params.n_batch = backend->config.batch_size > 0 ? backend->config.batch_size : 512; + ctx_params.n_threads = n_threads; + ctx_params.n_threads_batch = n_threads; + + backend->ctx = llama_init_from_model(backend->model, ctx_params); + if (!backend->ctx) { + RAC_LOG_ERROR(LOG_CAT, "Failed to create context"); + llama_model_free(backend->model); + backend->model = nullptr; + return RAC_ERROR_MODEL_LOAD_FAILED; + } + + // Initialize sampler with default parameters + // Sampler is reconfigured per-request in process()/process_stream() to respect user options + configure_sampler(backend, nullptr); + +#ifdef RAC_VLM_USE_MTMD + // Initialize mtmd context if mmproj provided + if (mmproj_path && mmproj_path[0]) { + mtmd_context_params mparams = mtmd_context_params_default(); + mparams.use_gpu = backend->config.use_gpu_vision; + mparams.n_threads = n_threads; + mparams.print_timings = false; + mparams.warmup = true; + + backend->mtmd_ctx = mtmd_init_from_file(mmproj_path, backend->model, mparams); + if (!backend->mtmd_ctx) { + RAC_LOG_ERROR(LOG_CAT, "Failed to load vision projector: %s", mmproj_path); + // Continue without vision - will work as text-only LLM + RAC_LOG_WARNING(LOG_CAT, "VLM will operate in text-only mode"); + } else { + RAC_LOG_INFO(LOG_CAT, "Vision projector loaded successfully"); + } + backend->mmproj_path = mmproj_path; + } +#endif + + backend->model_path = model_path; + backend->model_loaded = true; + backend->n_past = 0; + + // Detect model type for chat template + backend->model_type = detect_vlm_model_type(backend->model); + + RAC_LOG_INFO(LOG_CAT, "VLM model loaded successfully (ctx=%d, threads=%d)", ctx_size, n_threads); + return RAC_SUCCESS; +} + +rac_result_t rac_vlm_llamacpp_unload_model(rac_handle_t handle) { + if (!handle) { + return RAC_ERROR_NULL_POINTER; + } + + auto* backend = static_cast(handle); + std::lock_guard lock(backend->mutex); + +#ifdef RAC_VLM_USE_MTMD + if (backend->mtmd_ctx) { + mtmd_free(backend->mtmd_ctx); + backend->mtmd_ctx = nullptr; + } +#endif + + if (backend->sampler) { + llama_sampler_free(backend->sampler); + backend->sampler = nullptr; + } + + if (backend->ctx) { + llama_free(backend->ctx); + backend->ctx = nullptr; + } + + if (backend->model) { + llama_model_free(backend->model); + backend->model = nullptr; + } + + backend->model_loaded = false; + backend->n_past = 0; + RAC_LOG_INFO(LOG_CAT, "VLM model unloaded"); + return RAC_SUCCESS; +} + +rac_bool_t rac_vlm_llamacpp_is_model_loaded(rac_handle_t handle) { + if (!handle) return RAC_FALSE; + auto* backend = static_cast(handle); + return backend->model_loaded ? RAC_TRUE : RAC_FALSE; +} + +void rac_vlm_llamacpp_destroy(rac_handle_t handle) { + if (!handle) return; + + auto* backend = static_cast(handle); + + // Unload model first + rac_vlm_llamacpp_unload_model(handle); + + delete backend; + RAC_LOG_INFO(LOG_CAT, "VLM backend destroyed"); +} + +// ============================================================================= +// INFERENCE +// ============================================================================= + +rac_result_t rac_vlm_llamacpp_process(rac_handle_t handle, const rac_vlm_image_t* image, + const char* prompt, const rac_vlm_options_t* options, + rac_vlm_result_t* out_result) { + if (!handle || !prompt || !out_result) { + return RAC_ERROR_NULL_POINTER; + } + + auto* backend = static_cast(handle); + std::lock_guard lock(backend->mutex); + + if (!backend->model_loaded) { + RAC_LOG_ERROR(LOG_CAT, "No model loaded"); + return RAC_ERROR_MODEL_NOT_LOADED; + } + + backend->cancel_requested = false; + + // Reconfigure sampler with per-request options (temperature, top_p) + configure_sampler(backend, options); + + // Clear KV cache (memory) before each new request to avoid position conflicts + llama_memory_t mem = llama_get_memory(backend->ctx); + if (mem) { + llama_memory_clear(mem, true); + } + backend->n_past = 0; + + // Build the prompt with proper chat template formatting + std::string full_prompt; + bool has_image = false; + const char* image_marker = get_image_marker(); + +#ifdef RAC_VLM_USE_MTMD + mtmd_bitmap* bitmap = nullptr; + + if (image && backend->mtmd_ctx) { + // Load image based on format + if (image->format == RAC_VLM_IMAGE_FORMAT_FILE_PATH && image->file_path) { + bitmap = mtmd_helper_bitmap_init_from_file(backend->mtmd_ctx, image->file_path); + } else if (image->format == RAC_VLM_IMAGE_FORMAT_RGB_PIXELS && image->pixel_data) { + bitmap = mtmd_bitmap_init(image->width, image->height, image->pixel_data); + } else if (image->format == RAC_VLM_IMAGE_FORMAT_BASE64 && image->base64_data) { + // Decode base64 first + // For now, skip base64 - would need base64 decoder + RAC_LOG_WARNING(LOG_CAT, "Base64 image format not yet supported, using text-only"); + } + + has_image = (bitmap != nullptr); + if (!has_image && image->format != RAC_VLM_IMAGE_FORMAT_BASE64) { + RAC_LOG_ERROR(LOG_CAT, "Failed to load image"); + return RAC_ERROR_INVALID_INPUT; + } + } + + // Format prompt using model's built-in chat template + full_prompt = format_vlm_prompt_with_template(backend->model, prompt, image_marker, has_image); + + // Tokenize and evaluate + if (backend->mtmd_ctx && bitmap) { + mtmd_input_chunks* chunks = mtmd_input_chunks_init(); + + mtmd_input_text text; + text.text = full_prompt.c_str(); + text.add_special = true; + text.parse_special = true; + + const mtmd_bitmap* bitmaps[] = { bitmap }; + int32_t tokenize_result = mtmd_tokenize(backend->mtmd_ctx, chunks, &text, bitmaps, 1); + + if (tokenize_result != 0) { + RAC_LOG_ERROR(LOG_CAT, "Failed to tokenize prompt with image: %d", tokenize_result); + mtmd_bitmap_free(bitmap); + mtmd_input_chunks_free(chunks); + return RAC_ERROR_PROCESSING_FAILED; + } + + // Evaluate chunks + llama_pos new_n_past = 0; + int32_t eval_result = mtmd_helper_eval_chunks( + backend->mtmd_ctx, + backend->ctx, + chunks, + 0, // n_past + 0, // seq_id + backend->config.batch_size > 0 ? backend->config.batch_size : 512, + true, // logits_last + &new_n_past + ); + + mtmd_bitmap_free(bitmap); + mtmd_input_chunks_free(chunks); + + if (eval_result != 0) { + RAC_LOG_ERROR(LOG_CAT, "Failed to evaluate chunks: %d", eval_result); + return RAC_ERROR_PROCESSING_FAILED; + } + + backend->n_past = new_n_past; + } else +#endif + { + // Text-only mode - still apply chat template for consistent formatting + full_prompt = format_vlm_prompt_with_template(backend->model, prompt, image_marker, false); + + const llama_vocab* vocab = llama_model_get_vocab(backend->model); + std::vector tokens(full_prompt.size() + 16); + int n_tokens = llama_tokenize(vocab, full_prompt.c_str(), full_prompt.size(), + tokens.data(), tokens.size(), true, true); + if (n_tokens < 0) { + tokens.resize(-n_tokens); + n_tokens = llama_tokenize(vocab, full_prompt.c_str(), full_prompt.size(), + tokens.data(), tokens.size(), true, true); + } + tokens.resize(n_tokens); + + // Create batch and decode + llama_batch batch = llama_batch_init(n_tokens, 0, 1); + for (int i = 0; i < n_tokens; i++) { + batch.token[i] = tokens[i]; + batch.pos[i] = i; + batch.n_seq_id[i] = 1; + batch.seq_id[i][0] = 0; + batch.logits[i] = (i == n_tokens - 1); + } + batch.n_tokens = n_tokens; + + if (llama_decode(backend->ctx, batch) != 0) { + llama_batch_free(batch); + RAC_LOG_ERROR(LOG_CAT, "Failed to decode prompt"); + return RAC_ERROR_PROCESSING_FAILED; + } + + llama_batch_free(batch); + backend->n_past = n_tokens; + } + + // Generate response + int max_tokens = (options && options->max_tokens > 0) ? options->max_tokens : 2048; + std::string response; + int tokens_generated = 0; + + llama_batch batch = llama_batch_init(1, 0, 1); + const llama_vocab* vocab = llama_model_get_vocab(backend->model); + + for (int i = 0; i < max_tokens && !backend->cancel_requested; i++) { + llama_token token = llama_sampler_sample(backend->sampler, backend->ctx, -1); + llama_sampler_accept(backend->sampler, token); + + if (llama_vocab_is_eog(vocab, token)) { + break; + } + + char buf[256]; + int len = llama_token_to_piece(vocab, token, buf, sizeof(buf), 0, true); + if (len > 0) { + response.append(buf, len); + } + tokens_generated++; + + // Prepare next token + batch.token[0] = token; + batch.pos[0] = backend->n_past++; + batch.n_seq_id[0] = 1; + batch.seq_id[0][0] = 0; + batch.logits[0] = true; + batch.n_tokens = 1; + + if (llama_decode(backend->ctx, batch) != 0) { + break; + } + } + + llama_batch_free(batch); + + // Fill result + out_result->text = strdup(response.c_str()); + out_result->completion_tokens = tokens_generated; + out_result->prompt_tokens = backend->n_past - tokens_generated; + out_result->total_tokens = backend->n_past; + + RAC_LOG_INFO(LOG_CAT, "Generated %d tokens", tokens_generated); + return RAC_SUCCESS; +} + +rac_result_t rac_vlm_llamacpp_process_stream(rac_handle_t handle, const rac_vlm_image_t* image, + const char* prompt, const rac_vlm_options_t* options, + rac_vlm_llamacpp_stream_callback_fn callback, + void* user_data) { + if (!handle || !prompt || !callback) { + return RAC_ERROR_NULL_POINTER; + } + + auto* backend = static_cast(handle); + std::lock_guard lock(backend->mutex); + + if (!backend->model_loaded) { + RAC_LOG_ERROR(LOG_CAT, "No model loaded"); + return RAC_ERROR_MODEL_NOT_LOADED; + } + + backend->cancel_requested = false; + + // Reconfigure sampler with per-request options (temperature, top_p) + configure_sampler(backend, options); + + // Clear KV cache (memory) before each new request to avoid position conflicts + llama_memory_t mem = llama_get_memory(backend->ctx); + if (mem) { + llama_memory_clear(mem, true); + } + backend->n_past = 0; + RAC_LOG_DEBUG(LOG_CAT, "Cleared KV cache for new request"); + + // Build the prompt with proper chat template formatting + std::string full_prompt; + bool has_image = false; + const char* image_marker = get_image_marker(); + +#ifdef RAC_VLM_USE_MTMD + mtmd_bitmap* bitmap = nullptr; + + if (image && backend->mtmd_ctx) { + // Load image based on format + if (image->format == RAC_VLM_IMAGE_FORMAT_FILE_PATH && image->file_path) { + bitmap = mtmd_helper_bitmap_init_from_file(backend->mtmd_ctx, image->file_path); + } else if (image->format == RAC_VLM_IMAGE_FORMAT_RGB_PIXELS && image->pixel_data) { + bitmap = mtmd_bitmap_init(image->width, image->height, image->pixel_data); + } + + has_image = (bitmap != nullptr); + if (!has_image) { + RAC_LOG_WARNING(LOG_CAT, "Failed to load image, using text-only"); + } + } + + // Format prompt using model's built-in chat template + full_prompt = format_vlm_prompt_with_template(backend->model, prompt, image_marker, has_image); + + // Tokenize and evaluate + if (backend->mtmd_ctx && bitmap) { + mtmd_input_chunks* chunks = mtmd_input_chunks_init(); + + mtmd_input_text text; + text.text = full_prompt.c_str(); + text.add_special = true; + text.parse_special = true; + + const mtmd_bitmap* bitmaps[] = { bitmap }; + int32_t tokenize_result = mtmd_tokenize(backend->mtmd_ctx, chunks, &text, bitmaps, 1); + + if (tokenize_result != 0) { + RAC_LOG_ERROR(LOG_CAT, "Failed to tokenize prompt with image: %d", tokenize_result); + mtmd_bitmap_free(bitmap); + mtmd_input_chunks_free(chunks); + return RAC_ERROR_PROCESSING_FAILED; + } + + // Evaluate chunks + llama_pos new_n_past = 0; + int32_t eval_result = mtmd_helper_eval_chunks( + backend->mtmd_ctx, + backend->ctx, + chunks, + 0, // n_past + 0, // seq_id + backend->config.batch_size > 0 ? backend->config.batch_size : 512, + true, // logits_last + &new_n_past + ); + + mtmd_bitmap_free(bitmap); + mtmd_input_chunks_free(chunks); + + if (eval_result != 0) { + RAC_LOG_ERROR(LOG_CAT, "Failed to evaluate chunks: %d", eval_result); + return RAC_ERROR_PROCESSING_FAILED; + } + + backend->n_past = new_n_past; + } else +#endif + { + // Text-only mode - still apply chat template for consistent formatting + full_prompt = format_vlm_prompt_with_template(backend->model, prompt, image_marker, false); + + const llama_vocab* vocab = llama_model_get_vocab(backend->model); + std::vector tokens(full_prompt.size() + 16); + int n_tokens = llama_tokenize(vocab, full_prompt.c_str(), full_prompt.size(), + tokens.data(), tokens.size(), true, true); + if (n_tokens < 0) { + tokens.resize(-n_tokens); + n_tokens = llama_tokenize(vocab, full_prompt.c_str(), full_prompt.size(), + tokens.data(), tokens.size(), true, true); + } + tokens.resize(n_tokens); + + llama_batch batch = llama_batch_init(n_tokens, 0, 1); + for (int i = 0; i < n_tokens; i++) { + batch.token[i] = tokens[i]; + batch.pos[i] = i; + batch.n_seq_id[i] = 1; + batch.seq_id[i][0] = 0; + batch.logits[i] = (i == n_tokens - 1); + } + batch.n_tokens = n_tokens; + + if (llama_decode(backend->ctx, batch) != 0) { + llama_batch_free(batch); + return RAC_ERROR_PROCESSING_FAILED; + } + + llama_batch_free(batch); + backend->n_past = n_tokens; + } + + // Generate response with streaming + int max_tokens = (options && options->max_tokens > 0) ? options->max_tokens : 2048; + + llama_batch batch = llama_batch_init(1, 0, 1); + const llama_vocab* vocab = llama_model_get_vocab(backend->model); + + for (int i = 0; i < max_tokens && !backend->cancel_requested; i++) { + llama_token token = llama_sampler_sample(backend->sampler, backend->ctx, -1); + llama_sampler_accept(backend->sampler, token); + + bool is_eog = llama_vocab_is_eog(vocab, token); + + char buf[256]; + int len = llama_token_to_piece(vocab, token, buf, sizeof(buf), 0, true); + if (len > 0) { + buf[len] = '\0'; + if (callback(buf, is_eog ? RAC_TRUE : RAC_FALSE, user_data) == RAC_FALSE) { + break; // Callback requested stop + } + } + + if (is_eog) { + break; + } + + // Prepare next token + batch.token[0] = token; + batch.pos[0] = backend->n_past++; + batch.n_seq_id[0] = 1; + batch.seq_id[0][0] = 0; + batch.logits[0] = true; + batch.n_tokens = 1; + + if (llama_decode(backend->ctx, batch) != 0) { + break; + } + } + + llama_batch_free(batch); + return RAC_SUCCESS; +} + +void rac_vlm_llamacpp_cancel(rac_handle_t handle) { + if (!handle) return; + auto* backend = static_cast(handle); + backend->cancel_requested = true; +} + +rac_result_t rac_vlm_llamacpp_get_model_info(rac_handle_t handle, char** out_json) { + if (!handle || !out_json) { + return RAC_ERROR_NULL_POINTER; + } + + auto* backend = static_cast(handle); + std::lock_guard lock(backend->mutex); + + if (!backend->model_loaded) { + return RAC_ERROR_MODEL_NOT_LOADED; + } + + // Build simple JSON info + char buffer[1024]; + snprintf(buffer, sizeof(buffer), + "{\"context_size\":%d,\"model_path\":\"%s\",\"has_vision\":%s}", + backend->context_size, + backend->model_path.c_str(), +#ifdef RAC_VLM_USE_MTMD + backend->mtmd_ctx ? "true" : "false" +#else + "false" +#endif + ); + + *out_json = strdup(buffer); + return RAC_SUCCESS; +} + +} // extern "C" diff --git a/sdk/runanywhere-commons/src/backends/onnx/CMakeLists.txt b/sdk/runanywhere-commons/src/backends/onnx/CMakeLists.txt index da53474e1..11aedb8bc 100644 --- a/sdk/runanywhere-commons/src/backends/onnx/CMakeLists.txt +++ b/sdk/runanywhere-commons/src/backends/onnx/CMakeLists.txt @@ -123,8 +123,9 @@ elseif(RAC_PLATFORM_MACOS) endif() # ============================================================================= -# ONNX Backend Library +# ONNX Backend Library (STT, TTS, VAD only) # ============================================================================= +# Diffusion is Apple CoreML-only; no ONNX diffusion in this backend. set(ONNX_BACKEND_SOURCES onnx_backend.cpp @@ -177,6 +178,9 @@ if(RAC_PLATFORM_IOS) "-framework CoreML" "-framework Accelerate" ) + # CoreML Execution Provider is available in ONNX Runtime iOS package + target_compile_definitions(rac_backend_onnx PRIVATE ORT_USE_COREML=1) + message(STATUS "ONNX Backend: CoreML EP enabled for iOS") elseif(RAC_PLATFORM_ANDROID) target_link_libraries(rac_backend_onnx PRIVATE log) @@ -184,6 +188,9 @@ elseif(RAC_PLATFORM_ANDROID) target_compile_options(rac_backend_onnx PRIVATE -O3 -ffunction-sections -fdata-sections) # 16KB page alignment for Android 15+ (API 35) compliance - required Nov 2025 target_link_options(rac_backend_onnx PRIVATE -Wl,--gc-sections -Wl,-z,max-page-size=16384) + # NNAPI Execution Provider for Android NPU acceleration + target_compile_definitions(rac_backend_onnx PRIVATE ORT_USE_NNAPI=1) + message(STATUS "ONNX Backend: NNAPI EP enabled for Android") elseif(RAC_PLATFORM_MACOS) target_link_libraries(rac_backend_onnx PUBLIC @@ -191,6 +198,9 @@ elseif(RAC_PLATFORM_MACOS) "-framework CoreML" "-framework Accelerate" ) + # CoreML Execution Provider is available in ONNX Runtime macOS package + target_compile_definitions(rac_backend_onnx PRIVATE ORT_USE_COREML=1) + message(STATUS "ONNX Backend: CoreML EP enabled for macOS") endif() # ============================================================================= @@ -226,4 +236,5 @@ endif() message(STATUS "ONNX Backend Configuration:") message(STATUS " ONNX Runtime: Enabled") message(STATUS " Sherpa-ONNX: ${SHERPA_ONNX_AVAILABLE}") +message(STATUS " Diffusion: Not in ONNX backend (Apple CoreML only)") message(STATUS " Platform: ${RAC_PLATFORM_NAME}") diff --git a/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.cpp b/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.cpp index d48f45b60..8ec7fd929 100644 --- a/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.cpp +++ b/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.cpp @@ -4,6 +4,12 @@ * This file implements the ONNX backend using: * - ONNX Runtime for general ML inference * - Sherpa-ONNX for speech tasks (STT, TTS, VAD) + * + * โš ๏ธ SHERPA-ONNX VERSION DEPENDENCY: + * The SherpaOnnx*Config structs used here MUST match the prebuilt + * libsherpa-onnx-c-api.so exactly (same version of c-api.h). + * A mismatch causes SIGSEGV due to ABI/struct layout differences. + * See VERSIONS file for the current SHERPA_ONNX_VERSION_ANDROID. */ #include "onnx_backend.h" @@ -227,6 +233,13 @@ bool ONNXSTT::load_model(const std::string& model_path, STTModelType model_type, return false; } + // Keep path strings in members so config pointers stay valid for recognizer lifetime + encoder_path_ = encoder_path; + decoder_path_ = decoder_path; + tokens_path_ = tokens_path; + + // Initialize all config fields explicitly to avoid any uninitialized pointer issues. + // The struct layout MUST match the prebuilt libsherpa-onnx-c-api.so version (v1.12.20). SherpaOnnxOfflineRecognizerConfig recognizer_config; memset(&recognizer_config, 0, sizeof(recognizer_config)); @@ -240,13 +253,13 @@ bool ONNXSTT::load_model(const std::string& model_path, STTModelType model_type, recognizer_config.model_config.nemo_ctc.model = ""; recognizer_config.model_config.tdnn.model = ""; - recognizer_config.model_config.whisper.encoder = encoder_path.c_str(); - recognizer_config.model_config.whisper.decoder = decoder_path.c_str(); + recognizer_config.model_config.whisper.encoder = encoder_path_.c_str(); + recognizer_config.model_config.whisper.decoder = decoder_path_.c_str(); recognizer_config.model_config.whisper.language = language_.c_str(); recognizer_config.model_config.whisper.task = "transcribe"; recognizer_config.model_config.whisper.tail_paddings = -1; - recognizer_config.model_config.tokens = tokens_path.c_str(); + recognizer_config.model_config.tokens = tokens_path_.c_str(); recognizer_config.model_config.num_threads = 2; recognizer_config.model_config.debug = 1; recognizer_config.model_config.provider = "cpu"; @@ -278,6 +291,10 @@ bool ONNXSTT::load_model(const std::string& model_path, STTModelType model_type, recognizer_config.model_config.wenet_ctc.model = ""; recognizer_config.model_config.omnilingual.model = ""; + // NOTE: Do NOT set medasr or funasr_nano here - they don't exist in + // Sherpa-ONNX v1.12.20 (the prebuilt .so version). Setting them would shift + // the struct layout and cause SherpaOnnxCreateOfflineRecognizer to crash. + recognizer_config.lm_config.model = ""; recognizer_config.lm_config.scale = 1.0f; diff --git a/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.h b/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.h index d4db37428..3d7390059 100644 --- a/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.h +++ b/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.h @@ -281,6 +281,10 @@ class ONNXSTT { int stream_counter_ = 0; std::string model_dir_; std::string language_; + // Kept alive so config string pointers remain valid for recognizer lifetime + std::string encoder_path_; + std::string decoder_path_; + std::string tokens_path_; mutable std::mutex mutex_; }; diff --git a/sdk/runanywhere-commons/src/backends/onnx/rac_backend_onnx_register.cpp b/sdk/runanywhere-commons/src/backends/onnx/rac_backend_onnx_register.cpp index 20de3076f..222a0cd90 100644 --- a/sdk/runanywhere-commons/src/backends/onnx/rac_backend_onnx_register.cpp +++ b/sdk/runanywhere-commons/src/backends/onnx/rac_backend_onnx_register.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "rac/core/rac_core.h" @@ -23,6 +24,8 @@ #include "rac/infrastructure/model_management/rac_model_strategy.h" #include "rac/infrastructure/model_management/rac_model_types.h" +namespace fs = std::filesystem; + // ============================================================================= // STT VTABLE IMPLEMENTATION // ============================================================================= @@ -58,6 +61,15 @@ static rac_result_t onnx_stt_vtable_initialize(void* impl, const char* model_pat static rac_result_t onnx_stt_vtable_transcribe(void* impl, const void* audio_data, size_t audio_size, const rac_stt_options_t* options, rac_stt_result_t* out_result) { + if (!audio_data || audio_size == 0 || !out_result) { + return RAC_ERROR_INVALID_ARGUMENT; + } + // Minimum ~0.05s at 16kHz 16-bit to avoid Sherpa crash on empty/tiny input + if (audio_size < 1600) { + out_result->text = nullptr; + out_result->confidence = 0.0f; + return RAC_SUCCESS; + } std::vector float_samples = convert_int16_to_float32(audio_data, audio_size); return rac_stt_onnx_transcribe(impl, float_samples.data(), float_samples.size(), options, out_result); @@ -472,13 +484,12 @@ rac_result_t rac_backend_onnx_register(void) { return RAC_ERROR_MODULE_ALREADY_REGISTERED; } - // Register module + // Register module (STT, TTS, VAD only; diffusion is CoreML-only in Swift SDK) rac_module_info_t module_info = {}; module_info.id = MODULE_ID; module_info.name = "ONNX Runtime"; module_info.version = "1.0.0"; - module_info.description = "STT/TTS/VAD backend using ONNX Runtime via Sherpa-ONNX"; - + module_info.description = "STT/TTS/VAD backend using ONNX Runtime"; rac_capability_t capabilities[] = {RAC_CAPABILITY_STT, RAC_CAPABILITY_TTS, RAC_CAPABILITY_VAD}; module_info.capabilities = capabilities; module_info.num_capabilities = 3; diff --git a/sdk/runanywhere-commons/src/core/rac_core.cpp b/sdk/runanywhere-commons/src/core/rac_core.cpp index 09c5c486a..bd73f3958 100644 --- a/sdk/runanywhere-commons/src/core/rac_core.cpp +++ b/sdk/runanywhere-commons/src/core/rac_core.cpp @@ -17,6 +17,9 @@ #include "rac/core/rac_structured_error.h" #include "rac/infrastructure/device/rac_device_manager.h" #include "rac/infrastructure/model_management/rac_model_registry.h" +#if !defined(RAC_PLATFORM_ANDROID) +#include "rac/features/diffusion/rac_diffusion_model_registry.h" +#endif // ============================================================================= // STATIC STATE @@ -104,6 +107,11 @@ rac_result_t rac_init(const rac_config_t* config) { s_initialized.store(true); +#if !defined(RAC_PLATFORM_ANDROID) + // Initialize diffusion model registry (iOS/Apple only; extensible model definitions) + rac_diffusion_model_registry_init(); +#endif + internal_log(RAC_LOG_INFO, "RunAnywhere Commons initialized"); return RAC_SUCCESS; @@ -118,6 +126,11 @@ void rac_shutdown(void) { internal_log(RAC_LOG_INFO, "RunAnywhere Commons shutting down"); +#if !defined(RAC_PLATFORM_ANDROID) + // Cleanup diffusion model registry (iOS/Apple only) + rac_diffusion_model_registry_cleanup(); +#endif + // Clear state s_platform_adapter = nullptr; s_log_level = RAC_LOG_INFO; @@ -255,6 +268,14 @@ rac_result_t rac_get_model(const char* model_id, rac_model_info_t** out_model) { return rac_model_registry_get(registry, model_id, out_model); } +rac_result_t rac_get_model_by_path(const char* local_path, rac_model_info_t** out_model) { + rac_model_registry_handle_t registry = rac_get_model_registry(); + if (registry == nullptr) { + return RAC_ERROR_NOT_INITIALIZED; + } + return rac_model_registry_get_by_path(registry, local_path, out_model); +} + rac_bool_t rac_framework_is_platform_service(rac_inference_framework_t framework) { // Platform services are Swift-native implementations // that use service registry callbacks rather than C++ backends diff --git a/sdk/runanywhere-commons/src/features/diffusion/diffusion_component.cpp b/sdk/runanywhere-commons/src/features/diffusion/diffusion_component.cpp new file mode 100644 index 000000000..fe11166d3 --- /dev/null +++ b/sdk/runanywhere-commons/src/features/diffusion/diffusion_component.cpp @@ -0,0 +1,665 @@ +/** + * @file diffusion_component.cpp + * @brief Diffusion Capability Component Implementation + * + * Actor-based diffusion capability that owns model lifecycle and generation. + * Uses lifecycle manager for unified lifecycle + analytics handling. + * + * Supports text-to-image, image-to-image, and inpainting. + */ + +#include +#include +#include +#include +#include +#include + +#include "rac/core/capabilities/rac_lifecycle.h" +#include "rac/core/rac_logger.h" +#include "rac/core/rac_platform_adapter.h" +#include "rac/features/diffusion/rac_diffusion_component.h" +#include "rac/features/diffusion/rac_diffusion_service.h" +#include "rac/features/diffusion/rac_diffusion_tokenizer.h" + +// ============================================================================= +// INTERNAL STRUCTURES +// ============================================================================= + +/** + * Internal diffusion component state. + */ +struct rac_diffusion_component { + /** Lifecycle manager handle */ + rac_handle_t lifecycle; + + /** Current configuration */ + rac_diffusion_config_t config; + + /** Storage for optional string fields in config */ + std::string model_id_storage; + std::string tokenizer_custom_url_storage; + + /** Default generation options based on config */ + rac_diffusion_options_t default_options; + + /** Mutex for thread safety */ + std::mutex mtx; + + /** Cancellation flag (atomic for thread-safe access from cancel() while generate holds mutex) */ + std::atomic cancel_requested; + + rac_diffusion_component() : lifecycle(nullptr), cancel_requested(false) { + // Initialize with defaults + config = RAC_DIFFUSION_CONFIG_DEFAULT; + default_options = RAC_DIFFUSION_OPTIONS_DEFAULT; + } +}; + +// ============================================================================= +// HELPER FUNCTIONS +// ============================================================================= + +/** + * Merge user-provided options over component defaults. + * + * For numeric fields, zero/negative values mean "use default" (except guidance_scale + * where 0.0 is valid for CFG-free models like SDXS/SDXL Turbo - use negative to skip). + * Pointer fields are copied if non-null. Enums are always copied. + */ +static rac_diffusion_options_t merge_diffusion_options( + const rac_diffusion_options_t& defaults, const rac_diffusion_options_t* options) { + rac_diffusion_options_t effective = defaults; + + effective.prompt = options->prompt; + if (options->negative_prompt) { + effective.negative_prompt = options->negative_prompt; + } + if (options->width > 0) { + effective.width = options->width; + } + if (options->height > 0) { + effective.height = options->height; + } + if (options->steps > 0) { + effective.steps = options->steps; + } + // guidance_scale >= 0 allows 0.0 (valid for CFG-free models like SDXS, SDXL Turbo) + // Only skip override if user passes a negative sentinel (which is never valid) + if (options->guidance_scale >= 0.0f) { + effective.guidance_scale = options->guidance_scale; + } + if (options->seed != 0) { + effective.seed = options->seed; + } + effective.scheduler = options->scheduler; + effective.mode = options->mode; + + // Image-to-image / inpainting fields + effective.input_image_data = options->input_image_data; + effective.input_image_size = options->input_image_size; + effective.input_image_width = options->input_image_width; + effective.input_image_height = options->input_image_height; + effective.mask_data = options->mask_data; + effective.mask_size = options->mask_size; + effective.denoise_strength = options->denoise_strength; + + // Progress reporting fields + effective.report_intermediate_images = options->report_intermediate_images; + effective.progress_stride = options->progress_stride > 0 ? options->progress_stride : 1; + + return effective; +} + +/** + * Generate a unique ID for generation tracking. + */ +static std::string generate_unique_id() { + auto now = std::chrono::high_resolution_clock::now(); + auto epoch = now.time_since_epoch(); + auto ns = std::chrono::duration_cast(epoch).count(); + char buffer[64]; + snprintf(buffer, sizeof(buffer), "diffusion_%lld", static_cast(ns)); + return std::string(buffer); +} + +// ============================================================================= +// LIFECYCLE CALLBACKS +// ============================================================================= + +/** + * Service creation callback for lifecycle manager. + * Creates and initializes the diffusion service. + */ +static rac_result_t diffusion_create_service(const char* model_id, void* user_data, + rac_handle_t* out_service) { + auto* component = reinterpret_cast(user_data); + + RAC_LOG_INFO("Diffusion.Component", "Creating diffusion service for model: %s", + model_id ? model_id : ""); + + if (component && model_id) { + rac_result_t ensure_result = + rac_diffusion_tokenizer_ensure_files(model_id, &component->config.tokenizer); + if (ensure_result != RAC_SUCCESS) { + RAC_LOG_ERROR("Diffusion.Component", + "Failed to ensure tokenizer files for %s: %d", + model_id, ensure_result); + return ensure_result; + } + } + + // Create diffusion service + rac_result_t result = + rac_diffusion_create_with_config(model_id, component ? &component->config : nullptr, + out_service); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR("Diffusion.Component", "Failed to create diffusion service: %d", result); + return result; + } + + // Initialize with model path and config + result = rac_diffusion_initialize(*out_service, model_id, + component ? &component->config : nullptr); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR("Diffusion.Component", "Failed to initialize diffusion service: %d", result); + rac_diffusion_destroy(*out_service); + *out_service = nullptr; + return result; + } + + RAC_LOG_INFO("Diffusion.Component", "Diffusion service created successfully"); + return RAC_SUCCESS; +} + +/** + * Service destruction callback for lifecycle manager. + * Cleans up the diffusion service. + */ +static void diffusion_destroy_service(rac_handle_t service, void* user_data) { + (void)user_data; + + if (service) { + RAC_LOG_DEBUG("Diffusion.Component", "Destroying diffusion service"); + rac_diffusion_cleanup(service); + rac_diffusion_destroy(service); + } +} + +// ============================================================================= +// LIFECYCLE API +// ============================================================================= + +extern "C" rac_result_t rac_diffusion_component_create(rac_handle_t* out_handle) { + if (!out_handle) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + auto* component = new (std::nothrow) rac_diffusion_component(); + if (!component) { + return RAC_ERROR_OUT_OF_MEMORY; + } + + // Create lifecycle manager + rac_lifecycle_config_t lifecycle_config = {}; + lifecycle_config.resource_type = RAC_RESOURCE_TYPE_DIFFUSION_MODEL; + lifecycle_config.logger_category = "Diffusion.Lifecycle"; + lifecycle_config.user_data = component; + + rac_result_t result = rac_lifecycle_create(&lifecycle_config, diffusion_create_service, + diffusion_destroy_service, &component->lifecycle); + + if (result != RAC_SUCCESS) { + delete component; + return result; + } + + *out_handle = reinterpret_cast(component); + + RAC_LOG_INFO("Diffusion.Component", "Diffusion component created"); + + return RAC_SUCCESS; +} + +extern "C" rac_result_t rac_diffusion_component_configure(rac_handle_t handle, + const rac_diffusion_config_t* config) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!config) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + // Copy configuration (shallow) then normalize owned string fields + component->config = *config; + + if (config->model_id) { + component->model_id_storage = config->model_id; + component->config.model_id = component->model_id_storage.c_str(); + } else { + component->model_id_storage.clear(); + component->config.model_id = nullptr; + } + + if (config->tokenizer.custom_base_url) { + component->tokenizer_custom_url_storage = config->tokenizer.custom_base_url; + component->config.tokenizer.custom_base_url = + component->tokenizer_custom_url_storage.c_str(); + } else { + component->tokenizer_custom_url_storage.clear(); + component->config.tokenizer.custom_base_url = nullptr; + } + + // Update default options based on model variant + switch (config->model_variant) { + case RAC_DIFFUSION_MODEL_SDXL: + case RAC_DIFFUSION_MODEL_SDXL_TURBO: + component->default_options.width = 1024; + component->default_options.height = 1024; + break; + case RAC_DIFFUSION_MODEL_SD_2_1: + component->default_options.width = 768; + component->default_options.height = 768; + break; + case RAC_DIFFUSION_MODEL_SDXS: + case RAC_DIFFUSION_MODEL_LCM: + case RAC_DIFFUSION_MODEL_SD_1_5: + default: + component->default_options.width = 512; + component->default_options.height = 512; + break; + } + + // Ultra-fast models: SDXS (1 step), SDXL Turbo (4 steps), LCM (4 steps) + switch (config->model_variant) { + case RAC_DIFFUSION_MODEL_SDXS: + // SDXS: 1 step, no CFG + component->default_options.steps = 1; + component->default_options.guidance_scale = 0.0f; + component->default_options.scheduler = RAC_DIFFUSION_SCHEDULER_EULER; + break; + case RAC_DIFFUSION_MODEL_SDXL_TURBO: + // SDXL Turbo: 4 steps, no CFG + component->default_options.steps = 4; + component->default_options.guidance_scale = 0.0f; + break; + case RAC_DIFFUSION_MODEL_LCM: + // LCM: 4 steps, lower CFG + component->default_options.steps = 4; + component->default_options.guidance_scale = 1.5f; + component->default_options.scheduler = RAC_DIFFUSION_SCHEDULER_EULER; + break; + default: + // Standard models keep default values + break; + } + + RAC_LOG_INFO("Diffusion.Component", "Diffusion component configured"); + + return RAC_SUCCESS; +} + +extern "C" rac_bool_t rac_diffusion_component_is_loaded(rac_handle_t handle) { + if (!handle) + return RAC_FALSE; + + auto* component = reinterpret_cast(handle); + return rac_lifecycle_is_loaded(component->lifecycle); +} + +extern "C" const char* rac_diffusion_component_get_model_id(rac_handle_t handle) { + if (!handle) + return nullptr; + + auto* component = reinterpret_cast(handle); + return rac_lifecycle_get_model_id(component->lifecycle); +} + +extern "C" void rac_diffusion_component_destroy(rac_handle_t handle) { + if (!handle) + return; + + auto* component = reinterpret_cast(handle); + + // Destroy lifecycle manager (will cleanup service if loaded) + if (component->lifecycle) { + rac_lifecycle_destroy(component->lifecycle); + } + + RAC_LOG_INFO("Diffusion.Component", "Diffusion component destroyed"); + + delete component; +} + +// ============================================================================= +// MODEL LIFECYCLE +// ============================================================================= + +extern "C" rac_result_t rac_diffusion_component_load_model(rac_handle_t handle, + const char* model_path, + const char* model_id, + const char* model_name) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + // Delegate to lifecycle manager + rac_handle_t service = nullptr; + return rac_lifecycle_load(component->lifecycle, model_path, model_id, model_name, &service); +} + +extern "C" rac_result_t rac_diffusion_component_unload(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + return rac_lifecycle_unload(component->lifecycle); +} + +extern "C" rac_result_t rac_diffusion_component_cleanup(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + return rac_lifecycle_reset(component->lifecycle); +} + +// ============================================================================= +// GENERATION API +// ============================================================================= + +extern "C" rac_result_t rac_diffusion_component_generate(rac_handle_t handle, + const rac_diffusion_options_t* options, + rac_diffusion_result_t* out_result) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!options || !options->prompt) + return RAC_ERROR_INVALID_ARGUMENT; + if (!out_result) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + // Reset cancellation flag + component->cancel_requested = false; + + // Generate unique ID for this generation + std::string generation_id = generate_unique_id(); + + // Get model ID and name from lifecycle manager + const char* model_id = rac_lifecycle_get_model_id(component->lifecycle); + const char* model_name = rac_lifecycle_get_model_name(component->lifecycle); + + // Get service from lifecycle manager + rac_handle_t service = nullptr; + rac_result_t result = rac_lifecycle_require_service(component->lifecycle, &service); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR("Diffusion.Component", "No model loaded - cannot generate"); + return result; + } + + // Merge user options over component defaults + rac_diffusion_options_t effective_options = merge_diffusion_options( + component->default_options, options); + + RAC_LOG_INFO("Diffusion.Component", + "Starting generation: %dx%d, %d steps, guidance=%.1f, scheduler=%d", + effective_options.width, effective_options.height, effective_options.steps, + effective_options.guidance_scale, effective_options.scheduler); + + auto start_time = std::chrono::steady_clock::now(); + + // Perform generation + result = rac_diffusion_generate(service, &effective_options, out_result); + + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR("Diffusion.Component", "Generation failed: %d", result); + rac_lifecycle_track_error(component->lifecycle, result, "generate"); + return result; + } + + auto end_time = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time); + out_result->generation_time_ms = duration.count(); + + RAC_LOG_INFO("Diffusion.Component", "Generation completed in %lld ms, seed=%lld", + static_cast(out_result->generation_time_ms), + static_cast(out_result->seed_used)); + + return RAC_SUCCESS; +} + +/** + * Internal structure for progress callback context. + */ +struct diffusion_callback_context { + rac_diffusion_component* component; + rac_diffusion_progress_callback_fn progress_callback; + rac_diffusion_complete_callback_fn complete_callback; + rac_diffusion_error_callback_fn error_callback; + void* user_data; + + std::chrono::steady_clock::time_point start_time; + std::string generation_id; +}; + +/** + * Internal progress callback that wraps user callback and checks cancellation. + */ +static rac_bool_t diffusion_progress_wrapper(const rac_diffusion_progress_t* progress, + void* user_data) { + auto* ctx = reinterpret_cast(user_data); + + // Check cancellation + if (ctx->component->cancel_requested) { + RAC_LOG_INFO("Diffusion.Component", "Generation cancelled by user"); + return RAC_FALSE; // Signal to stop + } + + // Call user callback + if (ctx->progress_callback) { + return ctx->progress_callback(progress, ctx->user_data); + } + + return RAC_TRUE; // Continue by default +} + +extern "C" rac_result_t rac_diffusion_component_generate_with_callbacks( + rac_handle_t handle, const rac_diffusion_options_t* options, + rac_diffusion_progress_callback_fn progress_callback, + rac_diffusion_complete_callback_fn complete_callback, + rac_diffusion_error_callback_fn error_callback, void* user_data) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!options || !options->prompt) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + // Reset cancellation flag + component->cancel_requested = false; + + // Get service from lifecycle manager + rac_handle_t service = nullptr; + rac_result_t result = rac_lifecycle_require_service(component->lifecycle, &service); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR("Diffusion.Component", "No model loaded - cannot generate"); + if (error_callback) { + error_callback(result, "No model loaded", user_data); + } + return result; + } + + // Merge user options over component defaults + rac_diffusion_options_t effective_options = merge_diffusion_options( + component->default_options, options); + + RAC_LOG_INFO("Diffusion.Component", + "Starting generation with callbacks: %dx%d, %d steps, stride=%d", + effective_options.width, effective_options.height, effective_options.steps, + effective_options.progress_stride); + + // Setup callback context + diffusion_callback_context ctx; + ctx.component = component; + ctx.progress_callback = progress_callback; + ctx.complete_callback = complete_callback; + ctx.error_callback = error_callback; + ctx.user_data = user_data; + ctx.start_time = std::chrono::steady_clock::now(); + ctx.generation_id = generate_unique_id(); + + // Perform generation with progress + rac_diffusion_result_t gen_result = {}; + result = rac_diffusion_generate_with_progress(service, &effective_options, + diffusion_progress_wrapper, &ctx, &gen_result); + + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR("Diffusion.Component", "Generation failed: %d", result); + rac_lifecycle_track_error(component->lifecycle, result, "generateWithCallbacks"); + if (error_callback) { + error_callback(result, gen_result.error_message ? gen_result.error_message + : "Generation failed", + user_data); + } + rac_diffusion_result_free(&gen_result); + return result; + } + + auto end_time = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(end_time - ctx.start_time); + gen_result.generation_time_ms = duration.count(); + + RAC_LOG_INFO("Diffusion.Component", "Generation completed in %lld ms", + static_cast(gen_result.generation_time_ms)); + + // Call completion callback + if (complete_callback) { + complete_callback(&gen_result, user_data); + } + + // Free result (user should have copied what they need in callback) + rac_diffusion_result_free(&gen_result); + + return RAC_SUCCESS; +} + +extern "C" rac_result_t rac_diffusion_component_cancel(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + + auto* component = reinterpret_cast(handle); + + // Set cancellation flag (checked by progress callback) + component->cancel_requested = true; + + // Also try to cancel via service + rac_handle_t service = rac_lifecycle_get_service(component->lifecycle); + if (service) { + rac_diffusion_cancel(service); + } + + RAC_LOG_INFO("Diffusion.Component", "Generation cancellation requested"); + + return RAC_SUCCESS; +} + +// ============================================================================= +// CAPABILITY QUERY API +// ============================================================================= + +extern "C" uint32_t rac_diffusion_component_get_capabilities(rac_handle_t handle) { + if (!handle) + return 0; + + auto* component = reinterpret_cast(handle); + + rac_handle_t service = rac_lifecycle_get_service(component->lifecycle); + if (!service) { + // Return default capabilities based on config + uint32_t caps = RAC_DIFFUSION_CAP_TEXT_TO_IMAGE | RAC_DIFFUSION_CAP_INTERMEDIATE_IMAGES; + if (component->config.enable_safety_checker) { + caps |= RAC_DIFFUSION_CAP_SAFETY_CHECKER; + } + return caps; + } + + return rac_diffusion_get_capabilities(service); +} + +extern "C" rac_result_t rac_diffusion_component_get_info(rac_handle_t handle, + rac_diffusion_info_t* out_info) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!out_info) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + + rac_handle_t service = rac_lifecycle_get_service(component->lifecycle); + if (!service) { + // Return info based on config + out_info->is_ready = RAC_FALSE; + out_info->current_model = nullptr; + out_info->model_variant = component->config.model_variant; + out_info->supports_text_to_image = RAC_TRUE; + out_info->supports_image_to_image = RAC_TRUE; + out_info->supports_inpainting = RAC_TRUE; + out_info->safety_checker_enabled = component->config.enable_safety_checker; + + // Set max dimensions based on variant + switch (component->config.model_variant) { + case RAC_DIFFUSION_MODEL_SDXL: + case RAC_DIFFUSION_MODEL_SDXL_TURBO: + out_info->max_width = 1024; + out_info->max_height = 1024; + break; + case RAC_DIFFUSION_MODEL_SD_2_1: + out_info->max_width = 768; + out_info->max_height = 768; + break; + case RAC_DIFFUSION_MODEL_SDXS: + case RAC_DIFFUSION_MODEL_LCM: + case RAC_DIFFUSION_MODEL_SD_1_5: + default: + out_info->max_width = 512; + out_info->max_height = 512; + break; + } + return RAC_SUCCESS; + } + + return rac_diffusion_get_info(service, out_info); +} + +// ============================================================================= +// STATE QUERY API +// ============================================================================= + +extern "C" rac_lifecycle_state_t rac_diffusion_component_get_state(rac_handle_t handle) { + if (!handle) + return RAC_LIFECYCLE_STATE_IDLE; + + auto* component = reinterpret_cast(handle); + return rac_lifecycle_get_state(component->lifecycle); +} + +extern "C" rac_result_t rac_diffusion_component_get_metrics(rac_handle_t handle, + rac_lifecycle_metrics_t* out_metrics) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!out_metrics) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + return rac_lifecycle_get_metrics(component->lifecycle, out_metrics); +} diff --git a/sdk/runanywhere-commons/src/features/diffusion/diffusion_json.cpp b/sdk/runanywhere-commons/src/features/diffusion/diffusion_json.cpp new file mode 100644 index 000000000..f42403817 --- /dev/null +++ b/sdk/runanywhere-commons/src/features/diffusion/diffusion_json.cpp @@ -0,0 +1,435 @@ +/** + * @file diffusion_json.cpp + * @brief JSON convenience helpers for diffusion component + * + * Provides JSON parsing and serialization wrappers over the typed C API. + */ + +#include "rac/features/diffusion/rac_diffusion_component.h" + +#include +#include +#include +#include + +#include "rac/core/rac_types.h" +#include "rac/infrastructure/model_management/rac_model_types.h" + +namespace { + +static const char* skip_ws(const char* p) { + if (!p) return nullptr; + while (*p && std::isspace(static_cast(*p))) { + ++p; + } + return p; +} + +static const char* find_key(const char* json, const char* key) { + if (!json || !key) return nullptr; + std::string needle = "\""; + needle += key; + needle += "\""; + const char* pos = std::strstr(json, needle.c_str()); + if (!pos) return nullptr; + pos += needle.size(); + while (*pos && *pos != ':') { + ++pos; + } + if (*pos != ':') return nullptr; + pos = skip_ws(pos + 1); + return pos; +} + +static bool json_read_string(const char* json, const char* key, std::string* out) { + if (!out) return false; + const char* p = find_key(json, key); + if (!p || *p != '"') return false; + ++p; + std::string result; + while (*p) { + if (*p == '\\') { + ++p; + if (!*p) break; + switch (*p) { + case '"': result.push_back('"'); break; + case '\\': result.push_back('\\'); break; + case 'n': result.push_back('\n'); break; + case 'r': result.push_back('\r'); break; + case 't': result.push_back('\t'); break; + case 'b': result.push_back('\b'); break; + case 'f': result.push_back('\f'); break; + default: result.push_back(*p); break; + } + ++p; + continue; + } + if (*p == '"') { + *out = result; + return true; + } + result.push_back(*p++); + } + return false; +} + +static bool json_read_bool(const char* json, const char* key, bool* out) { + if (!out) return false; + const char* p = find_key(json, key); + if (!p) return false; + if (std::strncmp(p, "true", 4) == 0) { + *out = true; + return true; + } + if (std::strncmp(p, "false", 5) == 0) { + *out = false; + return true; + } + return false; +} + +static bool json_read_number(const char* json, const char* key, double* out) { + if (!out) return false; + const char* p = find_key(json, key); + if (!p) return false; + char* end = nullptr; + double val = std::strtod(p, &end); + if (end == p) return false; + *out = val; + return true; +} + +static bool json_read_int64(const char* json, const char* key, int64_t* out) { + if (!out) return false; + const char* p = find_key(json, key); + if (!p) return false; + char* end = nullptr; + long long val = std::strtoll(p, &end, 10); + if (end == p) return false; + *out = static_cast(val); + return true; +} + +static rac_diffusion_scheduler_t parse_scheduler(const char* json, + rac_diffusion_scheduler_t fallback) { + double num = 0.0; + if (json_read_number(json, "scheduler", &num)) { + return static_cast(static_cast(num)); + } + + std::string val; + if (!json_read_string(json, "scheduler", &val)) { + return fallback; + } + + if (val == "dpm++_2m_karras") return RAC_DIFFUSION_SCHEDULER_DPM_PP_2M_KARRAS; + if (val == "dpm++_2m") return RAC_DIFFUSION_SCHEDULER_DPM_PP_2M; + if (val == "dpm++_2m_sde") return RAC_DIFFUSION_SCHEDULER_DPM_PP_2M_SDE; + if (val == "ddim") return RAC_DIFFUSION_SCHEDULER_DDIM; + if (val == "euler") return RAC_DIFFUSION_SCHEDULER_EULER; + if (val == "euler_a") return RAC_DIFFUSION_SCHEDULER_EULER_ANCESTRAL; + if (val == "pndm") return RAC_DIFFUSION_SCHEDULER_PNDM; + if (val == "lms") return RAC_DIFFUSION_SCHEDULER_LMS; + return fallback; +} + +static rac_diffusion_mode_t parse_mode(const char* json, rac_diffusion_mode_t fallback) { + double num = 0.0; + if (json_read_number(json, "mode", &num)) { + return static_cast(static_cast(num)); + } + + std::string val; + if (!json_read_string(json, "mode", &val)) { + return fallback; + } + + if (val == "txt2img") return RAC_DIFFUSION_MODE_TEXT_TO_IMAGE; + if (val == "img2img") return RAC_DIFFUSION_MODE_IMAGE_TO_IMAGE; + if (val == "inpainting") return RAC_DIFFUSION_MODE_INPAINTING; + return fallback; +} + +static rac_diffusion_model_variant_t parse_variant(const char* json, + rac_diffusion_model_variant_t fallback) { + double num = 0.0; + if (json_read_number(json, "model_variant", &num)) { + return static_cast(static_cast(num)); + } + + std::string val; + if (!json_read_string(json, "model_variant", &val)) { + return fallback; + } + + if (val == "sd15") return RAC_DIFFUSION_MODEL_SD_1_5; + if (val == "sd21") return RAC_DIFFUSION_MODEL_SD_2_1; + if (val == "sdxl") return RAC_DIFFUSION_MODEL_SDXL; + if (val == "sdxl_turbo") return RAC_DIFFUSION_MODEL_SDXL_TURBO; + if (val == "sdxs") return RAC_DIFFUSION_MODEL_SDXS; + if (val == "lcm") return RAC_DIFFUSION_MODEL_LCM; + return fallback; +} + +static rac_diffusion_tokenizer_source_t parse_tokenizer_source( + const char* json, rac_diffusion_tokenizer_source_t fallback) { + double num = 0.0; + if (json_read_number(json, "tokenizer_source", &num)) { + return static_cast(static_cast(num)); + } + + std::string val; + if (!json_read_string(json, "tokenizer_source", &val)) { + return fallback; + } + + if (val == "sd15") return RAC_DIFFUSION_TOKENIZER_SD_1_5; + if (val == "sd2") return RAC_DIFFUSION_TOKENIZER_SD_2_X; + if (val == "sdxl") return RAC_DIFFUSION_TOKENIZER_SDXL; + if (val == "custom") return RAC_DIFFUSION_TOKENIZER_CUSTOM; + return fallback; +} + +static rac_inference_framework_t parse_preferred_framework( + const char* json, rac_inference_framework_t fallback) { + double num = 0.0; + if (json_read_number(json, "preferred_framework", &num)) { + return static_cast(static_cast(num)); + } + + std::string val; + if (!json_read_string(json, "preferred_framework", &val)) { + return fallback; + } + + for (auto& c : val) { + c = static_cast(std::tolower(static_cast(c))); + } + + if (val == "onnx") return RAC_FRAMEWORK_ONNX; + if (val == "llamacpp" || val == "llama_cpp") return RAC_FRAMEWORK_LLAMACPP; + if (val == "foundationmodels" || val == "foundation_models") + return RAC_FRAMEWORK_FOUNDATION_MODELS; + if (val == "systemtts" || val == "system_tts") return RAC_FRAMEWORK_SYSTEM_TTS; + if (val == "fluidaudio" || val == "fluid_audio") return RAC_FRAMEWORK_FLUID_AUDIO; + if (val == "builtin" || val == "built_in") return RAC_FRAMEWORK_BUILTIN; + if (val == "none") return RAC_FRAMEWORK_NONE; + if (val == "mlx") return RAC_FRAMEWORK_MLX; + if (val == "coreml" || val == "core_ml") return RAC_FRAMEWORK_COREML; + if (val == "unknown") return RAC_FRAMEWORK_UNKNOWN; + + return fallback; +} + +static std::string base64_encode(const uint8_t* data, size_t len) { + static const char table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(((len + 2) / 3) * 4); + + size_t i = 0; + while (i < len) { + size_t remaining = len - i; + uint32_t octet_a = data[i++]; + uint32_t octet_b = remaining > 1 ? data[i++] : 0; + uint32_t octet_c = remaining > 2 ? data[i++] : 0; + + uint32_t triple = (octet_a << 16) | (octet_b << 8) | octet_c; + + out.push_back(table[(triple >> 18) & 0x3F]); + out.push_back(table[(triple >> 12) & 0x3F]); + out.push_back(remaining > 1 ? table[(triple >> 6) & 0x3F] : '='); + out.push_back(remaining > 2 ? table[triple & 0x3F] : '='); + } + + return out; +} + +static std::string json_escape(const std::string& input) { + std::string out; + out.reserve(input.size() + 16); + for (char c : input) { + switch (c) { + case '"': out += "\\\""; break; + case '\\': out += "\\\\"; break; + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: out += c; break; + } + } + return out; +} + +} // namespace + +extern "C" { + +rac_result_t rac_diffusion_component_configure_json(rac_handle_t handle, const char* config_json) { + if (!handle || !config_json) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + rac_diffusion_config_t config = RAC_DIFFUSION_CONFIG_DEFAULT; + config.model_variant = parse_variant(config_json, config.model_variant); + + bool bool_val = false; + if (json_read_bool(config_json, "enable_safety_checker", &bool_val)) { + config.enable_safety_checker = bool_val ? RAC_TRUE : RAC_FALSE; + } + if (json_read_bool(config_json, "reduce_memory", &bool_val)) { + config.reduce_memory = bool_val ? RAC_TRUE : RAC_FALSE; + } + + config.preferred_framework = static_cast( + parse_preferred_framework(config_json, + static_cast(config.preferred_framework))); + + config.tokenizer.source = parse_tokenizer_source(config_json, config.tokenizer.source); + + std::string model_id; + if (json_read_string(config_json, "model_id", &model_id) && !model_id.empty()) { + config.model_id = model_id.c_str(); + } + + std::string custom_url; + if (json_read_string(config_json, "tokenizer_custom_url", &custom_url) && + !custom_url.empty()) { + config.tokenizer.custom_base_url = custom_url.c_str(); + } + + return rac_diffusion_component_configure(handle, &config); +} + +rac_result_t rac_diffusion_component_generate_json( + rac_handle_t handle, const char* options_json, const uint8_t* input_image_data, + size_t input_image_size, const uint8_t* mask_data, size_t mask_size, char** out_json) { + if (!handle || !options_json || !out_json) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + rac_diffusion_options_t options = RAC_DIFFUSION_OPTIONS_DEFAULT; + + std::string prompt; + if (!json_read_string(options_json, "prompt", &prompt) || prompt.empty()) { + return RAC_ERROR_INVALID_ARGUMENT; + } + options.prompt = prompt.c_str(); + + std::string negative_prompt; + if (json_read_string(options_json, "negative_prompt", &negative_prompt)) { + options.negative_prompt = negative_prompt.c_str(); + } + + double num = 0.0; + if (json_read_number(options_json, "width", &num)) { + options.width = static_cast(num); + } + if (json_read_number(options_json, "height", &num)) { + options.height = static_cast(num); + } + if (json_read_number(options_json, "steps", &num)) { + options.steps = static_cast(num); + } + if (json_read_number(options_json, "guidance_scale", &num)) { + options.guidance_scale = static_cast(num); + } + int64_t seed = 0; + if (json_read_int64(options_json, "seed", &seed)) { + options.seed = seed; + } + + options.scheduler = parse_scheduler(options_json, options.scheduler); + options.mode = parse_mode(options_json, options.mode); + + if (json_read_number(options_json, "denoise_strength", &num)) { + options.denoise_strength = static_cast(num); + } + + bool bool_val = false; + if (json_read_bool(options_json, "report_intermediate_images", &bool_val)) { + options.report_intermediate_images = bool_val ? RAC_TRUE : RAC_FALSE; + } + if (json_read_number(options_json, "progress_stride", &num)) { + options.progress_stride = static_cast(num); + } + + if (input_image_data && input_image_size > 0) { + options.input_image_data = input_image_data; + options.input_image_size = input_image_size; + } + if (json_read_number(options_json, "input_image_width", &num)) { + options.input_image_width = static_cast(num); + } + if (json_read_number(options_json, "input_image_height", &num)) { + options.input_image_height = static_cast(num); + } + if (mask_data && mask_size > 0) { + options.mask_data = mask_data; + options.mask_size = mask_size; + } + + rac_diffusion_result_t result = {}; + rac_result_t status = rac_diffusion_component_generate(handle, &options, &result); + if (status != RAC_SUCCESS) { + rac_diffusion_result_free(&result); + return status; + } + + std::string json = "{"; + if (result.image_data && result.image_size > 0) { + std::string b64 = base64_encode(result.image_data, result.image_size); + json += "\"image_data\":\"" + b64 + "\","; + json += "\"image_base64\":\"" + b64 + "\","; + } else { + json += "\"image_data\":\"\","; + json += "\"image_base64\":\"\","; + } + json += "\"width\":" + std::to_string(result.width) + ","; + json += "\"height\":" + std::to_string(result.height) + ","; + json += "\"seed_used\":" + std::to_string(static_cast(result.seed_used)) + ","; + json += "\"generation_time_ms\":" + + std::to_string(static_cast(result.generation_time_ms)) + ","; + json += "\"safety_flagged\":" + std::string(result.safety_flagged ? "true" : "false"); + json += "}"; + + *out_json = rac_strdup(json.c_str()); + rac_diffusion_result_free(&result); + + return (*out_json != nullptr) ? RAC_SUCCESS : RAC_ERROR_OUT_OF_MEMORY; +} + +rac_result_t rac_diffusion_component_get_info_json(rac_handle_t handle, char** out_json) { + if (!handle || !out_json) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + rac_diffusion_info_t info = {}; + rac_result_t status = rac_diffusion_component_get_info(handle, &info); + if (status != RAC_SUCCESS) { + return status; + } + + std::string json = "{"; + json += "\"is_ready\":" + std::string(info.is_ready ? "true" : "false") + ","; + json += "\"current_model\":\"" + + std::string(info.current_model ? json_escape(info.current_model) : "") + "\","; + json += "\"model_variant\":" + std::to_string(static_cast(info.model_variant)) + ","; + json += "\"supports_text_to_image\":" + + std::string(info.supports_text_to_image ? "true" : "false") + ","; + json += "\"supports_image_to_image\":" + + std::string(info.supports_image_to_image ? "true" : "false") + ","; + json += "\"supports_inpainting\":" + + std::string(info.supports_inpainting ? "true" : "false") + ","; + json += "\"safety_checker_enabled\":" + + std::string(info.safety_checker_enabled ? "true" : "false") + ","; + json += "\"max_width\":" + std::to_string(info.max_width) + ","; + json += "\"max_height\":" + std::to_string(info.max_height); + json += "}"; + + *out_json = rac_strdup(json.c_str()); + return (*out_json != nullptr) ? RAC_SUCCESS : RAC_ERROR_OUT_OF_MEMORY; +} + +} // extern "C" diff --git a/sdk/runanywhere-commons/src/features/diffusion/diffusion_model_registry.cpp b/sdk/runanywhere-commons/src/features/diffusion/diffusion_model_registry.cpp new file mode 100644 index 000000000..8d2607e8c --- /dev/null +++ b/sdk/runanywhere-commons/src/features/diffusion/diffusion_model_registry.cpp @@ -0,0 +1,499 @@ +/** + * @file diffusion_model_registry.cpp + * @brief Diffusion Model Registry Implementation + * + * Contains built-in model definitions and the extensible registry implementation. + * This is the shared C++ layer used by all SDKs. + */ + +#include "rac/features/diffusion/rac_diffusion_model_registry.h" +#include "rac/core/rac_logger.h" +#include "rac/core/rac_error.h" + +#include +#include +#include +#include +#include + +#if defined(__APPLE__) +#include +#endif + +namespace { + +const char* LOG_CAT = "DiffusionModelRegistry"; + +// ============================================================================= +// BUILT-IN MODEL DEFINITIONS (CoreML only for now - iOS/macOS) +// ============================================================================= + +// SD 1.5 CoreML (iOS/macOS - uses Apple Neural Engine) +static const rac_diffusion_model_def_t MODEL_SD15_COREML = { + .model_id = "stable-diffusion-v1-5-coreml", + .display_name = "Stable Diffusion 1.5", + .description = "Apple-optimized SD 1.5 for iOS/macOS. Uses Neural Engine for fast generation.", + .variant = RAC_DIFFUSION_MODEL_SD_1_5, + .backend = RAC_DIFFUSION_BACKEND_COREML, + .platforms = RAC_DIFFUSION_PLATFORM_IOS | RAC_DIFFUSION_PLATFORM_MACOS, + .hardware = RAC_DIFFUSION_HW_ANE | RAC_DIFFUSION_HW_GPU | RAC_DIFFUSION_HW_CPU, + .defaults = { + .width = 512, + .height = 512, + .steps = 20, + .guidance_scale = 7.5f, + .scheduler = RAC_DIFFUSION_SCHEDULER_DPM_PP_2M, + .requires_cfg = RAC_TRUE + }, + .download = { + .base_url = "https://huggingface.co/apple/coreml-stable-diffusion-v1-5-palettized", + .onnx_path = nullptr, + .coreml_path = "split_einsum_v2_compiled", + .size_bytes = 1200000000ULL, + .checksum = nullptr + }, + .tokenizer = { + .source = RAC_DIFFUSION_TOKENIZER_SD_1_5, + .custom_url = nullptr + }, + .is_recommended = RAC_TRUE, + .supports_img2img = RAC_TRUE, + .supports_inpainting = RAC_FALSE +}; + +// SD 2.1 CoreML (iOS/macOS) +static const rac_diffusion_model_def_t MODEL_SD21_COREML = { + .model_id = "stable-diffusion-v2-1-coreml", + .display_name = "Stable Diffusion 2.1", + .description = "Apple-optimized SD 2.1 for iOS/macOS. Higher resolution (768x768).", + .variant = RAC_DIFFUSION_MODEL_SD_2_1, + .backend = RAC_DIFFUSION_BACKEND_COREML, + .platforms = RAC_DIFFUSION_PLATFORM_IOS | RAC_DIFFUSION_PLATFORM_MACOS, + .hardware = RAC_DIFFUSION_HW_ANE | RAC_DIFFUSION_HW_GPU | RAC_DIFFUSION_HW_CPU, + .defaults = { + .width = 768, + .height = 768, + .steps = 20, + .guidance_scale = 7.5f, + .scheduler = RAC_DIFFUSION_SCHEDULER_DPM_PP_2M, + .requires_cfg = RAC_TRUE + }, + .download = { + .base_url = "https://huggingface.co/apple/coreml-stable-diffusion-2-1-base-palettized", + .onnx_path = nullptr, + .coreml_path = "split_einsum_v2_compiled", + .size_bytes = 1500000000ULL, + .checksum = nullptr + }, + .tokenizer = { + .source = RAC_DIFFUSION_TOKENIZER_SD_2_X, + .custom_url = nullptr + }, + .is_recommended = RAC_FALSE, + .supports_img2img = RAC_TRUE, + .supports_inpainting = RAC_FALSE +}; + +// All built-in models (CoreML only) +static const rac_diffusion_model_def_t* BUILTIN_MODELS[] = { + &MODEL_SD15_COREML, + &MODEL_SD21_COREML, +}; + +static const size_t BUILTIN_MODEL_COUNT = sizeof(BUILTIN_MODELS) / sizeof(BUILTIN_MODELS[0]); + +// ============================================================================= +// REGISTRY STATE +// ============================================================================= + +struct RegistryState { + std::mutex mutex; + std::vector strategies; + bool initialized = false; +}; + +RegistryState& get_state() { + static RegistryState state; + return state; +} + +// ============================================================================= +// PLATFORM DETECTION +// ============================================================================= + +uint32_t detect_current_platform() { +#if defined(__APPLE__) + #if TARGET_OS_IOS || TARGET_OS_SIMULATOR + return RAC_DIFFUSION_PLATFORM_IOS; + #else + return RAC_DIFFUSION_PLATFORM_MACOS; + #endif +#elif defined(__ANDROID__) + return RAC_DIFFUSION_PLATFORM_ANDROID; +#elif defined(_WIN32) || defined(_WIN64) + return RAC_DIFFUSION_PLATFORM_WINDOWS; +#elif defined(__linux__) + return RAC_DIFFUSION_PLATFORM_LINUX; +#else + return RAC_DIFFUSION_PLATFORM_LINUX; // Default to Linux +#endif +} + +// ============================================================================= +// BUILT-IN STRATEGY IMPLEMENTATION +// ============================================================================= + +static rac_bool_t builtin_can_handle(const char* model_id, void* /*user_data*/) { + if (!model_id) return RAC_FALSE; + + for (size_t i = 0; i < BUILTIN_MODEL_COUNT; i++) { + if (std::strcmp(model_id, BUILTIN_MODELS[i]->model_id) == 0) { + return RAC_TRUE; + } + } + return RAC_FALSE; +} + +static rac_result_t builtin_get_model_def(const char* model_id, + rac_diffusion_model_def_t* out_def, + void* /*user_data*/) { + if (!model_id || !out_def) return RAC_ERROR_INVALID_ARGUMENT; + + for (size_t i = 0; i < BUILTIN_MODEL_COUNT; i++) { + if (std::strcmp(model_id, BUILTIN_MODELS[i]->model_id) == 0) { + *out_def = *BUILTIN_MODELS[i]; + return RAC_SUCCESS; + } + } + return RAC_ERROR_NOT_FOUND; +} + +static rac_result_t builtin_list_models(rac_diffusion_model_def_t** out_models, + size_t* out_count, + void* /*user_data*/) { + if (!out_models || !out_count) return RAC_ERROR_INVALID_ARGUMENT; + + uint32_t current_platform = detect_current_platform(); + + // Count available models for this platform + size_t count = 0; + for (size_t i = 0; i < BUILTIN_MODEL_COUNT; i++) { + if (BUILTIN_MODELS[i]->platforms & current_platform) { + count++; + } + } + + // Handle empty result (no models for this platform) + if (count == 0) { + *out_models = nullptr; + *out_count = 0; + return RAC_SUCCESS; + } + + // Allocate output array + auto* models = static_cast( + std::malloc(count * sizeof(rac_diffusion_model_def_t))); + if (!models) { + return RAC_ERROR_OUT_OF_MEMORY; + } + + // Copy available models + size_t idx = 0; + for (size_t i = 0; i < BUILTIN_MODEL_COUNT; i++) { + if (BUILTIN_MODELS[i]->platforms & current_platform) { + models[idx++] = *BUILTIN_MODELS[i]; + } + } + + *out_models = models; + *out_count = count; + return RAC_SUCCESS; +} + +static rac_diffusion_backend_t builtin_select_backend(const rac_diffusion_model_def_t* model, + void* /*user_data*/) { + // Diffusion is Apple CoreML-only; no ONNX diffusion. + if (!model) { +#if defined(__APPLE__) + return RAC_DIFFUSION_BACKEND_COREML; +#else + return RAC_DIFFUSION_BACKEND_COREML; // Unused on non-Apple (diffusion not built) +#endif + } + if (model->backend != RAC_DIFFUSION_BACKEND_AUTO) { + return model->backend; + } +#if defined(__APPLE__) + if (model->download.coreml_path != nullptr) { + return RAC_DIFFUSION_BACKEND_COREML; + } +#endif + return RAC_DIFFUSION_BACKEND_COREML; +} + +static rac_diffusion_model_strategy_t BUILTIN_STRATEGY = { + .name = "BuiltIn", + .can_handle = builtin_can_handle, + .get_model_def = builtin_get_model_def, + .list_models = builtin_list_models, + .select_backend = builtin_select_backend, + .load_model = nullptr, // Use default loading + .user_data = nullptr +}; + +} // anonymous namespace + +// ============================================================================= +// PUBLIC API IMPLEMENTATION +// ============================================================================= + +extern "C" { + +void rac_diffusion_model_registry_init(void) { + auto& state = get_state(); + std::lock_guard lock(state.mutex); + + if (state.initialized) { + RAC_LOG_DEBUG(LOG_CAT, "Registry already initialized"); + return; + } + + // Register built-in strategy + state.strategies.push_back(BUILTIN_STRATEGY); + state.initialized = true; + + RAC_LOG_INFO(LOG_CAT, "Diffusion model registry initialized with %zu built-in models", + BUILTIN_MODEL_COUNT); + + // Log available models for current platform + uint32_t platform = detect_current_platform(); + const char* platform_name = "Unknown"; +#if defined(__APPLE__) + #if TARGET_OS_IOS || TARGET_OS_SIMULATOR + platform_name = "iOS"; + #else + platform_name = "macOS"; + #endif +#elif defined(__ANDROID__) + platform_name = "Android"; +#elif defined(_WIN32) + platform_name = "Windows"; +#else + platform_name = "Linux"; +#endif + + RAC_LOG_INFO(LOG_CAT, "Current platform: %s (0x%x)", platform_name, platform); +} + +void rac_diffusion_model_registry_cleanup(void) { + auto& state = get_state(); + std::lock_guard lock(state.mutex); + + state.strategies.clear(); + state.initialized = false; + + RAC_LOG_INFO(LOG_CAT, "Diffusion model registry cleaned up"); +} + +rac_result_t rac_diffusion_model_registry_register(const rac_diffusion_model_strategy_t* strategy) { + if (!strategy || !strategy->name) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + auto& state = get_state(); + std::lock_guard lock(state.mutex); + + // Check for duplicate + for (const auto& s : state.strategies) { + if (std::strcmp(s.name, strategy->name) == 0) { + RAC_LOG_WARNING(LOG_CAT, "Strategy '%s' already registered", strategy->name); + return RAC_ERROR_SERVICE_ALREADY_REGISTERED; + } + } + + state.strategies.push_back(*strategy); + RAC_LOG_INFO(LOG_CAT, "Registered diffusion model strategy: %s", strategy->name); + + return RAC_SUCCESS; +} + +rac_result_t rac_diffusion_model_registry_unregister(const char* name) { + if (!name) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + auto& state = get_state(); + std::lock_guard lock(state.mutex); + + for (auto it = state.strategies.begin(); it != state.strategies.end(); ++it) { + if (std::strcmp(it->name, name) == 0) { + state.strategies.erase(it); + RAC_LOG_INFO(LOG_CAT, "Unregistered diffusion model strategy: %s", name); + return RAC_SUCCESS; + } + } + + return RAC_ERROR_NOT_FOUND; +} + +rac_result_t rac_diffusion_model_registry_get(const char* model_id, + rac_diffusion_model_def_t* out_def) { + if (!model_id || !out_def) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + auto& state = get_state(); + std::lock_guard lock(state.mutex); + + // Try each strategy + for (const auto& strategy : state.strategies) { + if (strategy.can_handle && strategy.can_handle(model_id, strategy.user_data)) { + if (strategy.get_model_def) { + rac_result_t result = strategy.get_model_def(model_id, out_def, strategy.user_data); + if (result == RAC_SUCCESS) { + return RAC_SUCCESS; + } + } + } + } + + RAC_LOG_WARNING(LOG_CAT, "Model not found: %s", model_id); + return RAC_ERROR_NOT_FOUND; +} + +rac_result_t rac_diffusion_model_registry_list(rac_diffusion_model_def_t** out_models, + size_t* out_count) { + if (!out_models || !out_count) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + auto& state = get_state(); + std::lock_guard lock(state.mutex); + + // Collect all models from all strategies + std::vector all_models; + + for (const auto& strategy : state.strategies) { + if (strategy.list_models) { + rac_diffusion_model_def_t* models = nullptr; + size_t count = 0; + + if (strategy.list_models(&models, &count, strategy.user_data) == RAC_SUCCESS && models) { + for (size_t i = 0; i < count; i++) { + all_models.push_back(models[i]); + } + std::free(models); + } + } + } + + if (all_models.empty()) { + *out_models = nullptr; + *out_count = 0; + return RAC_SUCCESS; + } + + // Allocate output + auto* result = static_cast( + std::malloc(all_models.size() * sizeof(rac_diffusion_model_def_t))); + if (!result) { + return RAC_ERROR_OUT_OF_MEMORY; + } + + for (size_t i = 0; i < all_models.size(); i++) { + result[i] = all_models[i]; + } + + *out_models = result; + *out_count = all_models.size(); + return RAC_SUCCESS; +} + +rac_diffusion_backend_t rac_diffusion_model_registry_select_backend(const char* model_id) { + rac_diffusion_model_def_t model_def; + + if (rac_diffusion_model_registry_get(model_id, &model_def) != RAC_SUCCESS) { + RAC_LOG_DEBUG(LOG_CAT, "Model '%s' not found, using CoreML (Apple only)", + model_id ? model_id : "(null)"); + return RAC_DIFFUSION_BACKEND_COREML; + } + + auto& state = get_state(); + std::lock_guard lock(state.mutex); + + // Find strategy that handles this model and use its backend selection + for (const auto& strategy : state.strategies) { + if (strategy.can_handle && strategy.can_handle(model_id, strategy.user_data)) { + if (strategy.select_backend) { + rac_diffusion_backend_t backend = strategy.select_backend(&model_def, strategy.user_data); + RAC_LOG_DEBUG(LOG_CAT, "Selected backend %d for model '%s'", backend, model_id); + return backend; + } + } + } + + // Return model's preferred backend + return model_def.backend; +} + +rac_bool_t rac_diffusion_model_registry_is_available(const char* model_id) { + rac_diffusion_model_def_t model_def; + if (rac_diffusion_model_registry_get(model_id, &model_def) != RAC_SUCCESS) { + return RAC_FALSE; + } + + // Check platform availability + uint32_t current_platform = detect_current_platform(); + return (model_def.platforms & current_platform) ? RAC_TRUE : RAC_FALSE; +} + +rac_result_t rac_diffusion_model_registry_get_recommended(rac_diffusion_model_def_t* out_def) { + if (!out_def) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + rac_diffusion_model_def_t* models = nullptr; + size_t count = 0; + + rac_result_t result = rac_diffusion_model_registry_list(&models, &count); + if (result != RAC_SUCCESS || !models || count == 0) { + return RAC_ERROR_NOT_FOUND; + } + + // Find first recommended model + for (size_t i = 0; i < count; i++) { + if (models[i].is_recommended) { + *out_def = models[i]; + std::free(models); + return RAC_SUCCESS; + } + } + + // No recommended model found, return first available + *out_def = models[0]; + std::free(models); + return RAC_SUCCESS; +} + +uint32_t rac_diffusion_model_registry_get_current_platform(void) { + return detect_current_platform(); +} + +rac_bool_t rac_diffusion_model_requires_cfg(rac_diffusion_model_variant_t variant) { + // These models don't need classifier-free guidance + switch (variant) { + case RAC_DIFFUSION_MODEL_SDXS: + case RAC_DIFFUSION_MODEL_SDXL_TURBO: + return RAC_FALSE; + + // Standard models need CFG + case RAC_DIFFUSION_MODEL_SD_1_5: + case RAC_DIFFUSION_MODEL_SD_2_1: + case RAC_DIFFUSION_MODEL_SDXL: + case RAC_DIFFUSION_MODEL_LCM: + default: + return RAC_TRUE; + } +} + +} // extern "C" diff --git a/sdk/runanywhere-commons/src/features/diffusion/rac_diffusion_service.cpp b/sdk/runanywhere-commons/src/features/diffusion/rac_diffusion_service.cpp new file mode 100644 index 000000000..39fdd0c98 --- /dev/null +++ b/sdk/runanywhere-commons/src/features/diffusion/rac_diffusion_service.cpp @@ -0,0 +1,301 @@ +/** + * @file rac_diffusion_service.cpp + * @brief Diffusion Service - Generic API with VTable Dispatch + * + * Simple dispatch layer that routes calls through the service vtable. + * Each backend provides its own vtable when creating a service. + * No wrappers, no switch statements - just vtable calls. + */ + +#include "rac/features/diffusion/rac_diffusion_service.h" + +#include +#include +#include +#include + +#include "rac/core/rac_core.h" +#include "rac/core/rac_logger.h" +#include "rac/infrastructure/model_management/rac_model_registry.h" + +static const char* LOG_CAT = "Diffusion.Service"; +namespace fs = std::filesystem; + +// ============================================================================= +// INTERNAL HELPERS +// ============================================================================= + +/** + * Detect model format from path. Only Apple CoreML diffusion is supported. + * ONNX diffusion is not supported; we only look for CoreML. + */ +static rac_inference_framework_t detect_model_format_from_path(const char* path) { + if (!path) { + return RAC_FRAMEWORK_UNKNOWN; + } + fs::path dir_path(path); + if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) { + return RAC_FRAMEWORK_UNKNOWN; + } + // Only support CoreML (.mlmodelc, .mlpackage) for Apple Stable Diffusion + try { + for (const auto& entry : fs::directory_iterator(dir_path)) { + std::string ext = entry.path().extension().string(); + std::string name = entry.path().filename().string(); + if (ext == ".mlmodelc" || ext == ".mlpackage" || + name.find(".mlmodelc") != std::string::npos || + name.find(".mlpackage") != std::string::npos) { + RAC_LOG_DEBUG(LOG_CAT, "Found CoreML model at path: %s", path); + return RAC_FRAMEWORK_COREML; + } + } + } catch (const fs::filesystem_error&) { + // Ignore + } + return RAC_FRAMEWORK_UNKNOWN; +} + +static rac_result_t diffusion_create_service_internal(const char* model_id, + const rac_diffusion_config_t* config, + rac_handle_t* out_handle) { + if (!model_id || !out_handle) { + return RAC_ERROR_NULL_POINTER; + } + + *out_handle = nullptr; + + RAC_LOG_INFO(LOG_CAT, "Creating diffusion service for: %s", model_id); + + // Query model registry to get framework + rac_model_info_t* model_info = nullptr; + rac_result_t result = rac_get_model(model_id, &model_info); + + // If not found by model_id, try looking up by path (model_id might be a path) + if (result != RAC_SUCCESS) { + RAC_LOG_DEBUG(LOG_CAT, "Model not found by ID, trying path lookup: %s", model_id); + result = rac_get_model_by_path(model_id, &model_info); + } + + // Start with UNKNOWN framework - will be determined by file detection or registry + rac_inference_framework_t framework = RAC_FRAMEWORK_UNKNOWN; + const char* model_path = model_id; + + if (result == RAC_SUCCESS && model_info) { + framework = model_info->framework; + model_path = model_info->local_path ? model_info->local_path : model_id; + RAC_LOG_INFO(LOG_CAT, "Found model in registry: id=%s, framework=%d, local_path=%s", + model_info->id ? model_info->id : "NULL", + static_cast(framework), model_path ? model_path : "NULL"); + } else { + RAC_LOG_WARNING(LOG_CAT, + "Model NOT found in registry (result=%d), will detect from path", + result); + + // Try to detect framework from the model path/id + framework = detect_model_format_from_path(model_id); + + if (framework == RAC_FRAMEWORK_UNKNOWN) { + framework = RAC_FRAMEWORK_COREML; + RAC_LOG_INFO(LOG_CAT, "Could not detect format, defaulting to CoreML (Apple only)"); + } else if (framework == RAC_FRAMEWORK_ONNX) { + RAC_LOG_WARNING(LOG_CAT, "ONNX diffusion is not supported; only Apple CoreML. Ignoring ONNX."); + framework = RAC_FRAMEWORK_COREML; + } else { + RAC_LOG_INFO(LOG_CAT, "Detected framework=%d from path inspection", + static_cast(framework)); + } + } + + if (config && + static_cast(config->preferred_framework) != + RAC_FRAMEWORK_UNKNOWN) { + framework = static_cast(config->preferred_framework); + RAC_LOG_INFO(LOG_CAT, "Using preferred framework override: %d", + static_cast(framework)); + } + + // Build service request + rac_service_request_t request = {}; + request.identifier = model_id; + request.capability = RAC_CAPABILITY_DIFFUSION; + request.framework = framework; + request.model_path = model_path; + + RAC_LOG_INFO(LOG_CAT, "Diffusion service request: framework=%d (%s), model_path=%s", + static_cast(request.framework), + framework == RAC_FRAMEWORK_COREML ? "CoreML" : + framework == RAC_FRAMEWORK_ONNX ? "ONNX" : + framework == RAC_FRAMEWORK_UNKNOWN ? "Unknown" : "Other", + request.model_path ? request.model_path : "NULL"); + + // Service registry returns an rac_diffusion_service_t* with vtable already set + RAC_LOG_INFO(LOG_CAT, "Calling rac_service_create for DIFFUSION capability..."); + result = rac_service_create(RAC_CAPABILITY_DIFFUSION, &request, out_handle); + + if (model_info) { + rac_model_info_free(model_info); + } + + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR(LOG_CAT, "Failed to create service via registry: %d", result); + return result; + } + + RAC_LOG_INFO(LOG_CAT, "Diffusion service created"); + return RAC_SUCCESS; +} + +// ============================================================================= +// SERVICE CREATION - Routes through Service Registry +// ============================================================================= + +extern "C" { + +rac_result_t rac_diffusion_create(const char* model_id, rac_handle_t* out_handle) { + return diffusion_create_service_internal(model_id, nullptr, out_handle); +} + +rac_result_t rac_diffusion_create_with_config(const char* model_id, + const rac_diffusion_config_t* config, + rac_handle_t* out_handle) { + return diffusion_create_service_internal(model_id, config, out_handle); +} + +// ============================================================================= +// GENERIC API - Simple vtable dispatch +// ============================================================================= + +rac_result_t rac_diffusion_initialize(rac_handle_t handle, const char* model_path, + const rac_diffusion_config_t* config) { + if (!handle) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->initialize) { + return RAC_ERROR_NOT_SUPPORTED; + } + + return service->ops->initialize(service->impl, model_path, config); +} + +rac_result_t rac_diffusion_generate(rac_handle_t handle, const rac_diffusion_options_t* options, + rac_diffusion_result_t* out_result) { + if (!handle || !options || !out_result) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->generate) { + return RAC_ERROR_NOT_SUPPORTED; + } + + return service->ops->generate(service->impl, options, out_result); +} + +rac_result_t rac_diffusion_generate_with_progress(rac_handle_t handle, + const rac_diffusion_options_t* options, + rac_diffusion_progress_callback_fn progress_callback, + void* user_data, + rac_diffusion_result_t* out_result) { + if (!handle || !options || !out_result) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->generate_with_progress) { + // Fall back to non-progress version if available + if (service->ops && service->ops->generate) { + return service->ops->generate(service->impl, options, out_result); + } + return RAC_ERROR_NOT_SUPPORTED; + } + + return service->ops->generate_with_progress(service->impl, options, progress_callback, user_data, + out_result); +} + +rac_result_t rac_diffusion_get_info(rac_handle_t handle, rac_diffusion_info_t* out_info) { + if (!handle || !out_info) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->get_info) { + return RAC_ERROR_NOT_SUPPORTED; + } + + return service->ops->get_info(service->impl, out_info); +} + +uint32_t rac_diffusion_get_capabilities(rac_handle_t handle) { + if (!handle) + return 0; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->get_capabilities) { + // Return minimal capabilities + return RAC_DIFFUSION_CAP_TEXT_TO_IMAGE; + } + + return service->ops->get_capabilities(service->impl); +} + +rac_result_t rac_diffusion_cancel(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->cancel) { + return RAC_SUCCESS; // No-op if not supported + } + + return service->ops->cancel(service->impl); +} + +rac_result_t rac_diffusion_cleanup(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->cleanup) { + return RAC_SUCCESS; // No-op if not supported + } + + return service->ops->cleanup(service->impl); +} + +void rac_diffusion_destroy(rac_handle_t handle) { + if (!handle) + return; + + auto* service = static_cast(handle); + + // Call backend destroy + if (service->ops && service->ops->destroy) { + service->ops->destroy(service->impl); + } + + // Free model_id if allocated + if (service->model_id) { + free(const_cast(service->model_id)); + } + + // Free service struct + free(service); +} + +void rac_diffusion_result_free(rac_diffusion_result_t* result) { + if (!result) + return; + + if (result->image_data) { + free(result->image_data); + result->image_data = nullptr; + } + + if (result->error_message) { + free(result->error_message); + result->error_message = nullptr; + } + + result->image_size = 0; +} + +} // extern "C" diff --git a/sdk/runanywhere-commons/src/features/diffusion/rac_diffusion_tokenizer.cpp b/sdk/runanywhere-commons/src/features/diffusion/rac_diffusion_tokenizer.cpp new file mode 100644 index 000000000..88d7d75b3 --- /dev/null +++ b/sdk/runanywhere-commons/src/features/diffusion/rac_diffusion_tokenizer.cpp @@ -0,0 +1,298 @@ +/** + * @file rac_diffusion_tokenizer.cpp + * @brief RunAnywhere Commons - Diffusion Tokenizer Utilities Implementation + * + * Implementation of tokenizer file management utilities for diffusion models. + */ + +#include "rac/features/diffusion/rac_diffusion_tokenizer.h" + +#include +#include +#include +#include +#include +#include + +#include "rac/core/rac_error.h" +#include "rac/core/rac_logger.h" +#include "rac/core/rac_platform_adapter.h" + +// Platform-specific file existence check +#ifdef _WIN32 +#include +#define access _access +#define F_OK 0 +#else +#include +#endif + +// ============================================================================= +// CONSTANTS - Tokenizer base URLs for Apple Stable Diffusion models +// ============================================================================= +// Used when ensuring tokenizer files (vocab.json, merges.txt) for text encoding. +// Built-in Apple models: SD 1.5 CoreML and SD 2.1 CoreML use SD_1_5 and SD_2_X. + +// Apple SD 1.5 (same tokenizer as runwayml/stable-diffusion-v1-5) +static const char* TOKENIZER_URL_SD_1_5 = + "https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/tokenizer"; + +// Apple SD 2.1 (same tokenizer as stabilityai/stable-diffusion-2-1) +static const char* TOKENIZER_URL_SD_2_X = + "https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/tokenizer"; + +// SDXL (reserved for future use; built-in app models are SD 1.5 and SD 2.1 only) +static const char* TOKENIZER_URL_SDXL = + "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/tokenizer"; + +// ============================================================================= +// URL RESOLUTION +// ============================================================================= + +extern "C" const char* rac_diffusion_tokenizer_get_base_url(rac_diffusion_tokenizer_source_t source, + const char* custom_url) { + switch (source) { + case RAC_DIFFUSION_TOKENIZER_SD_1_5: + return TOKENIZER_URL_SD_1_5; + case RAC_DIFFUSION_TOKENIZER_SD_2_X: + return TOKENIZER_URL_SD_2_X; + case RAC_DIFFUSION_TOKENIZER_SDXL: + return TOKENIZER_URL_SDXL; + case RAC_DIFFUSION_TOKENIZER_CUSTOM: + return custom_url; + default: + return nullptr; + } +} + +extern "C" rac_result_t rac_diffusion_tokenizer_get_file_url(rac_diffusion_tokenizer_source_t source, + const char* custom_url, + const char* filename, char* out_url, + size_t out_url_size) { + if (!filename || !out_url || out_url_size == 0) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + const char* base_url = rac_diffusion_tokenizer_get_base_url(source, custom_url); + if (!base_url) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + // Construct full URL: base_url + "/" + filename + int written = snprintf(out_url, out_url_size, "%s/%s", base_url, filename); + if (written < 0 || static_cast(written) >= out_url_size) { + return RAC_ERROR_BUFFER_TOO_SMALL; + } + + return RAC_SUCCESS; +} + +// ============================================================================= +// FILE MANAGEMENT +// ============================================================================= + +extern "C" rac_result_t rac_diffusion_tokenizer_check_files(const char* model_dir, + rac_bool_t* out_has_vocab, + rac_bool_t* out_has_merges) { + if (!model_dir) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + std::string vocab_path = std::string(model_dir) + "/" + RAC_DIFFUSION_TOKENIZER_VOCAB_FILE; + std::string merges_path = std::string(model_dir) + "/" + RAC_DIFFUSION_TOKENIZER_MERGES_FILE; + + if (out_has_vocab) { + *out_has_vocab = (access(vocab_path.c_str(), F_OK) == 0) ? RAC_TRUE : RAC_FALSE; + } + + if (out_has_merges) { + *out_has_merges = (access(merges_path.c_str(), F_OK) == 0) ? RAC_TRUE : RAC_FALSE; + } + + return RAC_SUCCESS; +} + +extern "C" rac_result_t +rac_diffusion_tokenizer_ensure_files(const char* model_dir, + const rac_diffusion_tokenizer_config_t* config) { + if (!model_dir || !config) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + auto resolve_tokenizer_dir = [](const char* base_dir) -> std::string { + std::string root_dir = base_dir ? base_dir : ""; + if (root_dir.empty()) { + return root_dir; + } + + std::string root_vocab = root_dir + "/" + RAC_DIFFUSION_TOKENIZER_VOCAB_FILE; + std::string root_merges = root_dir + "/" + RAC_DIFFUSION_TOKENIZER_MERGES_FILE; + std::string tokenizer_dir = root_dir + "/tokenizer"; + std::string tokenizer_vocab = tokenizer_dir + "/" + RAC_DIFFUSION_TOKENIZER_VOCAB_FILE; + std::string tokenizer_merges = tokenizer_dir + "/" + RAC_DIFFUSION_TOKENIZER_MERGES_FILE; + + bool root_has_files = + (access(root_vocab.c_str(), F_OK) == 0) || (access(root_merges.c_str(), F_OK) == 0); + bool tokenizer_has_files = + (access(tokenizer_vocab.c_str(), F_OK) == 0) || + (access(tokenizer_merges.c_str(), F_OK) == 0); + bool tokenizer_exists = access(tokenizer_dir.c_str(), F_OK) == 0; + + if (tokenizer_has_files || (!root_has_files && tokenizer_exists)) { + return tokenizer_dir; + } + + return root_dir; + }; + + std::string tokenizer_dir = resolve_tokenizer_dir(model_dir); + if (tokenizer_dir.empty()) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + rac_bool_t has_vocab = RAC_FALSE; + rac_bool_t has_merges = RAC_FALSE; + + rac_result_t result = + rac_diffusion_tokenizer_check_files(tokenizer_dir.c_str(), &has_vocab, &has_merges); + if (result != RAC_SUCCESS) { + return result; + } + + // If both files exist, we're done + if (has_vocab == RAC_TRUE && has_merges == RAC_TRUE) { + RAC_LOG_DEBUG("Diffusion.Tokenizer", "Tokenizer files already exist in %s", + tokenizer_dir.c_str()); + return RAC_SUCCESS; + } + + // If auto_download is disabled and files are missing, return error + if (config->auto_download != RAC_TRUE) { + if (has_vocab != RAC_TRUE) { + RAC_LOG_ERROR("Diffusion.Tokenizer", "Missing %s in %s (auto_download disabled)", + RAC_DIFFUSION_TOKENIZER_VOCAB_FILE, tokenizer_dir.c_str()); + } + if (has_merges != RAC_TRUE) { + RAC_LOG_ERROR("Diffusion.Tokenizer", "Missing %s in %s (auto_download disabled)", + RAC_DIFFUSION_TOKENIZER_MERGES_FILE, tokenizer_dir.c_str()); + } + return RAC_ERROR_FILE_NOT_FOUND; + } + + // Download missing files + const char* custom_url = config->custom_base_url; + + if (has_vocab != RAC_TRUE) { + std::string vocab_path = tokenizer_dir + "/" + RAC_DIFFUSION_TOKENIZER_VOCAB_FILE; + result = rac_diffusion_tokenizer_download_file(config->source, custom_url, + RAC_DIFFUSION_TOKENIZER_VOCAB_FILE, + vocab_path.c_str()); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR("Diffusion.Tokenizer", "Failed to download %s: %d", + RAC_DIFFUSION_TOKENIZER_VOCAB_FILE, result); + return result; + } + } + + if (has_merges != RAC_TRUE) { + std::string merges_path = tokenizer_dir + "/" + RAC_DIFFUSION_TOKENIZER_MERGES_FILE; + result = rac_diffusion_tokenizer_download_file(config->source, custom_url, + RAC_DIFFUSION_TOKENIZER_MERGES_FILE, + merges_path.c_str()); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR("Diffusion.Tokenizer", "Failed to download %s: %d", + RAC_DIFFUSION_TOKENIZER_MERGES_FILE, result); + return result; + } + } + + RAC_LOG_INFO("Diffusion.Tokenizer", "Tokenizer files ensured in %s", tokenizer_dir.c_str()); + return RAC_SUCCESS; +} + +extern "C" rac_result_t rac_diffusion_tokenizer_download_file(rac_diffusion_tokenizer_source_t source, + const char* custom_url, + const char* filename, + const char* output_path) { + if (!filename || !output_path) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + // Get full URL + char url[1024]; + rac_result_t result = + rac_diffusion_tokenizer_get_file_url(source, custom_url, filename, url, sizeof(url)); + if (result != RAC_SUCCESS) { + return result; + } + + RAC_LOG_INFO("Diffusion.Tokenizer", "Downloading %s from %s", filename, url); + + struct download_context { + std::mutex mutex; + std::condition_variable cv; + bool completed = false; + rac_result_t result = RAC_ERROR_DOWNLOAD_FAILED; + }; + + auto progress_cb = [](int64_t /*downloaded*/, int64_t /*total*/, void* /*user_data*/) {}; + + auto complete_cb = [](rac_result_t result, const char* /*downloaded_path*/, + void* user_data) { + auto* ctx = static_cast(user_data); + if (!ctx) { + return; + } + { + std::lock_guard lock(ctx->mutex); + ctx->result = result; + ctx->completed = true; + } + ctx->cv.notify_one(); + }; + + download_context ctx; + char* task_id = nullptr; + + rac_result_t start_result = rac_http_download(url, output_path, progress_cb, complete_cb, + &ctx, &task_id); + if (start_result != RAC_SUCCESS) { + if (task_id) { + rac_free(task_id); + } + RAC_LOG_ERROR("Diffusion.Tokenizer", "HTTP download start failed: %d", start_result); + return start_result; + } + + std::unique_lock lock(ctx.mutex); + ctx.cv.wait(lock, [&ctx]() { return ctx.completed; }); + + if (task_id) { + rac_free(task_id); + } + + if (ctx.result != RAC_SUCCESS) { + RAC_LOG_ERROR("Diffusion.Tokenizer", "HTTP download failed: %d", ctx.result); + } + + return ctx.result; +} + +// ============================================================================= +// DEFAULT TOKENIZER SOURCE +// ============================================================================= + +extern "C" rac_diffusion_tokenizer_source_t +rac_diffusion_tokenizer_default_for_variant(rac_diffusion_model_variant_t model_variant) { + switch (model_variant) { + case RAC_DIFFUSION_MODEL_SD_1_5: + return RAC_DIFFUSION_TOKENIZER_SD_1_5; + case RAC_DIFFUSION_MODEL_SD_2_1: + return RAC_DIFFUSION_TOKENIZER_SD_2_X; + case RAC_DIFFUSION_MODEL_SDXL: + case RAC_DIFFUSION_MODEL_SDXL_TURBO: + return RAC_DIFFUSION_TOKENIZER_SDXL; + default: + return RAC_DIFFUSION_TOKENIZER_SD_1_5; + } +} diff --git a/sdk/runanywhere-commons/src/features/llm/rac_llm_service.cpp b/sdk/runanywhere-commons/src/features/llm/rac_llm_service.cpp index 14fe472b3..65f914c49 100644 --- a/sdk/runanywhere-commons/src/features/llm/rac_llm_service.cpp +++ b/sdk/runanywhere-commons/src/features/llm/rac_llm_service.cpp @@ -38,13 +38,27 @@ rac_result_t rac_llm_create(const char* model_id, rac_handle_t* out_handle) { rac_model_info_t* model_info = nullptr; rac_result_t result = rac_get_model(model_id, &model_info); + // If not found by model_id, try looking up by path (model_id might be a path) + if (result != RAC_SUCCESS) { + RAC_LOG_DEBUG(LOG_CAT, "Model not found by ID, trying path lookup: %s", model_id); + result = rac_get_model_by_path(model_id, &model_info); + } + rac_inference_framework_t framework = RAC_FRAMEWORK_LLAMACPP; const char* model_path = model_id; if (result == RAC_SUCCESS && model_info) { framework = model_info->framework; - model_path = model_info->local_path ? model_info->local_path : model_id; - RAC_LOG_INFO(LOG_CAT, "Found model in registry: framework=%d, local_path=%s", + const char* reg_path = model_info->local_path ? model_info->local_path : model_id; + // Registry local_path is often the model directory; LlamaCPP needs the path to the .gguf file. + // If model_id is already a path to a .gguf file (e.g. from path lookup), use it for loading. + if (strstr(model_id, ".gguf") != nullptr) { + model_path = model_id; + } else { + model_path = reg_path; + } + RAC_LOG_INFO(LOG_CAT, "Found model in registry: id=%s, framework=%d, local_path=%s", + model_info->id ? model_info->id : "NULL", static_cast(framework), model_path ? model_path : "NULL"); } else { RAC_LOG_WARNING(LOG_CAT, diff --git a/sdk/runanywhere-commons/src/features/llm/tool_calling.cpp b/sdk/runanywhere-commons/src/features/llm/tool_calling.cpp new file mode 100644 index 000000000..4212d4abb --- /dev/null +++ b/sdk/runanywhere-commons/src/features/llm/tool_calling.cpp @@ -0,0 +1,1667 @@ +/** + * @file tool_calling.cpp + * @brief RunAnywhere Commons - Tool Calling Implementation + * + * *** SINGLE SOURCE OF TRUTH FOR ALL TOOL CALLING LOGIC *** + * + * This implementation consolidates all tool calling logic from: + * - Swift: ToolCallParser.swift + * - React Native: ToolCallingBridge.cpp + * + * NO FALLBACKS - All SDKs must use these functions exclusively. + * + * Supported formats: + * - DEFAULT: {"tool":"name","arguments":{}} (Most general models) + * - LFM2: <|tool_call_start|>[func(arg="val")]<|tool_call_end|> (Liquid AI models) + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rac/core/rac_logger.h" +#include "rac/features/llm/rac_tool_calling.h" + +// ============================================================================= +// CONSTANTS - Format-specific tags +// ============================================================================= + +// Format: DEFAULT (JSON) +static const char* TAG_DEFAULT_START = ""; +static const char* TAG_DEFAULT_END = ""; + +// Format: LFM2 (Liquid AI) +static const char* TAG_LFM2_START = "<|tool_call_start|>"; +static const char* TAG_LFM2_END = "<|tool_call_end|>"; + +// Format names for logging/display +static const char* FORMAT_NAMES[] = { + "Default", // RAC_TOOL_FORMAT_DEFAULT + "LFM2 (Liquid)", // RAC_TOOL_FORMAT_LFM2 +}; + +// Legacy alias for backward compatibility +static const char* TOOL_CALL_START_TAG = TAG_DEFAULT_START; +static const char* TOOL_CALL_END_TAG = TAG_DEFAULT_END; + +// Standard keys for tool name (case-insensitive matching) +static const char* TOOL_NAME_KEYS[] = {"tool", "name", "function", "func", "method", + "action", "command", nullptr}; + +// Standard keys for arguments (case-insensitive matching) +static const char* ARGUMENT_KEYS[] = {"arguments", "args", "params", "parameters", "input", nullptr}; + +// ============================================================================= +// FORMAT DETECTION AND NAMING +// ============================================================================= + +extern "C" const char* rac_tool_call_format_name(rac_tool_call_format_t format) { + if (format >= 0 && format < RAC_TOOL_FORMAT_COUNT) { + return FORMAT_NAMES[format]; + } + return "Unknown"; +} + +extern "C" rac_tool_call_format_t rac_tool_call_format_from_name(const char* name) { + if (!name) { + return RAC_TOOL_FORMAT_DEFAULT; + } + + // Case-insensitive comparison + std::string name_lower(name); + for (char& c : name_lower) { + c = static_cast(tolower(c)); + } + + if (name_lower == "default") { + return RAC_TOOL_FORMAT_DEFAULT; + } else if (name_lower == "lfm2" || name_lower == "lfm" || name_lower == "liquid") { + return RAC_TOOL_FORMAT_LFM2; + } + + // Unknown format - default to DEFAULT + RAC_LOG_WARNING("ToolCalling", "Unknown tool call format name: '%s', using default", name); + return RAC_TOOL_FORMAT_DEFAULT; +} + +extern "C" rac_tool_call_format_t rac_tool_call_detect_format(const char* llm_output) { + if (!llm_output) { + return RAC_TOOL_FORMAT_DEFAULT; + } + + // Check for each format's start tag + // Order matters - check more specific formats first + + // Check LFM2 format: <|tool_call_start|> + if (strstr(llm_output, TAG_LFM2_START) != nullptr) { + return RAC_TOOL_FORMAT_LFM2; + } + + // Check Default format: + if (strstr(llm_output, TAG_DEFAULT_START) != nullptr) { + return RAC_TOOL_FORMAT_DEFAULT; + } + + // No recognizable format detected - return DEFAULT + return RAC_TOOL_FORMAT_DEFAULT; +} + +// ============================================================================= +// HELPER FUNCTIONS - String Operations +// ============================================================================= + +/** + * @brief Case-insensitive string comparison + */ +static bool str_equals_ignore_case(const char* a, const char* b) { + if (!a || !b) + return false; + while (*a && *b) { + char ca = (*a >= 'A' && *a <= 'Z') ? (*a + 32) : *a; + char cb = (*b >= 'A' && *b <= 'Z') ? (*b + 32) : *b; + if (ca != cb) + return false; + a++; + b++; + } + return *a == *b; +} + +/** + * @brief Trim whitespace from beginning and end + */ +static void trim_whitespace(const char* str, size_t len, size_t* out_start, size_t* out_end) { + size_t start = 0; + size_t end = len; + + while (start < len && (str[start] == ' ' || str[start] == '\t' || str[start] == '\n' || + str[start] == '\r')) { + start++; + } + + while (end > start && (str[end - 1] == ' ' || str[end - 1] == '\t' || str[end - 1] == '\n' || + str[end - 1] == '\r')) { + end--; + } + + *out_start = start; + *out_end = end; +} + +/** + * @brief Find substring in string + */ +static const char* find_str(const char* haystack, const char* needle) { + return strstr(haystack, needle); +} + +/** + * @brief Check if character is a key character (alphanumeric or underscore) + */ +static bool is_key_char(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; +} + +// ============================================================================= +// JSON PARSING HELPERS (Manual - No External Library) +// ============================================================================= + +/** + * @brief Find matching closing brace for JSON object + * + * Tracks string boundaries to ignore braces inside strings. + * + * @param str String to search + * @param start_pos Position of opening brace '{' + * @param out_end Output: Position of matching closing brace '}' + * @return true if found, false otherwise + */ +static bool find_matching_brace(const char* str, size_t start_pos, size_t* out_end) { + if (!str || str[start_pos] != '{') { + return false; + } + + size_t len = strlen(str); + int depth = 0; + bool in_string = false; + bool escaped = false; + + for (size_t i = start_pos; i < len; i++) { + char ch = str[i]; + + if (escaped) { + escaped = false; + continue; + } + + if (ch == '\\') { + escaped = true; + continue; + } + + if (ch == '"') { + in_string = !in_string; + continue; + } + + if (!in_string) { + if (ch == '{') { + depth++; + } else if (ch == '}') { + depth--; + if (depth == 0) { + *out_end = i; + return true; + } + } + } + } + + return false; +} + +/** + * @brief Skip whitespace in string + */ +static size_t skip_whitespace(const char* str, size_t pos, size_t len) { + while (pos < len && (str[pos] == ' ' || str[pos] == '\t' || str[pos] == '\n' || str[pos] == '\r')) { + pos++; + } + return pos; +} + +/** + * @brief Extract a JSON string value starting at the given position (must be after opening quote) + * + * @param str Input string + * @param pos Position after opening quote + * @param len Length of input string + * @param out_value Output: Allocated string value (caller must free) + * @param out_end_pos Output: Position after closing quote + * @return true if successful + */ +static bool extract_json_string(const char* str, size_t pos, size_t len, char** out_value, + size_t* out_end_pos) { + std::string result; + bool escaped = false; + + for (size_t i = pos; i < len; i++) { + char ch = str[i]; + + if (escaped) { + switch (ch) { + case 'n': + result += '\n'; + break; + case 'r': + result += '\r'; + break; + case 't': + result += '\t'; + break; + case '\\': + result += '\\'; + break; + case '"': + result += '"'; + break; + default: + result += ch; + break; + } + escaped = false; + continue; + } + + if (ch == '\\') { + escaped = true; + continue; + } + + if (ch == '"') { + // End of string + *out_value = static_cast(malloc(result.size() + 1)); + if (*out_value) { + memcpy(*out_value, result.c_str(), result.size() + 1); + } + *out_end_pos = i + 1; + return true; + } + + result += ch; + } + + return false; +} + +/** + * @brief Extract a JSON object as a raw string (including braces) + */ +static bool extract_json_object_raw(const char* str, size_t pos, size_t len, char** out_value, + size_t* out_end_pos) { + if (str[pos] != '{') { + return false; + } + + size_t end_brace; + if (!find_matching_brace(str, pos, &end_brace)) { + return false; + } + + size_t obj_len = end_brace - pos + 1; + *out_value = static_cast(malloc(obj_len + 1)); + if (!*out_value) { + return false; + } + + memcpy(*out_value, str + pos, obj_len); + (*out_value)[obj_len] = '\0'; + *out_end_pos = end_brace + 1; + return true; +} + +/** + * @brief Simple JSON key-value extractor + * + * Extracts a string or object value for a given key from a JSON object string. + * + * @param json_obj JSON object string (must include braces) + * @param key Key to find (case-insensitive) + * @param out_value Output: Allocated value string (caller must free) + * @param out_is_object Output: Whether the value is an object (vs string) + * @return true if found + */ +static bool extract_json_value(const char* json_obj, const char* key, char** out_value, + bool* out_is_object) { + if (!json_obj || !key || !out_value) { + return false; + } + + *out_value = nullptr; + *out_is_object = false; + + size_t len = strlen(json_obj); + bool in_string = false; + bool escaped = false; + + for (size_t i = 0; i < len; i++) { + char ch = json_obj[i]; + + if (escaped) { + escaped = false; + continue; + } + + if (ch == '\\') { + escaped = true; + continue; + } + + if (ch == '"') { + if (!in_string) { + // Start of a key string - extract it + size_t key_start = i + 1; + char* found_key = nullptr; + size_t key_end; + + if (extract_json_string(json_obj, key_start, len, &found_key, &key_end)) { + // Check if this key matches + bool matches = str_equals_ignore_case(found_key, key); + free(found_key); + + if (matches) { + // Skip to colon + size_t pos = skip_whitespace(json_obj, key_end, len); + if (pos < len && json_obj[pos] == ':') { + pos++; + pos = skip_whitespace(json_obj, pos, len); + + // Extract value + if (pos < len) { + if (json_obj[pos] == '"') { + // String value + size_t value_end; + if (extract_json_string(json_obj, pos + 1, len, out_value, + &value_end)) { + *out_is_object = false; + return true; + } + } else if (json_obj[pos] == '{') { + // Object value + size_t value_end; + if (extract_json_object_raw(json_obj, pos, len, out_value, + &value_end)) { + *out_is_object = true; + return true; + } + } + } + } + } + + // Move to end of key for continued scanning + i = key_end - 1; + } + } + in_string = !in_string; + } + } + + return false; +} + +/** + * @brief Get all keys from a JSON object (for fallback strategy) + */ +static std::vector get_json_keys(const char* json_obj) { + std::vector keys; + if (!json_obj) { + return keys; + } + + size_t len = strlen(json_obj); + bool in_string = false; + bool escaped = false; + int depth = 0; + + for (size_t i = 0; i < len; i++) { + char ch = json_obj[i]; + + if (escaped) { + escaped = false; + continue; + } + + if (ch == '\\') { + escaped = true; + continue; + } + + if (ch == '"') { + if (!in_string && depth == 1) { + // Start of a key at depth 1 (top-level) + size_t key_start = i + 1; + char* found_key = nullptr; + size_t key_end; + + if (extract_json_string(json_obj, key_start, len, &found_key, &key_end)) { + // Verify it's followed by colon + size_t pos = skip_whitespace(json_obj, key_end, len); + if (pos < len && json_obj[pos] == ':') { + keys.push_back(found_key); + } + free(found_key); + i = key_end - 1; + continue; + } + } + in_string = !in_string; + continue; + } + + if (!in_string) { + if (ch == '{') { + depth++; + } else if (ch == '}') { + depth--; + } + } + } + + return keys; +} + +/** + * @brief Check if key is a standard/reserved key + */ +static bool is_standard_key(const char* key) { + // Standard tool keys + for (int i = 0; TOOL_NAME_KEYS[i] != nullptr; i++) { + if (str_equals_ignore_case(key, TOOL_NAME_KEYS[i])) { + return true; + } + } + // Standard argument keys + for (int i = 0; ARGUMENT_KEYS[i] != nullptr; i++) { + if (str_equals_ignore_case(key, ARGUMENT_KEYS[i])) { + return true; + } + } + return false; +} + +/** + * @brief Escape a string for JSON output (manual implementation) + * + * Escapes special characters (quotes, backslashes, control characters) + * to produce valid JSON string content. + */ +static std::string escape_json_string(const char* str) { + if (!str) { + return ""; + } + + std::string result; + result.reserve(strlen(str) + 16); + + for (size_t i = 0; str[i]; i++) { + char c = str[i]; + switch (c) { + case '"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + default: + result += c; + break; + } + } + + return result; +} + +// ============================================================================= +// JSON NORMALIZATION +// ============================================================================= + +extern "C" rac_result_t rac_tool_call_normalize_json(const char* json_str, char** out_normalized) { + if (!json_str || !out_normalized) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + size_t len = strlen(json_str); + std::string result; + result.reserve(len + 32); + + bool in_string = false; + + for (size_t i = 0; i < len; i++) { + char c = json_str[i]; + + // Track if we're inside a string + if (c == '"' && (i == 0 || json_str[i - 1] != '\\')) { + in_string = !in_string; + result += c; + continue; + } + + if (in_string) { + result += c; + continue; + } + + // Look for unquoted keys: { key: or , key: + if ((c == '{' || c == ',') && i + 1 < len) { + result += c; + + // Skip whitespace + size_t j = i + 1; + while (j < len && (json_str[j] == ' ' || json_str[j] == '\t' || json_str[j] == '\n')) { + result += json_str[j]; + j++; + } + + // Check if next is an unquoted identifier followed by colon + if (j < len && json_str[j] != '"' && json_str[j] != '{' && json_str[j] != '[') { + size_t key_start = j; + while (j < len && is_key_char(json_str[j])) { + j++; + } + + if (j < len && j > key_start) { + size_t key_end = j; + // Skip whitespace to find colon + while (j < len && (json_str[j] == ' ' || json_str[j] == '\t')) { + j++; + } + if (j < len && json_str[j] == ':') { + // This is an unquoted key - add quotes + result += '"'; + result.append(json_str + key_start, key_end - key_start); + result += '"'; + i = key_end - 1; // -1 because loop will increment + continue; + } + } + } + + i = j - 1; // -1 because loop will increment + continue; + } + + result += c; + } + + *out_normalized = static_cast(malloc(result.size() + 1)); + if (!*out_normalized) { + return RAC_ERROR_OUT_OF_MEMORY; + } + memcpy(*out_normalized, result.c_str(), result.size() + 1); + + return RAC_SUCCESS; +} + +// ============================================================================= +// TOOL NAME AND ARGUMENTS EXTRACTION +// ============================================================================= + +/** + * @brief Extract tool name and arguments using multiple strategies + * + * Strategies in order: + * 1. Standard format: {"tool": "name", "arguments": {...}} + * 2. Name/function variant: {"name": "name", "params": {...}} + * 3. Placeholder key with value being tool name + * 4. Tool name as key: {"calculate": "5 * 100"} + */ +static bool extract_tool_name_and_args(const char* json_obj, char** out_tool_name, + char** out_args_json) { + *out_tool_name = nullptr; + *out_args_json = nullptr; + + // Strategy 1 & 2: Try standard tool name keys + for (int i = 0; TOOL_NAME_KEYS[i] != nullptr; i++) { + char* value = nullptr; + bool is_obj = false; + if (extract_json_value(json_obj, TOOL_NAME_KEYS[i], &value, &is_obj)) { + if (!is_obj && value && strlen(value) > 0) { + *out_tool_name = value; + + // Now find arguments + for (int j = 0; ARGUMENT_KEYS[j] != nullptr; j++) { + char* args_value = nullptr; + bool args_is_obj = false; + if (extract_json_value(json_obj, ARGUMENT_KEYS[j], &args_value, &args_is_obj)) { + if (args_is_obj) { + *out_args_json = args_value; + } else { + // Wrap scalar in {"input": value} - escape the value for valid JSON + std::string escaped_args = escape_json_string(args_value); + size_t wrap_len = escaped_args.size() + 14; // {"input":"" } + null + *out_args_json = static_cast(malloc(wrap_len)); + if (*out_args_json) { + snprintf(*out_args_json, wrap_len, "{\"input\":\"%s\"}", escaped_args.c_str()); + } + free(args_value); + } + return true; + } + } + + // No arguments found - use empty object + *out_args_json = static_cast(malloc(3)); + if (*out_args_json) { + std::memcpy(*out_args_json, "{}", 3); + } + return true; + } + free(value); + } + } + + // Strategy 3 & 4: Tool name as key (non-standard key) + std::vector keys = get_json_keys(json_obj); + for (const auto& key : keys) { + if (!is_standard_key(key.c_str())) { + // Found a non-standard key - treat it as tool name + char* value = nullptr; + bool is_obj = false; + if (extract_json_value(json_obj, key.c_str(), &value, &is_obj)) { + *out_tool_name = static_cast(malloc(key.size() + 1)); + if (*out_tool_name) { + std::memcpy(*out_tool_name, key.c_str(), key.size() + 1); + } + + if (is_obj) { + // Value is object - use as arguments + *out_args_json = value; + } else if (value) { + // Value is scalar - wrap in {"input": value} - escape for valid JSON + std::string escaped_value = escape_json_string(value); + size_t wrap_len = escaped_value.size() + 14; // {"input":"" } + null + *out_args_json = static_cast(malloc(wrap_len)); + if (*out_args_json) { + snprintf(*out_args_json, wrap_len, "{\"input\":\"%s\"}", escaped_value.c_str()); + } + free(value); + } else { + *out_args_json = static_cast(malloc(3)); + if (*out_args_json) { + std::memcpy(*out_args_json, "{}", 3); + } + } + return true; + } + } + } + + return false; +} + +// ============================================================================= +// FORMAT-SPECIFIC PARSERS +// ============================================================================= + +/** + * @brief Parse LFM2 (Liquid AI) format: <|tool_call_start|>[func(arg="val")]<|tool_call_end|> + * + * LFM2 uses Pythonic function call syntax: + * [func_name(arg1="value1", arg2="value2")] + * + * @return true if successfully parsed, false otherwise + */ +static bool parse_lfm2_format(const char* llm_output, char** out_tool_name, char** out_args_json, + char** out_clean_text) { + *out_tool_name = nullptr; + *out_args_json = nullptr; + *out_clean_text = nullptr; + + RAC_LOG_INFO("ToolCalling", "parse_lfm2_format: input='%.200s'%s", + llm_output, strlen(llm_output) > 200 ? "..." : ""); + + // Find start tag + const char* start_tag = strstr(llm_output, TAG_LFM2_START); + if (!start_tag) { + RAC_LOG_INFO("ToolCalling", "LFM2 start tag '%s' not found in output", TAG_LFM2_START); + return false; + } + + RAC_LOG_INFO("ToolCalling", "Found LFM2 start tag at position: %zu", (size_t)(start_tag - llm_output)); + + size_t tag_start_pos = start_tag - llm_output; + const char* content_start = start_tag + strlen(TAG_LFM2_START); + + // Find end tag + const char* end_tag = strstr(content_start, TAG_LFM2_END); + if (!end_tag) { + // Try to parse until end of line or end of string + const char* line_end = strchr(content_start, '\n'); + if (line_end) { + end_tag = line_end; + } else { + end_tag = content_start + strlen(content_start); + } + } + + // Extract content between tags + size_t content_len = end_tag - content_start; + std::string content(content_start, content_len); + + // Parse Pythonic format: [func_name(arg1="val1", arg2="val2")] + // First, strip leading/trailing whitespace and brackets + size_t start = 0, end = content.size(); + while (start < end && (content[start] == ' ' || content[start] == '\n' || content[start] == '[')) { + start++; + } + while (end > start && (content[end - 1] == ' ' || content[end - 1] == '\n' || content[end - 1] == ']')) { + end--; + } + + if (start >= end) { + return false; + } + + std::string call_str = content.substr(start, end - start); + + RAC_LOG_INFO("ToolCalling", "LFM2 call_str: '%s'", call_str.c_str()); + + // Find function name (everything before '(') + size_t paren_pos = call_str.find('('); + if (paren_pos == std::string::npos) { + // No arguments - whole thing is function name + *out_tool_name = static_cast(malloc(call_str.size() + 1)); + if (*out_tool_name) { + std::memcpy(*out_tool_name, call_str.c_str(), call_str.size() + 1); + } + *out_args_json = static_cast(malloc(3)); + if (*out_args_json) { + std::memcpy(*out_args_json, "{}", 3); + } + } else { + std::string func_name = call_str.substr(0, paren_pos); + + // Trim whitespace from function name + while (!func_name.empty() && func_name.back() == ' ') { + func_name.pop_back(); + } + + *out_tool_name = static_cast(malloc(func_name.size() + 1)); + if (*out_tool_name) { + std::memcpy(*out_tool_name, func_name.c_str(), func_name.size() + 1); + } + + // Parse arguments: arg1="val1", arg2="val2", ... + // Convert to JSON format + size_t args_start = paren_pos + 1; + size_t args_end = call_str.rfind(')'); + if (args_end == std::string::npos) { + args_end = call_str.size(); + } + + std::string args_str = call_str.substr(args_start, args_end - args_start); + + RAC_LOG_INFO("ToolCalling", "LFM2 args_str: '%s' (paren=%zu, end=%zu)", + args_str.c_str(), paren_pos, args_end); + + // Convert Python-style args to JSON + std::string json_args = "{"; + bool first_arg = true; + bool in_string = false; + char string_char = 0; + std::string current_key; + std::string current_value; + bool parsing_key = true; + + for (size_t i = 0; i < args_str.size(); i++) { + char c = args_str[i]; + + if (in_string) { + if (c == string_char && (i == 0 || args_str[i - 1] != '\\')) { + in_string = false; + // End of value - escape key and value for valid JSON + if (!current_key.empty()) { + if (!first_arg) { + json_args += ","; + } + std::string escaped_key = escape_json_string(current_key.c_str()); + std::string escaped_val = escape_json_string(current_value.c_str()); + json_args += "\"" + escaped_key + "\":\"" + escaped_val + "\""; + first_arg = false; + current_key.clear(); + current_value.clear(); + parsing_key = true; + } + } else { + current_value += c; + } + } else { + if (c == '"' || c == '\'') { + in_string = true; + string_char = c; + parsing_key = false; + } else if (c == '=') { + parsing_key = false; + } else if (c == ',') { + // Handle unquoted values or numeric values + if (!current_key.empty() && !current_value.empty()) { + if (!first_arg) { + json_args += ","; + } + // Check if value is numeric (handles edge cases) + bool is_numeric = !current_value.empty(); + bool has_dot = false; + bool has_minus = false; + for (size_t i = 0; i < current_value.size() && is_numeric; i++) { + char vc = current_value[i]; + if (vc == '-') { + if (i != 0 || has_minus) is_numeric = false; + has_minus = true; + } else if (vc == '.') { + if (has_dot) is_numeric = false; + has_dot = true; + } else if (!isdigit(vc)) { + is_numeric = false; + } + } + if (current_value == "-" || current_value == ".") is_numeric = false; + // Escape key always; escape value only for non-numeric strings + std::string escaped_key = escape_json_string(current_key.c_str()); + if (is_numeric) { + json_args += "\"" + escaped_key + "\":" + current_value; + } else { + std::string escaped_val = escape_json_string(current_value.c_str()); + json_args += "\"" + escaped_key + "\":\"" + escaped_val + "\""; + } + first_arg = false; + } + current_key.clear(); + current_value.clear(); + parsing_key = true; + } else if (c != ' ' || in_string) { + if (parsing_key) { + current_key += c; + } else { + current_value += c; + } + } + } + } + + // Handle last argument + if (!current_key.empty() && !current_value.empty()) { + if (!first_arg) { + json_args += ","; + } + // Check if value is numeric (handles edge cases) + bool is_numeric = !current_value.empty(); + bool has_dot = false; + bool has_minus = false; + for (size_t i = 0; i < current_value.size() && is_numeric; i++) { + char vc = current_value[i]; + if (vc == '-') { + if (i != 0 || has_minus) is_numeric = false; + has_minus = true; + } else if (vc == '.') { + if (has_dot) is_numeric = false; + has_dot = true; + } else if (!isdigit(vc)) { + is_numeric = false; + } + } + if (current_value == "-" || current_value == ".") is_numeric = false; + // Escape key always; escape value only for non-numeric strings + std::string escaped_key = escape_json_string(current_key.c_str()); + if (is_numeric) { + json_args += "\"" + escaped_key + "\":" + current_value; + } else { + std::string escaped_val = escape_json_string(current_value.c_str()); + json_args += "\"" + escaped_key + "\":\"" + escaped_val + "\""; + } + } + + json_args += "}"; + + RAC_LOG_INFO("ToolCalling", "LFM2 parsed json_args: '%s'", json_args.c_str()); + + *out_args_json = static_cast(malloc(json_args.size() + 1)); + if (*out_args_json) { + std::memcpy(*out_args_json, json_args.c_str(), json_args.size() + 1); + } + } + + RAC_LOG_INFO("ToolCalling", "LFM2 RESULT: tool='%s', args='%s'", + *out_tool_name ? *out_tool_name : "(null)", + *out_args_json ? *out_args_json : "(null)"); + + // Build clean text + std::string clean_text; + clean_text.append(llm_output, tag_start_pos); + + const char* after_end = end_tag; + if (strstr(end_tag, TAG_LFM2_END) == end_tag) { + after_end = end_tag + strlen(TAG_LFM2_END); + } + if (*after_end) { + clean_text.append(after_end); + } + + // Trim + size_t trim_start = 0, trim_end = clean_text.size(); + while (trim_start < trim_end && (clean_text[trim_start] == ' ' || clean_text[trim_start] == '\n')) { + trim_start++; + } + while (trim_end > trim_start && (clean_text[trim_end - 1] == ' ' || clean_text[trim_end - 1] == '\n')) { + trim_end--; + } + + *out_clean_text = static_cast(malloc(trim_end - trim_start + 1)); + if (*out_clean_text) { + memcpy(*out_clean_text, clean_text.c_str() + trim_start, trim_end - trim_start); + (*out_clean_text)[trim_end - trim_start] = '\0'; + } + + return *out_tool_name != nullptr; +} + +/** + * @brief Parse default format: JSON + * + * This is the original SDK format with JSON inside the tags. + * Handles edge cases like missing closing tags, unquoted keys, etc. + * + * @return true if successfully parsed, false otherwise + */ +static bool parse_default_format(const char* llm_output, char** out_tool_name, char** out_args_json, + char** out_clean_text); + +// ============================================================================= +// PARSE TOOL CALL - Main entry points +// ============================================================================= + +extern "C" rac_result_t rac_tool_call_parse(const char* llm_output, rac_tool_call_t* out_result) { + // Auto-detect format from output, then parse + rac_tool_call_format_t detected = rac_tool_call_detect_format(llm_output); + return rac_tool_call_parse_with_format(llm_output, detected, out_result); +} + +/** + * @brief Implementation of parse_default_format + * + * Parses the default JSON format. + */ +static bool parse_default_format(const char* llm_output, char** out_tool_name, char** out_args_json, + char** out_clean_text) { + *out_tool_name = nullptr; + *out_args_json = nullptr; + *out_clean_text = nullptr; + + size_t output_len = strlen(llm_output); + + // Find tag + const char* tag_start = find_str(llm_output, TAG_DEFAULT_START); + if (!tag_start) { + return false; + } + + size_t tag_start_pos = tag_start - llm_output; + size_t json_start_pos = tag_start_pos + strlen(TAG_DEFAULT_START); + + // Find end tag + const char* tag_end = find_str(llm_output + json_start_pos, TAG_DEFAULT_END); + size_t json_end_pos; + bool has_closing_tag; + + if (tag_end) { + json_end_pos = (tag_end - llm_output); + has_closing_tag = true; + } else { + // No closing tag - find JSON by matching braces + size_t brace_end; + if (!find_matching_brace(llm_output, json_start_pos, &brace_end)) { + return false; + } + json_end_pos = brace_end + 1; + has_closing_tag = false; + } + + // Extract JSON between tags + size_t json_len = json_end_pos - json_start_pos; + char* tool_json_str = static_cast(malloc(json_len + 1)); + if (!tool_json_str) { + return false; + } + memcpy(tool_json_str, llm_output + json_start_pos, json_len); + tool_json_str[json_len] = '\0'; + + // Normalize JSON (handle unquoted keys) + char* normalized_json = nullptr; + rac_result_t norm_result = rac_tool_call_normalize_json(tool_json_str, &normalized_json); + free(tool_json_str); + + if (norm_result != RAC_SUCCESS || !normalized_json) { + return false; + } + + // Extract tool name and arguments + if (!extract_tool_name_and_args(normalized_json, out_tool_name, out_args_json)) { + free(normalized_json); + return false; + } + + free(normalized_json); + + // Build clean text (everything except the tool call tags) + std::string clean_text; + clean_text.append(llm_output, tag_start_pos); + + if (has_closing_tag) { + size_t after_tag = json_end_pos + strlen(TAG_DEFAULT_END); + if (after_tag < output_len) { + clean_text.append(llm_output + after_tag); + } + } else { + if (json_end_pos < output_len) { + clean_text.append(llm_output + json_end_pos); + } + } + + // Trim whitespace + size_t trim_start, trim_end; + trim_whitespace(clean_text.c_str(), clean_text.size(), &trim_start, &trim_end); + + size_t clean_len = trim_end - trim_start; + *out_clean_text = static_cast(malloc(clean_len + 1)); + if (*out_clean_text) { + memcpy(*out_clean_text, clean_text.c_str() + trim_start, clean_len); + (*out_clean_text)[clean_len] = '\0'; + } + + return *out_tool_name != nullptr; +} + +extern "C" rac_result_t rac_tool_call_parse_with_format(const char* llm_output, + rac_tool_call_format_t format, + rac_tool_call_t* out_result) { + if (!llm_output || !out_result) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + // Initialize result + out_result->has_tool_call = RAC_FALSE; + out_result->tool_name = nullptr; + out_result->arguments_json = nullptr; + out_result->clean_text = nullptr; + out_result->call_id = 0; + out_result->format = RAC_TOOL_FORMAT_DEFAULT; + + size_t output_len = strlen(llm_output); + + // Parse using the appropriate format parser + char* tool_name = nullptr; + char* args_json = nullptr; + char* clean_text = nullptr; + bool parsed = false; + + switch (format) { + case RAC_TOOL_FORMAT_DEFAULT: + parsed = parse_default_format(llm_output, &tool_name, &args_json, &clean_text); + break; + + case RAC_TOOL_FORMAT_LFM2: + parsed = parse_lfm2_format(llm_output, &tool_name, &args_json, &clean_text); + break; + + default: + parsed = false; + break; + } + + if (parsed && tool_name) { + out_result->has_tool_call = RAC_TRUE; + out_result->tool_name = tool_name; + out_result->arguments_json = args_json; + out_result->clean_text = clean_text; + out_result->format = format; + out_result->call_id = static_cast(time(nullptr)) * 1000 + (rand() % 1000); + } else { + // Parsing failed - clean up any partial results + if (tool_name) free(tool_name); + if (args_json) free(args_json); + if (clean_text) free(clean_text); + + // Return original text as clean_text + out_result->clean_text = static_cast(malloc(output_len + 1)); + if (out_result->clean_text) { + std::memcpy(out_result->clean_text, llm_output, output_len + 1); + } + } + + return RAC_SUCCESS; +} + +extern "C" void rac_tool_call_free(rac_tool_call_t* result) { + if (!result) { + return; + } + + if (result->tool_name) { + free(result->tool_name); + result->tool_name = nullptr; + } + + if (result->arguments_json) { + free(result->arguments_json); + result->arguments_json = nullptr; + } + + if (result->clean_text) { + free(result->clean_text); + result->clean_text = nullptr; + } + + result->has_tool_call = RAC_FALSE; + result->call_id = 0; +} + +// ============================================================================= +// PROMPT FORMATTING +// ============================================================================= + +/** + * @brief Get parameter type name + */ +static const char* get_param_type_name(rac_tool_param_type_t type) { + switch (type) { + case RAC_TOOL_PARAM_STRING: + return "string"; + case RAC_TOOL_PARAM_NUMBER: + return "number"; + case RAC_TOOL_PARAM_BOOLEAN: + return "boolean"; + case RAC_TOOL_PARAM_OBJECT: + return "object"; + case RAC_TOOL_PARAM_ARRAY: + return "array"; + default: + return "unknown"; + } +} + +/** + * @brief Generate format-specific tool calling instructions + * + * Returns the format-specific syntax, examples, and rules. + */ +static std::string get_format_instructions(rac_tool_call_format_t format) { + std::string instructions; + + switch (format) { + case RAC_TOOL_FORMAT_LFM2: + // Liquid AI LFM2 format + instructions += "TOOL CALLING FORMAT (LFM2):\n"; + instructions += "When you need to use a tool, output ONLY this format:\n"; + instructions += "<|tool_call_start|>[TOOL_NAME(param=\"VALUE_FROM_USER_QUERY\")]<|tool_call_end|>\n\n"; + + instructions += "CRITICAL: Extract the EXACT value from the user's question:\n"; + instructions += "- User asks 'weather in Tokyo' -> <|tool_call_start|>[get_weather(location=\"Tokyo\")]<|tool_call_end|>\n"; + instructions += "- User asks 'weather in sf' -> <|tool_call_start|>[get_weather(location=\"San Francisco\")]<|tool_call_end|>\n\n"; + + instructions += "RULES:\n"; + instructions += "1. For greetings or general chat, respond normally without tools\n"; + instructions += "2. Use Python-style function call syntax inside the tags\n"; + instructions += "3. String values MUST be quoted with double quotes\n"; + instructions += "4. Multiple arguments are separated by commas"; + break; + + case RAC_TOOL_FORMAT_DEFAULT: + default: + // Default SDK format + instructions += "TOOL CALLING FORMAT - YOU MUST USE THIS EXACT FORMAT:\n"; + instructions += "When you need to use a tool, output ONLY this (no other text before or after):\n"; + instructions += "{\"tool\": \"TOOL_NAME\", \"arguments\": {\"PARAM_NAME\": \"VALUE_FROM_USER_QUERY\"}}\n\n"; + + instructions += "CRITICAL: Extract the EXACT value from the user's question:\n"; + instructions += "- User asks 'weather in Tokyo' -> {\"tool\": \"get_weather\", \"arguments\": {\"location\": \"Tokyo\"}}\n"; + instructions += "- User asks 'weather in sf' -> {\"tool\": \"get_weather\", \"arguments\": {\"location\": \"San Francisco\"}}\n\n"; + + instructions += "RULES:\n"; + instructions += "1. For greetings or general chat, respond normally without tools\n"; + instructions += "2. When using a tool, output ONLY the tag, nothing else\n"; + instructions += "3. Use the exact parameter names shown in the tool definitions above"; + break; + } + + return instructions; +} + +/** + * @brief Generate format-specific example for JSON prompt + */ +static std::string get_format_example_json(rac_tool_call_format_t format) { + std::string example; + + switch (format) { + case RAC_TOOL_FORMAT_LFM2: + // LFM2 format - enhanced with more math examples for better reliability + example += "## OUTPUT FORMAT\n"; + example += "You MUST respond with ONLY a tool call in this exact format:\n"; + example += "<|tool_call_start|>[function_name(param=\"value\")]<|tool_call_end|>\n\n"; + example += "CRITICAL: Always include the FULL format with <|tool_call_start|> and <|tool_call_end|> tags.\n\n"; + example += "## EXAMPLES\n"; + example += "Q: What's the weather in NYC?\n"; + example += "A: <|tool_call_start|>[get_weather(location=\"New York\")]<|tool_call_end|>\n\n"; + example += "Q: weather in sf\n"; + example += "A: <|tool_call_start|>[get_weather(location=\"San Francisco\")]<|tool_call_end|>\n\n"; + example += "Q: calculate 2+2\n"; + example += "A: <|tool_call_start|>[calculate(expression=\"2+2\")]<|tool_call_end|>\n\n"; + example += "Q: What's 5*10?\n"; + example += "A: <|tool_call_start|>[calculate(expression=\"5*10\")]<|tool_call_end|>\n\n"; + example += "Q: What is 100/4?\n"; + example += "A: <|tool_call_start|>[calculate(expression=\"100/4\")]<|tool_call_end|>\n"; + break; + + case RAC_TOOL_FORMAT_DEFAULT: + default: + example += "## OUTPUT FORMAT\n"; + example += "You MUST respond with ONLY a tool call in this exact format:\n"; + example += "{\"tool\": \"function_name\", \"arguments\": {\"param\": \"value\"}}\n\n"; + example += "## EXAMPLES\n"; + example += "Q: What's the weather in NYC?\n"; + example += "A: {\"tool\": \"get_weather\", \"arguments\": {\"location\": \"New York\"}}\n\n"; + example += "Q: weather in sf\n"; + example += "A: {\"tool\": \"get_weather\", \"arguments\": {\"location\": \"San Francisco\"}}\n\n"; + example += "Q: calculate 2+2\n"; + example += "A: {\"tool\": \"calculate\", \"arguments\": {\"expression\": \"2+2\"}}\n"; + break; + } + + return example; +} + +// ============================================================================= +// FORMAT-AWARE PROMPT GENERATION +// ============================================================================= + +extern "C" rac_result_t rac_tool_call_format_prompt_with_format(const rac_tool_definition_t* definitions, + size_t num_definitions, + rac_tool_call_format_t format, + char** out_prompt) { + if (!out_prompt) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + if (!definitions || num_definitions == 0) { + *out_prompt = static_cast(malloc(1)); + if (*out_prompt) { + (*out_prompt)[0] = '\0'; + } + return RAC_SUCCESS; + } + + rac_tool_call_format_t actual_format = format; + + std::string prompt; + prompt.reserve(1024); + + prompt += "You have access to these tools:\n\n"; + + for (size_t i = 0; i < num_definitions; i++) { + const rac_tool_definition_t& tool = definitions[i]; + + prompt += "- "; + prompt += tool.name ? tool.name : "unknown"; + prompt += ": "; + prompt += tool.description ? tool.description : ""; + prompt += "\n"; + + if (tool.parameters && tool.num_parameters > 0) { + prompt += " Parameters:\n"; + for (size_t j = 0; j < tool.num_parameters; j++) { + const rac_tool_parameter_t& param = tool.parameters[j]; + prompt += " - "; + prompt += param.name ? param.name : "unknown"; + prompt += " ("; + prompt += get_param_type_name(param.type); + if (param.required) { + prompt += ", required"; + } + prompt += "): "; + prompt += param.description ? param.description : ""; + prompt += "\n"; + } + } + prompt += "\n"; + } + + // Add format-specific instructions + prompt += get_format_instructions(actual_format); + + *out_prompt = static_cast(malloc(prompt.size() + 1)); + if (!*out_prompt) { + return RAC_ERROR_OUT_OF_MEMORY; + } + memcpy(*out_prompt, prompt.c_str(), prompt.size() + 1); + + return RAC_SUCCESS; +} + +extern "C" rac_result_t rac_tool_call_format_prompt_json_with_format(const char* tools_json, + rac_tool_call_format_t format, + char** out_prompt) { + if (!out_prompt) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + if (!tools_json || strlen(tools_json) == 0 || strcmp(tools_json, "[]") == 0) { + *out_prompt = static_cast(malloc(1)); + if (*out_prompt) { + (*out_prompt)[0] = '\0'; + } + return RAC_SUCCESS; + } + + rac_tool_call_format_t actual_format = format; + + std::string prompt; + prompt.reserve(1024 + strlen(tools_json)); + + prompt += "# TOOLS\n"; + prompt += tools_json; + prompt += "\n\n"; + + // Add format-specific example with direct instructions + prompt += get_format_example_json(actual_format); + + prompt += "\n\n## RULES\n"; + prompt += "- Weather question = call get_weather\n"; + prompt += "- Math/calculation question (add, subtract, multiply, divide, \"what's X*Y\", etc.) = call calculate with the EXPRESSION as a string\n"; + prompt += "- Time question = call get_current_time\n"; + prompt += "- DO NOT compute answers yourself. ALWAYS use the tool with the original expression.\n"; + + // Format-specific tag instructions + if (actual_format == RAC_TOOL_FORMAT_LFM2) { + prompt += "- ALWAYS include <|tool_call_start|> and <|tool_call_end|> tags.\n"; + } else { + prompt += "- ALWAYS include and tags.\n"; + } + + RAC_LOG_INFO("ToolCalling", "Generated tool prompt (format=%d): %.500s...", + (int)actual_format, prompt.c_str()); + + *out_prompt = static_cast(malloc(prompt.size() + 1)); + if (!*out_prompt) { + return RAC_ERROR_OUT_OF_MEMORY; + } + memcpy(*out_prompt, prompt.c_str(), prompt.size() + 1); + + return RAC_SUCCESS; +} + +// ============================================================================= +// LEGACY PROMPT GENERATION (uses DEFAULT format) +// ============================================================================= + +extern "C" rac_result_t rac_tool_call_format_prompt(const rac_tool_definition_t* definitions, + size_t num_definitions, char** out_prompt) { + // Delegate to format-aware version with DEFAULT format + return rac_tool_call_format_prompt_with_format(definitions, num_definitions, + RAC_TOOL_FORMAT_DEFAULT, out_prompt); +} + +extern "C" rac_result_t rac_tool_call_format_prompt_json(const char* tools_json, char** out_prompt) { + // Delegate to format-aware version with DEFAULT format + return rac_tool_call_format_prompt_json_with_format(tools_json, RAC_TOOL_FORMAT_DEFAULT, out_prompt); +} + +extern "C" rac_result_t rac_tool_call_format_prompt_json_with_format_name(const char* tools_json, + const char* format_name, + char** out_prompt) { + // Convert format name to enum and delegate + rac_tool_call_format_t format = rac_tool_call_format_from_name(format_name); + RAC_LOG_INFO("ToolCalling", "Formatting prompt with format_name='%s' -> enum=%d", + format_name ? format_name : "null", (int)format); + return rac_tool_call_format_prompt_json_with_format(tools_json, format, out_prompt); +} + +extern "C" rac_result_t rac_tool_call_build_initial_prompt(const char* user_prompt, + const char* tools_json, + const rac_tool_calling_options_t* options, + char** out_prompt) { + if (!user_prompt || !out_prompt) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + // Get format from options (default to DEFAULT) + rac_tool_call_format_t format = options ? options->format : RAC_TOOL_FORMAT_DEFAULT; + + // Format tools prompt with the specified format + char* tools_prompt = nullptr; + rac_result_t result = rac_tool_call_format_prompt_json_with_format(tools_json, format, &tools_prompt); + if (result != RAC_SUCCESS) { + return result; + } + + std::string full_prompt; + full_prompt.reserve(2048); + + // Add system prompt if provided + if (options && options->system_prompt) { + if (options->replace_system_prompt) { + // Replace entirely - just use the system prompt + full_prompt += options->system_prompt; + full_prompt += "\n\n"; + } else { + // Append tool instructions after system prompt + full_prompt += options->system_prompt; + full_prompt += "\n\n"; + } + } + + // Add tools prompt (unless replace_system_prompt is true and we already have system_prompt) + if (!(options && options->replace_system_prompt && options->system_prompt)) { + if (tools_prompt && strlen(tools_prompt) > 0) { + full_prompt += tools_prompt; + full_prompt += "\n\n"; + } + } + + // Add user prompt + full_prompt += "User: "; + full_prompt += user_prompt; + + free(tools_prompt); + + *out_prompt = static_cast(malloc(full_prompt.size() + 1)); + if (!*out_prompt) { + return RAC_ERROR_OUT_OF_MEMORY; + } + memcpy(*out_prompt, full_prompt.c_str(), full_prompt.size() + 1); + + return RAC_SUCCESS; +} + +extern "C" rac_result_t rac_tool_call_build_followup_prompt(const char* original_user_prompt, + const char* tools_prompt, + const char* tool_name, + const char* tool_result_json, + rac_bool_t keep_tools_available, + char** out_prompt) { + if (!original_user_prompt || !tool_name || !out_prompt) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + std::string prompt; + prompt.reserve(1024); + + // Include tools again if keepToolsAvailable + if (keep_tools_available && tools_prompt && strlen(tools_prompt) > 0) { + prompt += tools_prompt; + prompt += "\n\n"; + } + + prompt += "Previous user question: "; + prompt += original_user_prompt; + prompt += "\n\n"; + + prompt += "Tool '"; + prompt += tool_name; + prompt += "' was executed with this result:\n"; + prompt += tool_result_json ? tool_result_json : "{}"; + prompt += "\n\n"; + + if (keep_tools_available) { + prompt += "Using this information, respond to the user's original question. "; + prompt += "You may use additional tools if needed."; + } else { + prompt += "Using this information, provide a natural response to the user's original question. "; + prompt += "Do not use any tool tags in your response - just respond naturally."; + } + + *out_prompt = static_cast(malloc(prompt.size() + 1)); + if (!*out_prompt) { + return RAC_ERROR_OUT_OF_MEMORY; + } + memcpy(*out_prompt, prompt.c_str(), prompt.size() + 1); + + return RAC_SUCCESS; +} + +// ============================================================================= +// JSON SERIALIZATION UTILITIES +// ============================================================================= + +extern "C" rac_result_t rac_tool_call_definitions_to_json(const rac_tool_definition_t* definitions, + size_t num_definitions, + char** out_json) { + if (!out_json) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + if (!definitions || num_definitions == 0) { + *out_json = static_cast(malloc(3)); + if (*out_json) { + std::memcpy(*out_json, "[]", 3); + } + return RAC_SUCCESS; + } + + std::string json; + json.reserve(512 * num_definitions); + json += "["; + + for (size_t i = 0; i < num_definitions; i++) { + if (i > 0) { + json += ","; + } + + const rac_tool_definition_t& tool = definitions[i]; + + json += "{"; + json += "\"name\":\""; + json += escape_json_string(tool.name); + json += "\","; + json += "\"description\":\""; + json += escape_json_string(tool.description); + json += "\","; + json += "\"parameters\":["; + + if (tool.parameters) { + for (size_t j = 0; j < tool.num_parameters; j++) { + if (j > 0) { + json += ","; + } + + const rac_tool_parameter_t& param = tool.parameters[j]; + + json += "{"; + json += "\"name\":\""; + json += escape_json_string(param.name); + json += "\","; + json += "\"type\":\""; + json += get_param_type_name(param.type); + json += "\","; + json += "\"description\":\""; + json += escape_json_string(param.description); + json += "\","; + json += "\"required\":"; + json += param.required ? "true" : "false"; + json += "}"; + } + } + + json += "]"; + + if (tool.category) { + json += ",\"category\":\""; + json += escape_json_string(tool.category); + json += "\""; + } + + json += "}"; + } + + json += "]"; + + *out_json = static_cast(malloc(json.size() + 1)); + if (!*out_json) { + return RAC_ERROR_OUT_OF_MEMORY; + } + memcpy(*out_json, json.c_str(), json.size() + 1); + + return RAC_SUCCESS; +} + +extern "C" rac_result_t rac_tool_call_result_to_json(const char* tool_name, rac_bool_t success, + const char* result_json, + const char* error_message, + char** out_json) { + if (!tool_name || !out_json) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + std::string json; + json.reserve(256); + + json += "{"; + json += "\"toolName\":\""; + json += escape_json_string(tool_name); + json += "\","; + json += "\"success\":"; + json += success ? "true" : "false"; + + if (success && result_json) { + json += ",\"result\":"; + json += result_json; // Already JSON + } + + if (!success && error_message) { + json += ",\"error\":\""; + json += escape_json_string(error_message); + json += "\""; + } + + json += "}"; + + *out_json = static_cast(malloc(json.size() + 1)); + if (!*out_json) { + return RAC_ERROR_OUT_OF_MEMORY; + } + memcpy(*out_json, json.c_str(), json.size() + 1); + + return RAC_SUCCESS; +} diff --git a/sdk/runanywhere-commons/src/features/platform/rac_backend_platform_register.cpp b/sdk/runanywhere-commons/src/features/platform/rac_backend_platform_register.cpp index 2c281726c..0e999bda9 100644 --- a/sdk/runanywhere-commons/src/features/platform/rac_backend_platform_register.cpp +++ b/sdk/runanywhere-commons/src/features/platform/rac_backend_platform_register.cpp @@ -9,12 +9,17 @@ #include #include +#include #include #include "rac/core/rac_core.h" + +namespace fs = std::filesystem; #include "rac/core/rac_error.h" #include "rac/core/rac_logger.h" +#include "rac/features/diffusion/rac_diffusion_service.h" #include "rac/features/llm/rac_llm_service.h" +#include "rac/features/platform/rac_diffusion_platform.h" #include "rac/features/platform/rac_llm_platform.h" #include "rac/features/platform/rac_tts_platform.h" #include "rac/features/tts/rac_tts_service.h" @@ -268,6 +273,186 @@ static const rac_tts_service_ops_t g_platform_tts_ops = { .destroy = platform_tts_vtable_destroy, }; +// ============================================================================= +// DIFFUSION VTABLE IMPLEMENTATION - ml-stable-diffusion +// ============================================================================= + +// Initialize +static rac_result_t platform_diffusion_vtable_initialize(void* impl, const char* model_path, + const rac_diffusion_config_t* config) { + (void)impl; + (void)model_path; + (void)config; + RAC_LOG_DEBUG(LOG_CAT, "Diffusion initialize (handled during create)"); + return RAC_SUCCESS; +} + +// Generate (blocking) +static rac_result_t platform_diffusion_vtable_generate(void* impl, + const rac_diffusion_options_t* options, + rac_diffusion_result_t* out_result) { + if (!impl || !options || !out_result) + return RAC_ERROR_NULL_POINTER; + + RAC_LOG_DEBUG(LOG_CAT, "Diffusion generate via Swift"); + + // Convert options + rac_diffusion_platform_options_t platform_options = {}; + platform_options.prompt = options->prompt; + platform_options.negative_prompt = options->negative_prompt; + platform_options.width = options->width; + platform_options.height = options->height; + platform_options.steps = options->steps; + platform_options.guidance_scale = options->guidance_scale; + platform_options.seed = options->seed; + platform_options.scheduler = options->scheduler; + + auto handle = static_cast(impl); + rac_diffusion_platform_result_t platform_result = {}; + + rac_result_t result = rac_diffusion_platform_generate(handle, &platform_options, &platform_result); + + if (result == RAC_SUCCESS) { + // Copy result + out_result->image_data = platform_result.image_data; // Transfer ownership + out_result->image_size = platform_result.image_size; + out_result->width = platform_result.width; + out_result->height = platform_result.height; + out_result->seed_used = platform_result.seed_used; + out_result->safety_flagged = platform_result.safety_triggered; + out_result->error_code = RAC_SUCCESS; + } + + return result; +} + +// Progress callback wrapper +struct DiffusionProgressWrapper { + rac_diffusion_progress_callback_fn callback; + void* user_data; +}; + +static rac_bool_t platform_diffusion_progress_adapter(float progress, int32_t step, + int32_t total_steps, void* user_data) { + auto* wrapper = static_cast(user_data); + if (!wrapper || !wrapper->callback) { + return RAC_TRUE; + } + + rac_diffusion_progress_t prog = {}; + prog.progress = progress; + prog.current_step = step; + prog.total_steps = total_steps; + prog.stage = "Generating"; + + return wrapper->callback(&prog, wrapper->user_data); +} + +// Generate with progress +static rac_result_t platform_diffusion_vtable_generate_with_progress( + void* impl, const rac_diffusion_options_t* options, + rac_diffusion_progress_callback_fn progress_callback, void* user_data, + rac_diffusion_result_t* out_result) { + if (!impl || !options || !out_result) + return RAC_ERROR_NULL_POINTER; + + RAC_LOG_DEBUG(LOG_CAT, "Diffusion generate with progress via Swift"); + + // Convert options + rac_diffusion_platform_options_t platform_options = {}; + platform_options.prompt = options->prompt; + platform_options.negative_prompt = options->negative_prompt; + platform_options.width = options->width; + platform_options.height = options->height; + platform_options.steps = options->steps; + platform_options.guidance_scale = options->guidance_scale; + platform_options.seed = options->seed; + platform_options.scheduler = options->scheduler; + + auto handle = static_cast(impl); + rac_diffusion_platform_result_t platform_result = {}; + + // Setup progress wrapper + DiffusionProgressWrapper wrapper = {progress_callback, user_data}; + + rac_result_t result = rac_diffusion_platform_generate_with_progress( + handle, &platform_options, platform_diffusion_progress_adapter, &wrapper, &platform_result); + + if (result == RAC_SUCCESS) { + out_result->image_data = platform_result.image_data; + out_result->image_size = platform_result.image_size; + out_result->width = platform_result.width; + out_result->height = platform_result.height; + out_result->seed_used = platform_result.seed_used; + out_result->safety_flagged = platform_result.safety_triggered; + out_result->error_code = RAC_SUCCESS; + } + + return result; +} + +// Get info +static rac_result_t platform_diffusion_vtable_get_info(void* impl, rac_diffusion_info_t* out_info) { + (void)impl; + if (!out_info) + return RAC_ERROR_NULL_POINTER; + + out_info->is_ready = RAC_TRUE; + out_info->current_model = nullptr; + out_info->model_variant = RAC_DIFFUSION_MODEL_SD_1_5; + out_info->supports_text_to_image = RAC_TRUE; + out_info->supports_image_to_image = RAC_TRUE; + out_info->supports_inpainting = RAC_TRUE; + out_info->safety_checker_enabled = RAC_TRUE; + out_info->max_width = 1024; + out_info->max_height = 1024; + + return RAC_SUCCESS; +} + +// Get capabilities +static uint32_t platform_diffusion_vtable_get_capabilities(void* impl) { + (void)impl; + return RAC_DIFFUSION_CAP_TEXT_TO_IMAGE | RAC_DIFFUSION_CAP_IMAGE_TO_IMAGE | + RAC_DIFFUSION_CAP_INPAINTING | RAC_DIFFUSION_CAP_INTERMEDIATE_IMAGES | + RAC_DIFFUSION_CAP_SAFETY_CHECKER; +} + +// Cancel +static rac_result_t platform_diffusion_vtable_cancel(void* impl) { + if (!impl) + return RAC_ERROR_NULL_POINTER; + + RAC_LOG_DEBUG(LOG_CAT, "Diffusion cancel via Swift"); + return rac_diffusion_platform_cancel(static_cast(impl)); +} + +// Cleanup (no-op) +static rac_result_t platform_diffusion_vtable_cleanup(void* impl) { + (void)impl; + return RAC_SUCCESS; +} + +// Destroy +static void platform_diffusion_vtable_destroy(void* impl) { + if (impl) { + RAC_LOG_DEBUG(LOG_CAT, "Diffusion destroy via Swift"); + rac_diffusion_platform_destroy(static_cast(impl)); + } +} + +// Static vtable for Platform Diffusion +static const rac_diffusion_service_ops_t g_platform_diffusion_ops = { + .initialize = platform_diffusion_vtable_initialize, + .generate = platform_diffusion_vtable_generate, + .generate_with_progress = platform_diffusion_vtable_generate_with_progress, + .get_info = platform_diffusion_vtable_get_info, + .get_capabilities = platform_diffusion_vtable_get_capabilities, + .cancel = platform_diffusion_vtable_cancel, + .cleanup = platform_diffusion_vtable_cleanup, + .destroy = platform_diffusion_vtable_destroy, +}; + // ============================================================================= // REGISTRY STATE // ============================================================================= @@ -277,6 +462,7 @@ struct PlatformRegistryState { bool registered = false; char provider_llm_name[32] = "AppleFoundationModels"; char provider_tts_name[32] = "SystemTTS"; + char provider_diffusion_name[32] = "CoreMLDiffusion"; char module_id[16] = "platform"; }; @@ -438,10 +624,209 @@ rac_handle_t platform_tts_create(const rac_service_request_t* request, void* use return service; } +// ============================================================================= +// DIFFUSION SERVICE PROVIDER - CoreML Diffusion +// ============================================================================= + +rac_bool_t platform_diffusion_can_handle(const rac_service_request_t* request, void* user_data) { + (void)user_data; + + RAC_LOG_INFO(LOG_CAT, "CoreMLDiffusion can_handle: ENTRY"); + + if (request == nullptr) { + RAC_LOG_INFO(LOG_CAT, "CoreMLDiffusion can_handle: null request -> FALSE"); + return RAC_FALSE; + } + + // Get the model path - prefer model_path over identifier + const char* path_str = request->model_path ? request->model_path : request->identifier; + + RAC_LOG_INFO(LOG_CAT, "CoreMLDiffusion can_handle: path=%s, framework=%d", + path_str ? path_str : "NULL", request->framework); + + // CRITICAL: Check for CoreML model files FIRST, before framework hint + // This prevents incorrectly handling ONNX models when registry lookup fails + if (path_str != nullptr) { + fs::path model_path(path_str); + + // Check if the path itself is a .mlmodelc or .mlpackage + std::string extension = model_path.extension().string(); + if (extension == ".mlmodelc" || extension == ".mlpackage") { + if (fs::exists(model_path) && fs::is_directory(model_path)) { + RAC_LOG_DEBUG(LOG_CAT, "Diffusion can_handle: found CoreML model at path -> true"); + return RAC_TRUE; + } + } + + // Check if directory contains CoreML model subdirectories (Unet.mlmodelc, etc.) + if (fs::exists(model_path) && fs::is_directory(model_path)) { + try { + bool has_coreml_files = false; + bool has_onnx_files = false; + + for (const auto& entry : fs::directory_iterator(model_path)) { + std::string name = entry.path().filename().string(); + + // Check for CoreML model directories + if (entry.is_directory()) { + if (name.find(".mlmodelc") != std::string::npos || + name.find(".mlpackage") != std::string::npos) { + has_coreml_files = true; + } + } + + // Check for ONNX files - if present, this is NOT a CoreML model + if (entry.path().extension() == ".onnx") { + has_onnx_files = true; + } + + // Check subdirectories for ONNX files (unet/, text_encoder/, etc.) + if (entry.is_directory() && !has_onnx_files) { + try { + for (const auto& subentry : fs::directory_iterator(entry.path())) { + if (subentry.path().extension() == ".onnx") { + has_onnx_files = true; + break; + } + } + } catch (const fs::filesystem_error&) { + // Ignore + } + } + } + + // If we found ONNX files, this is NOT a CoreML model - let ONNX backend handle it + if (has_onnx_files) { + RAC_LOG_DEBUG(LOG_CAT, "Diffusion can_handle: found .onnx files, deferring to ONNX backend -> false"); + return RAC_FALSE; + } + + if (has_coreml_files) { + RAC_LOG_DEBUG(LOG_CAT, "Diffusion can_handle: found CoreML model in directory -> true"); + return RAC_TRUE; + } + } catch (const fs::filesystem_error&) { + // Ignore filesystem errors + } + } + } + + // Only accept framework hint if explicitly set to CoreML AND no path was provided + // (this handles built-in models that don't have a path) + if (request->framework == RAC_FRAMEWORK_COREML && path_str == nullptr) { + RAC_LOG_DEBUG(LOG_CAT, "Diffusion can_handle: framework hint COREML with no path -> true"); + return RAC_TRUE; + } + + // If framework explicitly set to something other than CoreML or Unknown, don't handle + if (request->framework != RAC_FRAMEWORK_UNKNOWN && request->framework != RAC_FRAMEWORK_COREML) { + RAC_LOG_DEBUG(LOG_CAT, "Diffusion can_handle: framework mismatch (%d) -> false", request->framework); + return RAC_FALSE; + } + + // Check if Swift callbacks are available for additional checks + const auto* callbacks = rac_platform_diffusion_get_callbacks(); + if (callbacks == nullptr || callbacks->can_handle == nullptr) { + RAC_LOG_DEBUG(LOG_CAT, "Diffusion can_handle: no Swift callbacks -> false"); + return RAC_FALSE; + } + + // Delegate to Swift for additional checks (e.g., model ID patterns) + rac_bool_t swift_result = callbacks->can_handle(request->identifier, callbacks->user_data); + RAC_LOG_DEBUG(LOG_CAT, "Diffusion can_handle: Swift callback returned %d", swift_result); + return swift_result; +} + +/** + * Create CoreML Diffusion service with vtable. + * Returns an rac_diffusion_service_t* that the generic API can dispatch through. + */ +rac_handle_t platform_diffusion_create(const rac_service_request_t* request, void* user_data) { + (void)user_data; + + if (request == nullptr) { + RAC_LOG_ERROR(LOG_CAT, "Diffusion create: null request"); + return nullptr; + } + + const auto* callbacks = rac_platform_diffusion_get_callbacks(); + if (callbacks == nullptr || callbacks->create == nullptr) { + RAC_LOG_ERROR(LOG_CAT, "Diffusion create: Swift callbacks not registered"); + return nullptr; + } + + RAC_LOG_INFO(LOG_CAT, "Creating CoreML Diffusion service via Swift"); + + const char* model_path = request->model_path ? request->model_path : request->identifier; + rac_diffusion_platform_config_t config = {}; + config.model_variant = RAC_DIFFUSION_MODEL_SD_1_5; + config.enable_safety_checker = RAC_TRUE; + config.reduce_memory = RAC_FALSE; + config.compute_units = 0; // Auto + + // Create backend-specific handle via Swift + rac_handle_t backend_handle = callbacks->create(model_path, &config, callbacks->user_data); + if (!backend_handle) { + RAC_LOG_ERROR(LOG_CAT, "Swift diffusion create callback returned null"); + return nullptr; + } + + // Allocate service struct with vtable + auto* service = static_cast(malloc(sizeof(rac_diffusion_service_t))); + if (!service) { + rac_diffusion_platform_destroy(static_cast(backend_handle)); + return nullptr; + } + + service->ops = &g_platform_diffusion_ops; + service->impl = backend_handle; + service->model_id = request->identifier ? strdup(request->identifier) : nullptr; + + RAC_LOG_INFO(LOG_CAT, "CoreML Diffusion service created successfully"); + return service; +} + // ============================================================================= // BUILT-IN MODEL REGISTRATION // ============================================================================= +void register_coreml_diffusion_entry() { + rac_model_registry* registry = rac_get_model_registry(); + if (registry == nullptr) { + return; + } + + rac_model_info_t model = {}; + model.id = strdup("coreml-diffusion"); + model.name = strdup("CoreML Diffusion"); + model.category = RAC_MODEL_CATEGORY_IMAGE_GENERATION; + model.format = RAC_MODEL_FORMAT_COREML; + model.framework = RAC_FRAMEWORK_COREML; + model.download_url = nullptr; + model.local_path = strdup("builtin://coreml-diffusion"); + model.artifact_info.kind = RAC_ARTIFACT_KIND_BUILT_IN; + model.download_size = 0; + model.memory_required = 4000000000; // ~4GB for SD 1.5 + model.context_length = 0; + model.supports_thinking = RAC_FALSE; + model.tags = nullptr; + model.tag_count = 0; + model.description = strdup( + "Platform's Stable Diffusion implementation using Core ML. " + "Provides text-to-image, image-to-image, and inpainting capabilities."); + model.source = RAC_MODEL_SOURCE_LOCAL; + + rac_result_t result = rac_model_registry_save(registry, &model); + if (result == RAC_SUCCESS) { + RAC_LOG_INFO(LOG_CAT, "Registered built-in model: %s", model.id); + } + + free(model.id); + free(model.name); + free(model.local_path); + free(model.description); +} + void register_foundation_models_entry() { rac_model_registry* registry = rac_get_model_registry(); if (registry == nullptr) { @@ -536,11 +921,13 @@ rac_result_t rac_backend_platform_register(void) { module_info.id = state.module_id; module_info.name = "Platform Services"; module_info.version = "1.0.0"; - module_info.description = "Apple platform services (Foundation Models, System TTS)"; + module_info.description = + "Apple platform services (Foundation Models, System TTS, CoreML Diffusion)"; - rac_capability_t capabilities[] = {RAC_CAPABILITY_TEXT_GENERATION, RAC_CAPABILITY_TTS}; + rac_capability_t capabilities[] = {RAC_CAPABILITY_TEXT_GENERATION, RAC_CAPABILITY_TTS, + RAC_CAPABILITY_DIFFUSION}; module_info.capabilities = capabilities; - module_info.num_capabilities = 2; + module_info.num_capabilities = 3; rac_result_t result = rac_module_register(&module_info); if (result != RAC_SUCCESS && result != RAC_ERROR_MODULE_ALREADY_REGISTERED) { @@ -578,9 +965,30 @@ rac_result_t rac_backend_platform_register(void) { return result; } + // Register Diffusion provider + RAC_LOG_INFO(LOG_CAT, "Registering CoreMLDiffusion provider with priority=100..."); + rac_service_provider_t diffusion_provider = {}; + diffusion_provider.name = state.provider_diffusion_name; + diffusion_provider.capability = RAC_CAPABILITY_DIFFUSION; + diffusion_provider.priority = 100; // High priority for platform provider + diffusion_provider.can_handle = platform_diffusion_can_handle; + diffusion_provider.create = platform_diffusion_create; + diffusion_provider.user_data = nullptr; + + result = rac_service_register_provider(&diffusion_provider); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR(LOG_CAT, "Failed to register CoreMLDiffusion provider: %d", result); + rac_service_unregister_provider(state.provider_tts_name, RAC_CAPABILITY_TTS); + rac_service_unregister_provider(state.provider_llm_name, RAC_CAPABILITY_TEXT_GENERATION); + rac_module_unregister(state.module_id); + return result; + } + RAC_LOG_INFO(LOG_CAT, "CoreMLDiffusion provider registered successfully"); + // Register built-in models register_foundation_models_entry(); register_system_tts_entry(); + register_coreml_diffusion_entry(); state.registered = true; RAC_LOG_INFO(LOG_CAT, "Platform backend registered successfully"); @@ -595,6 +1003,7 @@ rac_result_t rac_backend_platform_unregister(void) { return RAC_ERROR_MODULE_NOT_FOUND; } + rac_service_unregister_provider(state.provider_diffusion_name, RAC_CAPABILITY_DIFFUSION); rac_service_unregister_provider(state.provider_tts_name, RAC_CAPABILITY_TTS); rac_service_unregister_provider(state.provider_llm_name, RAC_CAPABILITY_TEXT_GENERATION); rac_module_unregister(state.module_id); diff --git a/sdk/runanywhere-commons/src/features/platform/rac_diffusion_platform.cpp b/sdk/runanywhere-commons/src/features/platform/rac_diffusion_platform.cpp new file mode 100644 index 000000000..8bdbf7e9c --- /dev/null +++ b/sdk/runanywhere-commons/src/features/platform/rac_diffusion_platform.cpp @@ -0,0 +1,191 @@ +/** + * @file rac_diffusion_platform.cpp + * @brief RunAnywhere Commons - Platform Diffusion Implementation + * + * C++ implementation of platform diffusion API. This is a thin wrapper that + * delegates all operations to Swift via registered callbacks. + */ + +#include "rac/features/platform/rac_diffusion_platform.h" + +#include +#include +#include + +#include "rac/core/rac_error.h" +#include "rac/core/rac_logger.h" + +static const char* LOG_CAT = "Platform.Diffusion"; + +// ============================================================================= +// CALLBACK STORAGE +// ============================================================================= + +namespace { + +std::mutex g_callbacks_mutex; +rac_platform_diffusion_callbacks_t g_callbacks = {}; +bool g_callbacks_set = false; + +} // namespace + +// ============================================================================= +// CALLBACK REGISTRATION +// ============================================================================= + +extern "C" { + +rac_result_t rac_platform_diffusion_set_callbacks( + const rac_platform_diffusion_callbacks_t* callbacks) { + if (callbacks == nullptr) { + return RAC_ERROR_INVALID_PARAMETER; + } + + std::lock_guard lock(g_callbacks_mutex); + g_callbacks = *callbacks; + g_callbacks_set = true; + + RAC_LOG_INFO(LOG_CAT, "Swift callbacks registered for platform diffusion"); + return RAC_SUCCESS; +} + +const rac_platform_diffusion_callbacks_t* rac_platform_diffusion_get_callbacks(void) { + std::lock_guard lock(g_callbacks_mutex); + if (!g_callbacks_set) { + return nullptr; + } + return &g_callbacks; +} + +rac_bool_t rac_platform_diffusion_is_available(void) { + std::lock_guard lock(g_callbacks_mutex); + return g_callbacks_set && g_callbacks.can_handle != nullptr && g_callbacks.create != nullptr + ? RAC_TRUE + : RAC_FALSE; +} + +// ============================================================================= +// SERVICE API +// ============================================================================= + +rac_result_t rac_diffusion_platform_create(const char* model_path, + const rac_diffusion_platform_config_t* config, + rac_diffusion_platform_handle_t* out_handle) { + if (out_handle == nullptr) { + return RAC_ERROR_INVALID_PARAMETER; + } + + *out_handle = nullptr; + + std::lock_guard lock(g_callbacks_mutex); + if (!g_callbacks_set || g_callbacks.create == nullptr) { + RAC_LOG_ERROR(LOG_CAT, "Swift callbacks not registered"); + return RAC_ERROR_NOT_INITIALIZED; + } + + RAC_LOG_DEBUG(LOG_CAT, "Creating platform diffusion via Swift"); + + rac_handle_t handle = g_callbacks.create(model_path, config, g_callbacks.user_data); + if (handle == nullptr) { + RAC_LOG_ERROR(LOG_CAT, "Swift create callback returned null"); + return RAC_ERROR_INTERNAL; + } + + *out_handle = reinterpret_cast(handle); + RAC_LOG_INFO(LOG_CAT, "Platform diffusion service created"); + return RAC_SUCCESS; +} + +void rac_diffusion_platform_destroy(rac_diffusion_platform_handle_t handle) { + if (handle == nullptr) { + return; + } + + std::lock_guard lock(g_callbacks_mutex); + if (!g_callbacks_set || g_callbacks.destroy == nullptr) { + RAC_LOG_WARNING(LOG_CAT, "Cannot destroy: Swift callbacks not registered"); + return; + } + + RAC_LOG_DEBUG(LOG_CAT, "Destroying platform diffusion via Swift"); + g_callbacks.destroy(handle, g_callbacks.user_data); +} + +rac_result_t rac_diffusion_platform_generate(rac_diffusion_platform_handle_t handle, + const rac_diffusion_platform_options_t* options, + rac_diffusion_platform_result_t* out_result) { + if (handle == nullptr || options == nullptr || out_result == nullptr) { + return RAC_ERROR_INVALID_PARAMETER; + } + + // Initialize output + memset(out_result, 0, sizeof(*out_result)); + + std::lock_guard lock(g_callbacks_mutex); + if (!g_callbacks_set || g_callbacks.generate == nullptr) { + RAC_LOG_ERROR(LOG_CAT, "Swift callbacks not registered"); + return RAC_ERROR_NOT_INITIALIZED; + } + + RAC_LOG_DEBUG(LOG_CAT, "Generating image via platform diffusion"); + return g_callbacks.generate(handle, options, out_result, g_callbacks.user_data); +} + +rac_result_t rac_diffusion_platform_generate_with_progress( + rac_diffusion_platform_handle_t handle, const rac_diffusion_platform_options_t* options, + rac_platform_diffusion_progress_fn progress_callback, void* progress_user_data, + rac_diffusion_platform_result_t* out_result) { + if (handle == nullptr || options == nullptr || out_result == nullptr) { + return RAC_ERROR_INVALID_PARAMETER; + } + + // Initialize output + memset(out_result, 0, sizeof(*out_result)); + + std::lock_guard lock(g_callbacks_mutex); + if (!g_callbacks_set) { + RAC_LOG_ERROR(LOG_CAT, "Swift callbacks not registered"); + return RAC_ERROR_NOT_INITIALIZED; + } + + // Use progress version if available, otherwise fall back to regular generate + if (g_callbacks.generate_with_progress != nullptr) { + RAC_LOG_DEBUG(LOG_CAT, "Generating image with progress via platform diffusion"); + return g_callbacks.generate_with_progress(handle, options, progress_callback, + progress_user_data, out_result, + g_callbacks.user_data); + } else if (g_callbacks.generate != nullptr) { + RAC_LOG_DEBUG(LOG_CAT, "Generating image via platform diffusion (no progress)"); + return g_callbacks.generate(handle, options, out_result, g_callbacks.user_data); + } + + return RAC_ERROR_NOT_SUPPORTED; +} + +rac_result_t rac_diffusion_platform_cancel(rac_diffusion_platform_handle_t handle) { + if (handle == nullptr) { + return RAC_ERROR_INVALID_PARAMETER; + } + + std::lock_guard lock(g_callbacks_mutex); + if (!g_callbacks_set || g_callbacks.cancel == nullptr) { + return RAC_SUCCESS; // No-op if not supported + } + + RAC_LOG_DEBUG(LOG_CAT, "Cancelling platform diffusion generation"); + return g_callbacks.cancel(handle, g_callbacks.user_data); +} + +void rac_diffusion_platform_result_free(rac_diffusion_platform_result_t* result) { + if (result == nullptr) { + return; + } + + if (result->image_data != nullptr) { + free(result->image_data); + result->image_data = nullptr; + } + result->image_size = 0; +} + +} // extern "C" diff --git a/sdk/runanywhere-commons/src/features/tts/rac_tts_service.cpp b/sdk/runanywhere-commons/src/features/tts/rac_tts_service.cpp index f0a911130..1a563ba51 100644 --- a/sdk/runanywhere-commons/src/features/tts/rac_tts_service.cpp +++ b/sdk/runanywhere-commons/src/features/tts/rac_tts_service.cpp @@ -36,13 +36,20 @@ rac_result_t rac_tts_create(const char* voice_id, rac_handle_t* out_handle) { rac_model_info_t* model_info = nullptr; rac_result_t result = rac_get_model(voice_id, &model_info); + // If not found by voice_id, try looking up by path (voice_id might be a path) + if (result != RAC_SUCCESS) { + RAC_LOG_DEBUG(LOG_CAT, "Model not found by ID, trying path lookup: %s", voice_id); + result = rac_get_model_by_path(voice_id, &model_info); + } + rac_inference_framework_t framework = RAC_FRAMEWORK_ONNX; const char* model_path = voice_id; if (result == RAC_SUCCESS && model_info) { framework = model_info->framework; model_path = model_info->local_path ? model_info->local_path : voice_id; - RAC_LOG_DEBUG(LOG_CAT, "Found model in registry, framework=%d", framework); + RAC_LOG_DEBUG(LOG_CAT, "Found model in registry: id=%s, framework=%d", + model_info->id ? model_info->id : "NULL", framework); } // Build service request diff --git a/sdk/runanywhere-commons/src/features/vlm/rac_vlm_service.cpp b/sdk/runanywhere-commons/src/features/vlm/rac_vlm_service.cpp new file mode 100644 index 000000000..8865a06a7 --- /dev/null +++ b/sdk/runanywhere-commons/src/features/vlm/rac_vlm_service.cpp @@ -0,0 +1,199 @@ +/** + * @file rac_vlm_service.cpp + * @brief VLM Service - Generic API with VTable Dispatch + * + * Simple dispatch layer that routes calls through the service vtable. + * Each backend provides its own vtable when creating a service. + * No wrappers, no switch statements - just vtable calls. + */ + +#include "rac/features/vlm/rac_vlm_service.h" + +#include +#include +#include + +#include "rac/core/rac_core.h" +#include "rac/core/rac_logger.h" +#include "rac/infrastructure/model_management/rac_model_registry.h" + +static const char* LOG_CAT = "VLM.Service"; + +// ============================================================================= +// SERVICE CREATION - Routes through Service Registry +// ============================================================================= + +extern "C" { + +rac_result_t rac_vlm_create(const char* model_id, rac_handle_t* out_handle) { + if (!model_id || !out_handle) { + return RAC_ERROR_NULL_POINTER; + } + + *out_handle = nullptr; + + RAC_LOG_INFO(LOG_CAT, "Creating VLM service for: %s", model_id); + + // Query model registry to get framework + rac_model_info_t* model_info = nullptr; + rac_result_t result = rac_get_model(model_id, &model_info); + + // If not found by model_id, try looking up by path (model_id might be a path) + if (result != RAC_SUCCESS) { + RAC_LOG_DEBUG(LOG_CAT, "Model not found by ID, trying path lookup: %s", model_id); + result = rac_get_model_by_path(model_id, &model_info); + } + + // Default to llama.cpp for VLM (has broad VLM support via mtmd) + rac_inference_framework_t framework = RAC_FRAMEWORK_LLAMACPP; + const char* model_path = model_id; + + if (result == RAC_SUCCESS && model_info) { + framework = model_info->framework; + model_path = model_info->local_path ? model_info->local_path : model_id; + RAC_LOG_INFO(LOG_CAT, "Found model in registry: id=%s, framework=%d, local_path=%s", + model_info->id ? model_info->id : "NULL", + static_cast(framework), model_path ? model_path : "NULL"); + } else { + RAC_LOG_WARNING(LOG_CAT, + "Model NOT found in registry (result=%d), using default framework=%d", + result, static_cast(framework)); + } + + // Build service request + rac_service_request_t request = {}; + request.identifier = model_id; + request.capability = RAC_CAPABILITY_VISION_LANGUAGE; + request.framework = framework; + request.model_path = model_path; + + RAC_LOG_INFO(LOG_CAT, "Service request: framework=%d, model_path=%s", + static_cast(request.framework), + request.model_path ? request.model_path : "NULL"); + + // Service registry returns an rac_vlm_service_t* with vtable already set + result = rac_service_create(RAC_CAPABILITY_VISION_LANGUAGE, &request, out_handle); + + if (model_info) { + rac_model_info_free(model_info); + } + + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR(LOG_CAT, "Failed to create service via registry: %d", result); + return result; + } + + RAC_LOG_INFO(LOG_CAT, "VLM service created"); + return RAC_SUCCESS; +} + +// ============================================================================= +// GENERIC API - Simple vtable dispatch +// ============================================================================= + +rac_result_t rac_vlm_initialize(rac_handle_t handle, const char* model_path, + const char* mmproj_path) { + if (!handle) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->initialize) { + return RAC_ERROR_NOT_SUPPORTED; + } + + return service->ops->initialize(service->impl, model_path, mmproj_path); +} + +rac_result_t rac_vlm_process(rac_handle_t handle, const rac_vlm_image_t* image, const char* prompt, + const rac_vlm_options_t* options, rac_vlm_result_t* out_result) { + if (!handle || !image || !prompt || !out_result) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->process) { + return RAC_ERROR_NOT_SUPPORTED; + } + + return service->ops->process(service->impl, image, prompt, options, out_result); +} + +rac_result_t rac_vlm_process_stream(rac_handle_t handle, const rac_vlm_image_t* image, + const char* prompt, const rac_vlm_options_t* options, + rac_vlm_stream_callback_fn callback, void* user_data) { + if (!handle || !image || !prompt || !callback) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->process_stream) { + return RAC_ERROR_NOT_SUPPORTED; + } + + return service->ops->process_stream(service->impl, image, prompt, options, callback, user_data); +} + +rac_result_t rac_vlm_get_info(rac_handle_t handle, rac_vlm_info_t* out_info) { + if (!handle || !out_info) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->get_info) { + return RAC_ERROR_NOT_SUPPORTED; + } + + return service->ops->get_info(service->impl, out_info); +} + +rac_result_t rac_vlm_cancel(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->cancel) { + return RAC_SUCCESS; // No-op if not supported + } + + return service->ops->cancel(service->impl); +} + +rac_result_t rac_vlm_cleanup(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_NULL_POINTER; + + auto* service = static_cast(handle); + if (!service->ops || !service->ops->cleanup) { + return RAC_SUCCESS; // No-op if not supported + } + + return service->ops->cleanup(service->impl); +} + +void rac_vlm_destroy(rac_handle_t handle) { + if (!handle) + return; + + auto* service = static_cast(handle); + + // Call backend destroy + if (service->ops && service->ops->destroy) { + service->ops->destroy(service->impl); + } + + // Free model_id if allocated + if (service->model_id) { + free(const_cast(service->model_id)); + } + + // Free service struct + free(service); +} + +void rac_vlm_result_free(rac_vlm_result_t* result) { + if (!result) + return; + if (result->text) { + free(result->text); + result->text = nullptr; + } +} + +} // extern "C" diff --git a/sdk/runanywhere-commons/src/features/vlm/vlm_component.cpp b/sdk/runanywhere-commons/src/features/vlm/vlm_component.cpp new file mode 100644 index 000000000..17b9c978a --- /dev/null +++ b/sdk/runanywhere-commons/src/features/vlm/vlm_component.cpp @@ -0,0 +1,542 @@ +/** + * @file vlm_component.cpp + * @brief VLM Capability Component Implementation + * + * Vision Language Model component that owns model lifecycle and generation. + * Uses lifecycle manager for unified lifecycle + analytics handling. + */ + +#include +#include +#include +#include +#include + +#include "rac/core/capabilities/rac_lifecycle.h" +#include "rac/core/rac_logger.h" +#include "rac/features/vlm/rac_vlm_component.h" +#include "rac/features/vlm/rac_vlm_service.h" + +static const char* LOG_CAT = "VLM.Component"; + +// ============================================================================= +// INTERNAL STRUCTURES +// ============================================================================= + +/** + * Internal VLM component state. + */ +struct rac_vlm_component { + /** Lifecycle manager handle */ + rac_handle_t lifecycle; + + /** Current configuration */ + rac_vlm_config_t config; + + /** Default generation options based on config */ + rac_vlm_options_t default_options; + + /** Path to vision projector (for llama.cpp backend) */ + std::string mmproj_path; + + /** Mutex for thread safety */ + std::mutex mtx; + + rac_vlm_component() : lifecycle(nullptr) { + config = RAC_VLM_CONFIG_DEFAULT; + + // Initialize default options + default_options.max_tokens = 2048; + default_options.temperature = 0.7f; + default_options.top_p = 0.9f; + default_options.stop_sequences = nullptr; + default_options.num_stop_sequences = 0; + default_options.streaming_enabled = RAC_TRUE; + default_options.system_prompt = nullptr; + default_options.max_image_size = 0; + default_options.n_threads = 0; + default_options.use_gpu = RAC_TRUE; + } +}; + +// ============================================================================= +// HELPER FUNCTIONS +// ============================================================================= + +/** + * Simple token estimation (~4 chars per token). + */ +static int32_t estimate_tokens(const char* text) { + if (!text) + return 1; + size_t len = strlen(text); + int32_t tokens = static_cast((len + 3) / 4); + return tokens > 0 ? tokens : 1; +} + +/** + * Generate a unique ID for generation tracking. + */ +static std::string generate_unique_id() { + auto now = std::chrono::high_resolution_clock::now(); + auto epoch = now.time_since_epoch(); + auto ns = std::chrono::duration_cast(epoch).count(); + char buffer[64]; + snprintf(buffer, sizeof(buffer), "vlm_gen_%lld", static_cast(ns)); + return std::string(buffer); +} + +// ============================================================================= +// LIFECYCLE CALLBACKS +// ============================================================================= + +/** + * Service creation callback for lifecycle manager. + * Creates and initializes the VLM service. + */ +static rac_result_t vlm_create_service(const char* model_id, void* user_data, + rac_handle_t* out_service) { + auto* component = reinterpret_cast(user_data); + + RAC_LOG_INFO(LOG_CAT, "Creating VLM service for model: %s", model_id ? model_id : ""); + + // Create VLM service + rac_result_t result = rac_vlm_create(model_id, out_service); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR(LOG_CAT, "Failed to create VLM service: %d", result); + return result; + } + + // Initialize with model path and mmproj path + const char* mmproj = component->mmproj_path.empty() ? nullptr : component->mmproj_path.c_str(); + result = rac_vlm_initialize(*out_service, model_id, mmproj); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR(LOG_CAT, "Failed to initialize VLM service: %d", result); + rac_vlm_destroy(*out_service); + *out_service = nullptr; + return result; + } + + RAC_LOG_INFO(LOG_CAT, "VLM service created successfully"); + return RAC_SUCCESS; +} + +/** + * Service destruction callback for lifecycle manager. + */ +static void vlm_destroy_service(rac_handle_t service, void* user_data) { + (void)user_data; + + if (service) { + RAC_LOG_DEBUG(LOG_CAT, "Destroying VLM service"); + rac_vlm_cleanup(service); + rac_vlm_destroy(service); + } +} + +// ============================================================================= +// LIFECYCLE API +// ============================================================================= + +extern "C" rac_result_t rac_vlm_component_create(rac_handle_t* out_handle) { + if (!out_handle) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + auto* component = new (std::nothrow) rac_vlm_component(); + if (!component) { + return RAC_ERROR_OUT_OF_MEMORY; + } + + // Create lifecycle manager + rac_lifecycle_config_t lifecycle_config = {}; + lifecycle_config.resource_type = RAC_RESOURCE_TYPE_VLM_MODEL; + lifecycle_config.logger_category = "VLM.Lifecycle"; + lifecycle_config.user_data = component; + + rac_result_t result = rac_lifecycle_create(&lifecycle_config, vlm_create_service, + vlm_destroy_service, &component->lifecycle); + + if (result != RAC_SUCCESS) { + delete component; + return result; + } + + *out_handle = reinterpret_cast(component); + + RAC_LOG_INFO(LOG_CAT, "VLM component created"); + + return RAC_SUCCESS; +} + +extern "C" rac_result_t rac_vlm_component_configure(rac_handle_t handle, + const rac_vlm_config_t* config) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!config) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + component->config = *config; + + // Update default options based on config + if (config->max_tokens > 0) { + component->default_options.max_tokens = config->max_tokens; + } + if (config->system_prompt) { + component->default_options.system_prompt = config->system_prompt; + } + component->default_options.temperature = config->temperature; + + RAC_LOG_INFO(LOG_CAT, "VLM component configured"); + + return RAC_SUCCESS; +} + +extern "C" rac_bool_t rac_vlm_component_is_loaded(rac_handle_t handle) { + if (!handle) + return RAC_FALSE; + + auto* component = reinterpret_cast(handle); + return rac_lifecycle_is_loaded(component->lifecycle); +} + +extern "C" const char* rac_vlm_component_get_model_id(rac_handle_t handle) { + if (!handle) + return nullptr; + + auto* component = reinterpret_cast(handle); + return rac_lifecycle_get_model_id(component->lifecycle); +} + +extern "C" void rac_vlm_component_destroy(rac_handle_t handle) { + if (!handle) + return; + + auto* component = reinterpret_cast(handle); + + // Destroy lifecycle manager (will cleanup service if loaded) + if (component->lifecycle) { + rac_lifecycle_destroy(component->lifecycle); + } + + RAC_LOG_INFO(LOG_CAT, "VLM component destroyed"); + + delete component; +} + +// ============================================================================= +// MODEL LIFECYCLE +// ============================================================================= + +extern "C" rac_result_t rac_vlm_component_load_model(rac_handle_t handle, const char* model_path, + const char* mmproj_path, const char* model_id, + const char* model_name) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!model_path) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + // Store mmproj path for service creation + component->mmproj_path = mmproj_path ? mmproj_path : ""; + + // Delegate to lifecycle manager + rac_handle_t service = nullptr; + return rac_lifecycle_load(component->lifecycle, model_path, model_id, model_name, &service); +} + +extern "C" rac_result_t rac_vlm_component_unload(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + component->mmproj_path.clear(); + return rac_lifecycle_unload(component->lifecycle); +} + +extern "C" rac_result_t rac_vlm_component_cleanup(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + component->mmproj_path.clear(); + return rac_lifecycle_reset(component->lifecycle); +} + +// ============================================================================= +// GENERATION API +// ============================================================================= + +extern "C" rac_result_t rac_vlm_component_process(rac_handle_t handle, const rac_vlm_image_t* image, + const char* prompt, + const rac_vlm_options_t* options, + rac_vlm_result_t* out_result) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!image || !prompt || !out_result) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + // Get service from lifecycle manager + rac_handle_t service = nullptr; + rac_result_t result = rac_lifecycle_require_service(component->lifecycle, &service); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR(LOG_CAT, "No model loaded - cannot process"); + return result; + } + + // Use provided options or defaults + const rac_vlm_options_t* effective_options = options ? options : &component->default_options; + + auto start_time = std::chrono::steady_clock::now(); + + // Perform VLM processing + result = rac_vlm_process(service, image, prompt, effective_options, out_result); + + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR(LOG_CAT, "VLM processing failed: %d", result); + rac_lifecycle_track_error(component->lifecycle, result, "process"); + return result; + } + + auto end_time = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time); + int64_t total_time_ms = duration.count(); + + // Update result metrics + if (out_result->prompt_tokens <= 0) { + out_result->prompt_tokens = estimate_tokens(prompt); + } + if (out_result->completion_tokens <= 0) { + out_result->completion_tokens = estimate_tokens(out_result->text); + } + out_result->total_tokens = out_result->prompt_tokens + out_result->completion_tokens; + out_result->total_time_ms = total_time_ms; + + if (total_time_ms > 0) { + out_result->tokens_per_second = static_cast(out_result->completion_tokens) / + (static_cast(total_time_ms) / 1000.0f); + } + + RAC_LOG_INFO(LOG_CAT, "VLM processing completed"); + + return RAC_SUCCESS; +} + +extern "C" rac_bool_t rac_vlm_component_supports_streaming(rac_handle_t handle) { + if (!handle) + return RAC_FALSE; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + rac_handle_t service = rac_lifecycle_get_service(component->lifecycle); + if (!service) { + return RAC_FALSE; + } + + rac_vlm_info_t info; + rac_result_t result = rac_vlm_get_info(service, &info); + if (result != RAC_SUCCESS) { + return RAC_FALSE; + } + + return info.supports_streaming; +} + +/** + * Internal structure for VLM streaming context. + */ +struct vlm_stream_context { + rac_vlm_component_token_callback_fn token_callback; + rac_vlm_component_complete_callback_fn complete_callback; + rac_vlm_component_error_callback_fn error_callback; + void* user_data; + + // Metrics tracking + std::chrono::steady_clock::time_point start_time; + std::chrono::steady_clock::time_point first_token_time; + bool first_token_recorded; + std::string full_text; + int32_t prompt_tokens; + int32_t token_count; +}; + +/** + * Internal token callback that wraps user callback and tracks metrics. + */ +static rac_bool_t vlm_stream_token_callback(const char* token, void* user_data) { + auto* ctx = reinterpret_cast(user_data); + + // Track first token time + if (!ctx->first_token_recorded) { + ctx->first_token_recorded = true; + ctx->first_token_time = std::chrono::steady_clock::now(); + } + + // Accumulate text + if (token) { + ctx->full_text += token; + ctx->token_count++; + } + + // Call user callback + if (ctx->token_callback) { + return ctx->token_callback(token, ctx->user_data); + } + + return RAC_TRUE; +} + +extern "C" rac_result_t rac_vlm_component_process_stream( + rac_handle_t handle, const rac_vlm_image_t* image, const char* prompt, + const rac_vlm_options_t* options, rac_vlm_component_token_callback_fn token_callback, + rac_vlm_component_complete_callback_fn complete_callback, + rac_vlm_component_error_callback_fn error_callback, void* user_data) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!image || !prompt) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + // Get service from lifecycle manager + rac_handle_t service = nullptr; + rac_result_t result = rac_lifecycle_require_service(component->lifecycle, &service); + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR(LOG_CAT, "No model loaded - cannot process stream"); + if (error_callback) { + error_callback(result, "No model loaded", user_data); + } + return result; + } + + // Check if streaming is supported + rac_vlm_info_t info; + result = rac_vlm_get_info(service, &info); + if (result != RAC_SUCCESS || (info.supports_streaming == 0)) { + RAC_LOG_ERROR(LOG_CAT, "Streaming not supported"); + if (error_callback) { + error_callback(RAC_ERROR_NOT_SUPPORTED, "Streaming not supported", user_data); + } + return RAC_ERROR_NOT_SUPPORTED; + } + + RAC_LOG_INFO(LOG_CAT, "Starting VLM streaming generation"); + + // Use provided options or defaults + const rac_vlm_options_t* effective_options = options ? options : &component->default_options; + + // Setup streaming context + vlm_stream_context ctx; + ctx.token_callback = token_callback; + ctx.complete_callback = complete_callback; + ctx.error_callback = error_callback; + ctx.user_data = user_data; + ctx.start_time = std::chrono::steady_clock::now(); + ctx.first_token_recorded = false; + ctx.prompt_tokens = estimate_tokens(prompt); + ctx.token_count = 0; + + // Perform streaming generation + result = rac_vlm_process_stream(service, image, prompt, effective_options, + vlm_stream_token_callback, &ctx); + + if (result != RAC_SUCCESS) { + RAC_LOG_ERROR(LOG_CAT, "VLM streaming generation failed"); + rac_lifecycle_track_error(component->lifecycle, result, "processStream"); + if (error_callback) { + error_callback(result, "Streaming generation failed", user_data); + } + return result; + } + + // Build final result for completion callback + auto end_time = std::chrono::steady_clock::now(); + auto total_duration = + std::chrono::duration_cast(end_time - ctx.start_time); + int64_t total_time_ms = total_duration.count(); + + rac_vlm_result_t final_result = {}; + final_result.text = strdup(ctx.full_text.c_str()); + final_result.prompt_tokens = ctx.prompt_tokens; + final_result.completion_tokens = estimate_tokens(ctx.full_text.c_str()); + final_result.total_tokens = final_result.prompt_tokens + final_result.completion_tokens; + final_result.total_time_ms = total_time_ms; + + // Calculate TTFT + if (ctx.first_token_recorded) { + auto ttft_duration = std::chrono::duration_cast( + ctx.first_token_time - ctx.start_time); + final_result.time_to_first_token_ms = ttft_duration.count(); + } + + // Calculate tokens per second + if (final_result.total_time_ms > 0) { + final_result.tokens_per_second = static_cast(final_result.completion_tokens) / + (static_cast(final_result.total_time_ms) / 1000.0f); + } + + if (complete_callback) { + complete_callback(&final_result, user_data); + } + + // Free the duplicated text + free(final_result.text); + + RAC_LOG_INFO(LOG_CAT, "VLM streaming generation completed"); + + return RAC_SUCCESS; +} + +extern "C" rac_result_t rac_vlm_component_cancel(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + rac_handle_t service = rac_lifecycle_get_service(component->lifecycle); + if (service) { + rac_vlm_cancel(service); + } + + RAC_LOG_INFO(LOG_CAT, "VLM generation cancellation requested"); + + return RAC_SUCCESS; +} + +// ============================================================================= +// STATE QUERY API +// ============================================================================= + +extern "C" rac_lifecycle_state_t rac_vlm_component_get_state(rac_handle_t handle) { + if (!handle) + return RAC_LIFECYCLE_STATE_IDLE; + + auto* component = reinterpret_cast(handle); + return rac_lifecycle_get_state(component->lifecycle); +} + +extern "C" rac_result_t rac_vlm_component_get_metrics(rac_handle_t handle, + rac_lifecycle_metrics_t* out_metrics) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!out_metrics) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + return rac_lifecycle_get_metrics(component->lifecycle, out_metrics); +} diff --git a/sdk/runanywhere-commons/src/infrastructure/model_management/model_assignment.cpp b/sdk/runanywhere-commons/src/infrastructure/model_management/model_assignment.cpp index a6415dd1c..ad575705a 100644 --- a/sdk/runanywhere-commons/src/infrastructure/model_management/model_assignment.cpp +++ b/sdk/runanywhere-commons/src/infrastructure/model_management/model_assignment.cpp @@ -218,6 +218,8 @@ static std::vector parse_models_json(const char* json_str, si model->format = RAC_MODEL_FORMAT_ORT; else if (format == "bin") model->format = RAC_MODEL_FORMAT_BIN; + else if (format == "coreml" || format == "mlmodelc" || format == "mlpackage") + model->format = RAC_MODEL_FORMAT_COREML; else model->format = RAC_MODEL_FORMAT_UNKNOWN; @@ -230,6 +232,12 @@ static std::vector parse_models_json(const char* json_str, si model->framework = RAC_FRAMEWORK_FOUNDATION_MODELS; else if (framework == "system_tts" || framework == "platform-tts") model->framework = RAC_FRAMEWORK_SYSTEM_TTS; + else if (framework == "coreml" || framework == "core_ml" || framework == "CoreML") + model->framework = RAC_FRAMEWORK_COREML; + else if (framework == "mlx" || framework == "MLX") + model->framework = RAC_FRAMEWORK_MLX; + else if (framework == "fluid_audio" || framework == "FluidAudio") + model->framework = RAC_FRAMEWORK_FLUID_AUDIO; else model->framework = RAC_FRAMEWORK_UNKNOWN; @@ -410,11 +418,41 @@ rac_result_t rac_model_assignment_fetch(rac_bool_t force_refresh, rac_model_info snprintf(msg, sizeof(msg), "Parsed %zu model assignments", models.size()); RAC_LOG_INFO(LOG_CAT, msg); - // Save to registry + // Save to registry - but preserve local metadata (like framework) if backend has less info rac_model_registry_handle_t registry = rac_get_model_registry(); if (registry) { for (auto* model : models) { - rac_model_registry_save(registry, model); + // Check if model already exists in registry with more specific info + rac_model_info_t* existing = nullptr; + if (rac_model_registry_get(registry, model->id, &existing) == RAC_SUCCESS && existing) { + // Preserve framework if existing has a known framework and new doesn't + if (existing->framework != RAC_FRAMEWORK_UNKNOWN && + model->framework == RAC_FRAMEWORK_UNKNOWN) { + model->framework = existing->framework; + RAC_LOG_DEBUG(LOG_CAT, "Preserved local framework for model: %s", model->id); + } + // Preserve format if existing has a known format and new doesn't + if (existing->format != RAC_MODEL_FORMAT_UNKNOWN && + model->format == RAC_MODEL_FORMAT_UNKNOWN) { + model->format = existing->format; + RAC_LOG_DEBUG(LOG_CAT, "Preserved local format for model: %s", model->id); + } + // Preserve local_path if existing has one and new doesn't + if (existing->local_path && !model->local_path) { + model->local_path = strdup(existing->local_path); + } + // Preserve artifact_info if existing has more specific type + if (existing->artifact_info.kind != RAC_ARTIFACT_KIND_SINGLE_FILE && + model->artifact_info.kind == RAC_ARTIFACT_KIND_SINGLE_FILE) { + model->artifact_info = existing->artifact_info; + // Note: This is a shallow copy โ€” existing must stay alive until + // after rac_model_registry_save deep-copies the data. + } + rac_model_registry_save(registry, model); + rac_model_info_free(existing); + } else { + rac_model_registry_save(registry, model); + } } RAC_LOG_DEBUG(LOG_CAT, "Saved models to registry"); } diff --git a/sdk/runanywhere-commons/src/infrastructure/model_management/model_paths.cpp b/sdk/runanywhere-commons/src/infrastructure/model_management/model_paths.cpp index 060a6ad08..d776ac6d6 100644 --- a/sdk/runanywhere-commons/src/infrastructure/model_management/model_paths.cpp +++ b/sdk/runanywhere-commons/src/infrastructure/model_management/model_paths.cpp @@ -101,6 +101,8 @@ const char* rac_framework_raw_value(rac_inference_framework_t framework) { return "ONNX"; case RAC_FRAMEWORK_LLAMACPP: return "LlamaCpp"; + case RAC_FRAMEWORK_COREML: + return "CoreML"; case RAC_FRAMEWORK_FOUNDATION_MODELS: return "FoundationModels"; case RAC_FRAMEWORK_SYSTEM_TTS: @@ -210,8 +212,19 @@ rac_result_t rac_model_paths_get_model_file_path(const char* model_id, return RAC_ERROR_NOT_INITIALIZED; } + const char* extension = rac_model_format_extension(format); + if (!extension) { + // Unknown format - return just the model folder path + // The caller should search for model files in this folder + RAC_LOG_WARNING("ModelPaths", "Unknown model format (%d) for model '%s', returning folder path", + static_cast(format), model_id); + std::string path = g_base_dir + "/RunAnywhere/Models/" + rac_framework_raw_value(framework) + + "/" + model_id; + return copy_string_to_buffer(path, out_path, path_size); + } + std::string path = g_base_dir + "/RunAnywhere/Models/" + rac_framework_raw_value(framework) + - "/" + model_id + "/" + model_id + "." + rac_model_format_extension(format); + "/" + model_id + "/" + model_id + "." + extension; return copy_string_to_buffer(path, out_path, path_size); } diff --git a/sdk/runanywhere-commons/src/infrastructure/model_management/model_registry.cpp b/sdk/runanywhere-commons/src/infrastructure/model_management/model_registry.cpp index 09f013f62..bd81935c3 100644 --- a/sdk/runanywhere-commons/src/infrastructure/model_management/model_registry.cpp +++ b/sdk/runanywhere-commons/src/infrastructure/model_management/model_registry.cpp @@ -204,6 +204,51 @@ rac_result_t rac_model_registry_get(rac_model_registry_handle_t handle, const ch return RAC_SUCCESS; } +rac_result_t rac_model_registry_get_by_path(rac_model_registry_handle_t handle, + const char* local_path, + rac_model_info_t** out_model) { + if (!handle || !local_path || !out_model) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + std::lock_guard lock(handle->mutex); + + // Search through all models for matching local_path + for (const auto& pair : handle->models) { + const rac_model_info_t* model = pair.second; + if (model->local_path && strcmp(model->local_path, local_path) == 0) { + *out_model = deep_copy_model(model); + if (!*out_model) { + return RAC_ERROR_OUT_OF_MEMORY; + } + RAC_LOG_DEBUG("ModelRegistry", "Found model by path: %s -> %s", local_path, model->id); + return RAC_SUCCESS; + } + } + + // Also check if the path starts with or contains the local_path + // This handles cases where the input path has extra components + std::string search_path(local_path); + for (const auto& pair : handle->models) { + const rac_model_info_t* model = pair.second; + if (model->local_path) { + std::string model_path(model->local_path); + // Check if search path starts with model's local_path + if (search_path.find(model_path) == 0 || model_path.find(search_path) == 0) { + *out_model = deep_copy_model(model); + if (!*out_model) { + return RAC_ERROR_OUT_OF_MEMORY; + } + RAC_LOG_DEBUG("ModelRegistry", "Found model by partial path match: %s -> %s", + local_path, model->id); + return RAC_SUCCESS; + } + } + } + + return RAC_ERROR_NOT_FOUND; +} + rac_result_t rac_model_registry_get_all(rac_model_registry_handle_t handle, rac_model_info_t*** out_models, size_t* out_count) { if (!handle || !out_models || !out_count) { @@ -546,10 +591,13 @@ rac_result_t rac_model_registry_discover_downloaded(rac_model_registry_handle_t return RAC_SUCCESS; } - // Frameworks to scan - rac_inference_framework_t frameworks[] = {RAC_FRAMEWORK_LLAMACPP, RAC_FRAMEWORK_ONNX, - RAC_FRAMEWORK_FOUNDATION_MODELS, - RAC_FRAMEWORK_SYSTEM_TTS}; + // Frameworks to scan - include all frameworks that can have downloaded models + // Note: RAC_FRAMEWORK_UNKNOWN is included to recover models that were incorrectly + // stored in the "Unknown" directory due to missing framework mappings + rac_inference_framework_t frameworks[] = {RAC_FRAMEWORK_LLAMACPP, RAC_FRAMEWORK_ONNX, + RAC_FRAMEWORK_COREML, RAC_FRAMEWORK_MLX, + RAC_FRAMEWORK_FLUID_AUDIO, RAC_FRAMEWORK_FOUNDATION_MODELS, + RAC_FRAMEWORK_SYSTEM_TTS, RAC_FRAMEWORK_UNKNOWN}; size_t framework_count = sizeof(frameworks) / sizeof(frameworks[0]); // Collect discovered models diff --git a/sdk/runanywhere-commons/src/infrastructure/model_management/model_types.cpp b/sdk/runanywhere-commons/src/infrastructure/model_management/model_types.cpp index 10e77db1f..a65c27f8d 100644 --- a/sdk/runanywhere-commons/src/infrastructure/model_management/model_types.cpp +++ b/sdk/runanywhere-commons/src/infrastructure/model_management/model_types.cpp @@ -165,6 +165,8 @@ rac_bool_t rac_framework_supports_format(rac_inference_framework_t framework, : RAC_FALSE; case RAC_FRAMEWORK_LLAMACPP: return (format == RAC_MODEL_FORMAT_GGUF) ? RAC_TRUE : RAC_FALSE; + case RAC_FRAMEWORK_COREML: + return (format == RAC_MODEL_FORMAT_COREML) ? RAC_TRUE : RAC_FALSE; case RAC_FRAMEWORK_FLUID_AUDIO: return (format == RAC_MODEL_FORMAT_BIN) ? RAC_TRUE : RAC_FALSE; default: @@ -176,6 +178,7 @@ rac_bool_t rac_framework_uses_directory_based_models(rac_inference_framework_t f // Mirrors Swift's InferenceFramework.usesDirectoryBasedModels switch (framework) { case RAC_FRAMEWORK_ONNX: + case RAC_FRAMEWORK_COREML: // CoreML compiled models (.mlmodelc) are directories return RAC_TRUE; default: return RAC_FALSE; @@ -222,6 +225,8 @@ const char* rac_framework_display_name(rac_inference_framework_t framework) { return "ONNX Runtime"; case RAC_FRAMEWORK_LLAMACPP: return "llama.cpp"; + case RAC_FRAMEWORK_COREML: + return "Core ML"; case RAC_FRAMEWORK_FOUNDATION_MODELS: return "Foundation Models"; case RAC_FRAMEWORK_SYSTEM_TTS: @@ -246,6 +251,8 @@ const char* rac_framework_analytics_key(rac_inference_framework_t framework) { return "onnx"; case RAC_FRAMEWORK_LLAMACPP: return "llama_cpp"; + case RAC_FRAMEWORK_COREML: + return "coreml"; case RAC_FRAMEWORK_FOUNDATION_MODELS: return "foundation_models"; case RAC_FRAMEWORK_SYSTEM_TTS: @@ -389,6 +396,8 @@ const char* rac_model_format_extension(rac_model_format_t format) { return "gguf"; case RAC_MODEL_FORMAT_BIN: return "bin"; + case RAC_MODEL_FORMAT_COREML: + return "mlmodelc"; // CoreML compiled model directory default: return nullptr; } diff --git a/sdk/runanywhere-commons/src/jni/runanywhere_commons_jni.cpp b/sdk/runanywhere-commons/src/jni/runanywhere_commons_jni.cpp index 3643d4a3a..f98861980 100644 --- a/sdk/runanywhere-commons/src/jni/runanywhere_commons_jni.cpp +++ b/sdk/runanywhere-commons/src/jni/runanywhere_commons_jni.cpp @@ -40,6 +40,7 @@ #include "rac/infrastructure/network/rac_environment.h" #include "rac/infrastructure/telemetry/rac_telemetry_manager.h" #include "rac/infrastructure/telemetry/rac_telemetry_types.h" +#include "rac/features/llm/rac_tool_calling.h" // NOTE: Backend headers are NOT included here. // Backend registration is handled by their respective JNI libraries: @@ -3269,6 +3270,198 @@ JNIEXPORT jint JNICALL Java_com_runanywhere_sdk_native_bridge_RunAnywhereBridge_ return static_cast(result); } +// ============================================================================= +// TOOL CALLING API (rac_tool_calling.h) +// Mirrors Swift SDK's CppBridge+ToolCalling.swift +// ============================================================================= + +JNIEXPORT jstring JNICALL +Java_com_runanywhere_sdk_native_bridge_RunAnywhereBridge_racToolCallParse(JNIEnv* env, jclass clazz, + jstring llmOutput) { + std::string outputStr = getCString(env, llmOutput); + rac_tool_call_t result; + + rac_result_t rc = rac_tool_call_parse(outputStr.c_str(), &result); + + // Build JSON response + std::string json = "{"; + json += "\"hasToolCall\":"; + json += (result.has_tool_call == RAC_TRUE) ? "true" : "false"; + json += ",\"cleanText\":\""; + + // Escape clean text + if (result.clean_text) { + for (const char* p = result.clean_text; *p; p++) { + switch (*p) { + case '"': json += "\\\""; break; + case '\\': json += "\\\\"; break; + case '\n': json += "\\n"; break; + case '\r': json += "\\r"; break; + case '\t': json += "\\t"; break; + default: json += *p; break; + } + } + } + json += "\""; + + if (result.has_tool_call == RAC_TRUE) { + json += ",\"toolName\":\""; + if (result.tool_name) json += result.tool_name; + json += "\",\"argumentsJson\":"; + if (result.arguments_json) { + // Validate that arguments_json is valid JSON object/array before inserting + // This prevents malformed JSON from breaking the response + std::string args(result.arguments_json); + // Trim leading whitespace + size_t start = args.find_first_not_of(" \t\n\r"); + if (start != std::string::npos && (args[start] == '{' || args[start] == '[')) { + // Appears to be valid JSON object/array - insert directly + json += args; + } else { + // Fallback: not a valid JSON object/array, use empty object + LOGe("racToolCallParse: arguments_json is not valid JSON object/array, using empty object"); + json += "{}"; + } + } else { + json += "{}"; + } + json += ",\"callId\":"; + json += std::to_string(result.call_id); + } + + json += "}"; + + rac_tool_call_free(&result); + return env->NewStringUTF(json.c_str()); +} + +JNIEXPORT jstring JNICALL +Java_com_runanywhere_sdk_native_bridge_RunAnywhereBridge_racToolCallFormatPromptJson( + JNIEnv* env, jclass clazz, jstring toolsJson) { + std::string toolsStr = getCString(env, toolsJson); + char* prompt = nullptr; + + rac_result_t rc = rac_tool_call_format_prompt_json(toolsStr.c_str(), &prompt); + + if (rc != RAC_SUCCESS || prompt == nullptr) { + return nullptr; + } + + jstring result = env->NewStringUTF(prompt); + rac_free(prompt); + return result; +} + +JNIEXPORT jstring JNICALL +Java_com_runanywhere_sdk_native_bridge_RunAnywhereBridge_racToolCallFormatPromptJsonWithFormat( + JNIEnv* env, jclass clazz, jstring toolsJson, jint format) { + std::string toolsStr = getCString(env, toolsJson); + char* prompt = nullptr; + + rac_result_t rc = rac_tool_call_format_prompt_json_with_format( + toolsStr.c_str(), + static_cast(format), + &prompt + ); + + if (rc != RAC_SUCCESS || prompt == nullptr) { + return nullptr; + } + + jstring result = env->NewStringUTF(prompt); + rac_free(prompt); + return result; +} + +JNIEXPORT jstring JNICALL +Java_com_runanywhere_sdk_native_bridge_RunAnywhereBridge_racToolCallFormatPromptJsonWithFormatName( + JNIEnv* env, jclass clazz, jstring toolsJson, jstring formatName) { + std::string toolsStr = getCString(env, toolsJson); + std::string formatStr = getCString(env, formatName); + char* prompt = nullptr; + + // Use string-based API (C++ is single source of truth for format names) + rac_result_t rc = rac_tool_call_format_prompt_json_with_format_name( + toolsStr.c_str(), + formatStr.c_str(), + &prompt + ); + + if (rc != RAC_SUCCESS || prompt == nullptr) { + return nullptr; + } + + jstring result = env->NewStringUTF(prompt); + rac_free(prompt); + return result; +} + +JNIEXPORT jstring JNICALL +Java_com_runanywhere_sdk_native_bridge_RunAnywhereBridge_racToolCallBuildInitialPrompt( + JNIEnv* env, jclass clazz, jstring userPrompt, jstring toolsJson, jstring optionsJson) { + std::string userStr = getCString(env, userPrompt); + std::string toolsStr = getCString(env, toolsJson); + + // Parse options if provided (simplified - use defaults for now) + rac_tool_calling_options_t options = {5, RAC_TRUE, 0.7f, 1024, nullptr, RAC_FALSE, RAC_FALSE}; + + char* prompt = nullptr; + rac_result_t rc = rac_tool_call_build_initial_prompt(userStr.c_str(), toolsStr.c_str(), &options, &prompt); + + if (rc != RAC_SUCCESS || prompt == nullptr) { + return nullptr; + } + + jstring result = env->NewStringUTF(prompt); + rac_free(prompt); + return result; +} + +JNIEXPORT jstring JNICALL +Java_com_runanywhere_sdk_native_bridge_RunAnywhereBridge_racToolCallBuildFollowupPrompt( + JNIEnv* env, jclass clazz, jstring originalPrompt, jstring toolsPrompt, jstring toolName, + jstring toolResultJson, jboolean keepToolsAvailable) { + std::string originalStr = getCString(env, originalPrompt); + std::string toolsPromptStr = getCString(env, toolsPrompt); + std::string toolNameStr = getCString(env, toolName); + std::string resultJsonStr = getCString(env, toolResultJson); + + char* prompt = nullptr; + rac_result_t rc = rac_tool_call_build_followup_prompt( + originalStr.c_str(), + toolsPromptStr.empty() ? nullptr : toolsPromptStr.c_str(), + toolNameStr.c_str(), + resultJsonStr.c_str(), + keepToolsAvailable ? RAC_TRUE : RAC_FALSE, + &prompt); + + if (rc != RAC_SUCCESS || prompt == nullptr) { + return nullptr; + } + + jstring result = env->NewStringUTF(prompt); + rac_free(prompt); + return result; +} + +JNIEXPORT jstring JNICALL +Java_com_runanywhere_sdk_native_bridge_RunAnywhereBridge_racToolCallNormalizeJson(JNIEnv* env, + jclass clazz, + jstring jsonStr) { + std::string inputStr = getCString(env, jsonStr); + char* normalized = nullptr; + + rac_result_t rc = rac_tool_call_normalize_json(inputStr.c_str(), &normalized); + + if (rc != RAC_SUCCESS || normalized == nullptr) { + return nullptr; + } + + jstring result = env->NewStringUTF(normalized); + rac_free(normalized); + return result; +} + } // extern "C" // ============================================================================= diff --git a/sdk/runanywhere-commons/src/utils/rac_image_utils.cpp b/sdk/runanywhere-commons/src/utils/rac_image_utils.cpp new file mode 100644 index 000000000..541a8e5e1 --- /dev/null +++ b/sdk/runanywhere-commons/src/utils/rac_image_utils.cpp @@ -0,0 +1,454 @@ +/** + * @file rac_image_utils.cpp + * @brief RunAnywhere Commons - Image Utilities Implementation + * + * Image loading and processing utilities for VLM backends. + * Uses stb_image for decoding various image formats. + */ + +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_RESIZE_IMPLEMENTATION + +#include "rac/utils/rac_image_utils.h" + +#include +#include +#include +#include +#include +#include + +#include "rac/core/rac_logger.h" + +// stb_image single-header library for image loading +// Will be included via CMake or directly +#ifdef RAC_USE_STB_IMAGE +#include "stb_image.h" +#include "stb_image_resize2.h" +#else +// Minimal fallback if stb_image is not available +// This will return an error when trying to load images +#endif + +static const char* LOG_CAT = "ImageUtils"; + +// ============================================================================= +// BASE64 DECODING +// ============================================================================= + +namespace { + +/** + * Base64 decoding table + */ +static const int base64_decode_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, + -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +/** + * Decode base64 string to bytes + */ +std::vector base64_decode(const char* data, size_t len) { + std::vector result; + if (!data || len == 0) + return result; + + // Strip data URI prefix if present (e.g., "data:image/png;base64,") + std::string input(data, len); + size_t comma_pos = input.find(','); + if (comma_pos != std::string::npos) { + input = input.substr(comma_pos + 1); + } + + // Remove whitespace + input.erase(std::remove_if(input.begin(), input.end(), ::isspace), input.end()); + + size_t input_len = input.length(); + if (input_len == 0) + return result; + + // Calculate output size + size_t out_len = (input_len / 4) * 3; + if (input_len >= 2 && input[input_len - 1] == '=') + out_len--; + if (input_len >= 2 && input[input_len - 2] == '=') + out_len--; + + result.resize(out_len); + + size_t out_idx = 0; + for (size_t i = 0; i < input_len;) { + int v1 = (i < input_len) ? base64_decode_table[(uint8_t)input[i++]] : 0; + int v2 = (i < input_len) ? base64_decode_table[(uint8_t)input[i++]] : 0; + int v3 = (i < input_len) ? base64_decode_table[(uint8_t)input[i++]] : 0; + int v4 = (i < input_len) ? base64_decode_table[(uint8_t)input[i++]] : 0; + + if (v1 < 0 || v2 < 0) + break; + + if (out_idx < out_len) { + result[out_idx++] = (v1 << 2) | (v2 >> 4); + } + if (v3 >= 0 && out_idx < out_len) { + result[out_idx++] = ((v2 & 0x0F) << 4) | (v3 >> 2); + } + if (v4 >= 0 && out_idx < out_len) { + result[out_idx++] = ((v3 & 0x03) << 6) | v4; + } + } + + result.resize(out_idx); + return result; +} + +/** + * Simple bilinear resize without stb + */ +void bilinear_resize(const uint8_t* src, int src_w, int src_h, uint8_t* dst, int dst_w, int dst_h, + int channels) { + float x_ratio = static_cast(src_w - 1) / static_cast(dst_w - 1); + float y_ratio = static_cast(src_h - 1) / static_cast(dst_h - 1); + + for (int y = 0; y < dst_h; y++) { + for (int x = 0; x < dst_w; x++) { + float src_x = x * x_ratio; + float src_y = y * y_ratio; + + int x0 = static_cast(src_x); + int y0 = static_cast(src_y); + int x1 = std::min(x0 + 1, src_w - 1); + int y1 = std::min(y0 + 1, src_h - 1); + + float x_lerp = src_x - x0; + float y_lerp = src_y - y0; + + for (int c = 0; c < channels; c++) { + float v00 = src[(y0 * src_w + x0) * channels + c]; + float v01 = src[(y0 * src_w + x1) * channels + c]; + float v10 = src[(y1 * src_w + x0) * channels + c]; + float v11 = src[(y1 * src_w + x1) * channels + c]; + + float v0 = v00 * (1 - x_lerp) + v01 * x_lerp; + float v1 = v10 * (1 - x_lerp) + v11 * x_lerp; + float v = v0 * (1 - y_lerp) + v1 * y_lerp; + + dst[(y * dst_w + x) * channels + c] = static_cast(v + 0.5f); + } + } + } +} + +} // namespace + +// ============================================================================= +// IMAGE LOADING +// ============================================================================= + +extern "C" { + +rac_result_t rac_image_load_file(const char* file_path, rac_image_data_t* out_image) { + if (!file_path || !out_image) { + return RAC_ERROR_NULL_POINTER; + } + + memset(out_image, 0, sizeof(rac_image_data_t)); + +#ifdef RAC_USE_STB_IMAGE + int width, height, channels; + uint8_t* data = stbi_load(file_path, &width, &height, &channels, 3); // Force RGB + + if (!data) { + RAC_LOG_ERROR(LOG_CAT, "Failed to load image: %s - %s", file_path, stbi_failure_reason()); + return RAC_ERROR_FILE_NOT_FOUND; + } + + out_image->pixels = data; + out_image->width = width; + out_image->height = height; + out_image->channels = 3; + out_image->size = static_cast(width) * height * 3; + + RAC_LOG_DEBUG(LOG_CAT, "Loaded image: %s (%dx%d)", file_path, width, height); + return RAC_SUCCESS; +#else + RAC_LOG_ERROR(LOG_CAT, "stb_image not available - cannot load images"); + return RAC_ERROR_NOT_SUPPORTED; +#endif +} + +rac_result_t rac_image_decode_base64(const char* base64_data, size_t data_size, + rac_image_data_t* out_image) { + if (!base64_data || !out_image) { + return RAC_ERROR_NULL_POINTER; + } + + memset(out_image, 0, sizeof(rac_image_data_t)); + + // Decode base64 + std::vector decoded = base64_decode(base64_data, data_size); + if (decoded.empty()) { + RAC_LOG_ERROR(LOG_CAT, "Failed to decode base64 data"); + return RAC_ERROR_INVALID_ARGUMENT; + } + + // Decode image from bytes + return rac_image_decode_bytes(decoded.data(), decoded.size(), out_image); +} + +rac_result_t rac_image_decode_bytes(const uint8_t* data, size_t data_size, + rac_image_data_t* out_image) { + if (!data || !out_image) { + return RAC_ERROR_NULL_POINTER; + } + + memset(out_image, 0, sizeof(rac_image_data_t)); + +#ifdef RAC_USE_STB_IMAGE + int width, height, channels; + uint8_t* pixels = + stbi_load_from_memory(data, static_cast(data_size), &width, &height, &channels, 3); + + if (!pixels) { + RAC_LOG_ERROR(LOG_CAT, "Failed to decode image from bytes: %s", stbi_failure_reason()); + return RAC_ERROR_INVALID_ARGUMENT; + } + + out_image->pixels = pixels; + out_image->width = width; + out_image->height = height; + out_image->channels = 3; + out_image->size = static_cast(width) * height * 3; + + RAC_LOG_DEBUG(LOG_CAT, "Decoded image from bytes (%dx%d)", width, height); + return RAC_SUCCESS; +#else + RAC_LOG_ERROR(LOG_CAT, "stb_image not available - cannot decode images"); + return RAC_ERROR_NOT_SUPPORTED; +#endif +} + +// ============================================================================= +// IMAGE PROCESSING +// ============================================================================= + +rac_result_t rac_image_resize(const rac_image_data_t* image, int32_t new_width, int32_t new_height, + rac_image_data_t* out_image) { + if (!image || !image->pixels || !out_image) { + return RAC_ERROR_NULL_POINTER; + } + if (new_width <= 0 || new_height <= 0) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + memset(out_image, 0, sizeof(rac_image_data_t)); + + size_t out_size = static_cast(new_width) * new_height * image->channels; + auto* out_pixels = static_cast(malloc(out_size)); + if (!out_pixels) { + return RAC_ERROR_OUT_OF_MEMORY; + } + +#ifdef RAC_USE_STB_IMAGE + stbir_resize_uint8_srgb(image->pixels, image->width, image->height, 0, out_pixels, new_width, + new_height, 0, static_cast(image->channels)); +#else + bilinear_resize(image->pixels, image->width, image->height, out_pixels, new_width, new_height, + image->channels); +#endif + + out_image->pixels = out_pixels; + out_image->width = new_width; + out_image->height = new_height; + out_image->channels = image->channels; + out_image->size = out_size; + + RAC_LOG_DEBUG(LOG_CAT, "Resized image from %dx%d to %dx%d", image->width, image->height, + new_width, new_height); + return RAC_SUCCESS; +} + +rac_result_t rac_image_resize_max(const rac_image_data_t* image, int32_t max_size, + rac_image_data_t* out_image) { + if (!image || !image->pixels || !out_image) { + return RAC_ERROR_NULL_POINTER; + } + + int32_t new_width, new_height; + rac_image_calc_resize(image->width, image->height, max_size, &new_width, &new_height); + + // If already smaller than max_size, just copy + if (new_width == image->width && new_height == image->height) { + size_t size = image->size; + auto* pixels = static_cast(malloc(size)); + if (!pixels) { + return RAC_ERROR_OUT_OF_MEMORY; + } + memcpy(pixels, image->pixels, size); + + out_image->pixels = pixels; + out_image->width = image->width; + out_image->height = image->height; + out_image->channels = image->channels; + out_image->size = size; + return RAC_SUCCESS; + } + + return rac_image_resize(image, new_width, new_height, out_image); +} + +rac_result_t rac_image_normalize(const rac_image_data_t* image, const float* mean, const float* std, + rac_image_float_t* out_float) { + if (!image || !image->pixels || !out_float) { + return RAC_ERROR_NULL_POINTER; + } + + memset(out_float, 0, sizeof(rac_image_float_t)); + + // Default mean and std (ImageNet-style normalization) + float default_mean[3] = {0.0f, 0.0f, 0.0f}; + float default_std[3] = {1.0f, 1.0f, 1.0f}; + + const float* m = mean ? mean : default_mean; + const float* s = std ? std : default_std; + + size_t count = static_cast(image->width) * image->height * image->channels; + auto* pixels = static_cast(malloc(count * sizeof(float))); + if (!pixels) { + return RAC_ERROR_OUT_OF_MEMORY; + } + + // Normalize: (pixel / 255.0 - mean) / std + for (size_t i = 0; i < count; i++) { + int channel = i % image->channels; + float val = static_cast(image->pixels[i]) / 255.0f; + pixels[i] = (val - m[channel]) / s[channel]; + } + + out_float->pixels = pixels; + out_float->width = image->width; + out_float->height = image->height; + out_float->channels = image->channels; + out_float->count = count; + + return RAC_SUCCESS; +} + +rac_result_t rac_image_to_chw(const rac_image_float_t* image, rac_image_float_t* out_chw) { + if (!image || !image->pixels || !out_chw) { + return RAC_ERROR_NULL_POINTER; + } + + memset(out_chw, 0, sizeof(rac_image_float_t)); + + size_t count = image->count; + auto* pixels = static_cast(malloc(count * sizeof(float))); + if (!pixels) { + return RAC_ERROR_OUT_OF_MEMORY; + } + + int w = image->width; + int h = image->height; + int c = image->channels; + + // Convert HWC to CHW + for (int ch = 0; ch < c; ch++) { + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int hwc_idx = (y * w + x) * c + ch; + int chw_idx = ch * h * w + y * w + x; + pixels[chw_idx] = image->pixels[hwc_idx]; + } + } + } + + out_chw->pixels = pixels; + out_chw->width = image->width; + out_chw->height = image->height; + out_chw->channels = image->channels; + out_chw->count = count; + + return RAC_SUCCESS; +} + +// ============================================================================= +// MEMORY MANAGEMENT +// ============================================================================= + +void rac_image_free(rac_image_data_t* image) { + if (!image) + return; + + if (image->pixels) { +#ifdef RAC_USE_STB_IMAGE + stbi_image_free(image->pixels); +#else + free(image->pixels); +#endif + image->pixels = nullptr; + } + + image->width = 0; + image->height = 0; + image->channels = 0; + image->size = 0; +} + +void rac_image_float_free(rac_image_float_t* image) { + if (!image) + return; + + if (image->pixels) { + free(image->pixels); + image->pixels = nullptr; + } + + image->width = 0; + image->height = 0; + image->channels = 0; + image->count = 0; +} + +// ============================================================================= +// UTILITY FUNCTIONS +// ============================================================================= + +void rac_image_calc_resize(int32_t width, int32_t height, int32_t max_size, int32_t* out_width, + int32_t* out_height) { + if (!out_width || !out_height) + return; + + if (width <= max_size && height <= max_size) { + *out_width = width; + *out_height = height; + return; + } + + float aspect = static_cast(width) / static_cast(height); + + if (width > height) { + *out_width = max_size; + *out_height = static_cast(max_size / aspect + 0.5f); + } else { + *out_height = max_size; + *out_width = static_cast(max_size * aspect + 0.5f); + } + + // Ensure minimum dimensions + if (*out_width < 1) + *out_width = 1; + if (*out_height < 1) + *out_height = 1; +} + +} // extern "C" diff --git a/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge.dart b/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge.dart index c831a7462..52bf1d761 100644 --- a/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge.dart +++ b/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge.dart @@ -24,6 +24,7 @@ import 'package:runanywhere/native/dart_bridge_state.dart'; import 'package:runanywhere/native/dart_bridge_storage.dart'; import 'package:runanywhere/native/dart_bridge_stt.dart'; import 'package:runanywhere/native/dart_bridge_telemetry.dart'; +import 'package:runanywhere/native/dart_bridge_tool_calling.dart'; import 'package:runanywhere/native/dart_bridge_tts.dart'; import 'package:runanywhere/native/dart_bridge_vad.dart'; import 'package:runanywhere/native/dart_bridge_voice_agent.dart'; diff --git a/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_platform.dart b/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_platform.dart index 147cbdaef..7e4e12823 100644 --- a/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_platform.dart +++ b/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_platform.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:io'; +import 'dart:isolate'; import 'package:ffi/ffi.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; @@ -144,8 +145,16 @@ class DartBridgePlatform { ); // Optional callbacks (handled by Dart directly) - adapter.ref.httpDownload = nullptr; - adapter.ref.httpDownloadCancel = nullptr; + adapter.ref.httpDownload = + Pointer.fromFunction( + _platformHttpDownloadCallback, + _exceptionalReturnInt32, + ).cast(); + adapter.ref.httpDownloadCancel = + Pointer.fromFunction( + _platformHttpDownloadCancelCallback, + _exceptionalReturnInt32, + ).cast(); adapter.ref.extractArchive = nullptr; adapter.ref.userData = nullptr; @@ -507,3 +516,208 @@ void _platformTrackErrorCallback( // Ignore errors in error handling } } + +// ============================================================================= +// HTTP DOWNLOAD (Platform Adapter) +// ============================================================================= + +int _httpDownloadCounter = 0; + +int _platformHttpDownloadCallback( + Pointer url, + Pointer destinationPath, + Pointer> progressCallback, + Pointer> completeCallback, + Pointer callbackUserData, + Pointer> outTaskId, + Pointer userData, +) { + try { + if (url == nullptr || destinationPath == nullptr || outTaskId == nullptr) { + return RacResultCode.errorInvalidParameter; + } + + final urlString = url.toDartString(); + final destinationString = destinationPath.toDartString(); + if (urlString.isEmpty || destinationString.isEmpty) { + return RacResultCode.errorInvalidParameter; + } + + final taskId = 'http_${_httpDownloadCounter++}'; + outTaskId.value = taskId.toNativeUtf8(); + + final progressAddress = progressCallback == nullptr ? 0 : progressCallback.address; + final completeAddress = completeCallback == nullptr ? 0 : completeCallback.address; + final userDataAddress = callbackUserData.address; + + unawaited( + Isolate.spawn( + _httpDownloadIsolateEntry, + [ + urlString, + destinationString, + progressAddress, + completeAddress, + userDataAddress, + ], + ), + ); + return RacResultCode.success; + } catch (_) { + return RacResultCode.errorDownloadFailed; + } +} + +int _platformHttpDownloadCancelCallback( + Pointer _taskId, + Pointer _userData, +) { + return RacResultCode.errorNotSupported; +} + +Future _performHttpDownloadIsolate( + String url, + String destinationPath, + void Function(int, int, Pointer)? progressCallback, + void Function(int, Pointer, Pointer)? completeCallback, + Pointer callbackUserData, +) async { + var result = RacResultCode.errorDownloadFailed; + String? finalPath; + File? tempFile; + HttpClient? client; + + try { + final uri = Uri.tryParse(url); + if (uri == null) { + result = RacResultCode.errorInvalidParameter; + return; + } + + client = HttpClient(); + final request = await client.getUrl(uri); + request.followRedirects = true; + final response = await request.close(); + + if (response.statusCode < 200 || response.statusCode >= 300) { + result = RacResultCode.errorDownloadFailed; + return; + } + + final totalBytes = response.contentLength > 0 ? response.contentLength : 0; + final destFile = File(destinationPath); + await destFile.parent.create(recursive: true); + final temp = File('${destFile.path}.part'); + tempFile = temp; + if (await temp.exists()) { + await temp.delete(); + } + + final sink = temp.openWrite(); + var downloaded = 0; + var lastReported = 0; + const reportThreshold = 256 * 1024; + + try { + await for (final chunk in response) { + sink.add(chunk); + downloaded += chunk.length; + if (progressCallback != null && + downloaded - lastReported >= reportThreshold) { + progressCallback( + downloaded, + totalBytes, + callbackUserData, + ); + lastReported = downloaded; + } + } + } finally { + await sink.flush(); + await sink.close(); + } + + if (await temp.exists()) { + if (await destFile.exists()) { + await destFile.delete(); + } + try { + await temp.rename(destFile.path); + } catch (_) { + await temp.copy(destFile.path); + await temp.delete(); + } + } + + if (progressCallback != null) { + progressCallback( + downloaded, + totalBytes, + callbackUserData, + ); + } + + finalPath = destFile.path; + result = RacResultCode.success; + } catch (_) { + result = RacResultCode.errorDownloadFailed; + } finally { + client?.close(force: true); + + if (result != RacResultCode.success && tempFile != null) { + try { + if (await tempFile!.exists()) { + await tempFile!.delete(); + } + } catch (_) { + // Ignore cleanup errors + } + } + + if (completeCallback != null) { + if (finalPath != null) { + final pathPtr = finalPath!.toNativeUtf8(); + completeCallback( + result, + pathPtr, + callbackUserData, + ); + calloc.free(pathPtr); + } else { + completeCallback( + result, + nullptr, + callbackUserData, + ); + } + } + } +} + +void _httpDownloadIsolateEntry(List args) async { + final url = args[0] as String; + final destinationPath = args[1] as String; + final progressAddress = args[2] as int; + final completeAddress = args[3] as int; + final userDataAddress = args[4] as int; + + final progressCallback = progressAddress == 0 + ? null + : Pointer>.fromAddress( + progressAddress) + .asFunction)>(); + final completeCallback = completeAddress == 0 + ? null + : Pointer>.fromAddress( + completeAddress) + .asFunction, Pointer)>(); + final userDataPtr = Pointer.fromAddress(userDataAddress); + + await _performHttpDownloadIsolate( + url, + destinationPath, + progressCallback, + completeCallback, + userDataPtr, + ); +} diff --git a/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_tool_calling.dart b/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_tool_calling.dart new file mode 100644 index 000000000..44c49faf4 --- /dev/null +++ b/sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_tool_calling.dart @@ -0,0 +1,437 @@ +/// DartBridge+ToolCalling +/// +/// Tool calling bridge - wraps C++ tool calling functions. +/// +/// *** SINGLE SOURCE OF TRUTH FOR TOOL CALLING LOGIC IS IN COMMONS C++ *** +/// +/// This is a THIN WRAPPER around rac_tool_calling.h functions. +/// NO LOCAL PARSING LOGIC - everything calls through to C++. +/// +/// Platform SDKs handle ONLY: +/// - Tool registry (Dart closures) +/// - Tool execution (Dart async calls) +library dart_bridge_tool_calling; + +import 'dart:convert'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +import 'package:runanywhere/foundation/logging/sdk_logger.dart'; +import 'package:runanywhere/native/ffi_types.dart'; +import 'package:runanywhere/native/platform_loader.dart'; + +/// Tool call parse result from C++ +class ToolCallParseResult { + final bool hasToolCall; + final String cleanText; + final String? toolName; + final Map? arguments; + final int callId; + + ToolCallParseResult({ + required this.hasToolCall, + required this.cleanText, + this.toolName, + this.arguments, + required this.callId, + }); +} + +/// Tool calling bridge for C++ interop. +/// +/// *** ALL PARSING LOGIC IS IN C++ - NO DART FALLBACKS *** +/// +/// Provides access to C++ tool calling functions: +/// - Parse tags from LLM output +/// - Format tools for system prompt +/// - Build initial and follow-up prompts +/// - Normalize JSON (fix unquoted keys) +class DartBridgeToolCalling { + // MARK: - Singleton + + /// Shared instance + static final DartBridgeToolCalling shared = DartBridgeToolCalling._(); + + DartBridgeToolCalling._(); + + // MARK: - State + + final _logger = SDKLogger('DartBridge.ToolCalling'); + DynamicLibrary? _lib; + + DynamicLibrary get lib { + _lib ??= PlatformLoader.loadCommons(); + return _lib!; + } + + // MARK: - Parse Tool Call (NO FALLBACK) + + /// Parse LLM output for tool calls using C++ implementation. + /// + /// *** THIS IS THE ONLY PARSING IMPLEMENTATION - NO DART FALLBACK *** + /// + /// Handles all edge cases: + /// - Missing closing tags (brace-matching) + /// - Unquoted JSON keys ({tool: "name"} โ†’ {"tool": "name"}) + /// - Multiple key naming conventions + /// - Tool name as key pattern + /// + /// [llmOutput] Raw LLM output text + /// Returns parsed result with tool call info + ToolCallParseResult parseToolCall(String llmOutput) { + try { + final parseFn = lib.lookupFunction< + Int32 Function(Pointer, Pointer), + int Function(Pointer, Pointer)>( + 'rac_tool_call_parse', + ); + + final freeFn = lib.lookupFunction< + Void Function(Pointer), + void Function(Pointer)>( + 'rac_tool_call_free', + ); + + final outputPtr = llmOutput.toNativeUtf8(); + final resultPtr = calloc(); + + try { + final rc = parseFn(outputPtr, resultPtr); + + if (rc != RAC_SUCCESS) { + return ToolCallParseResult( + hasToolCall: false, + cleanText: llmOutput, + callId: 0, + ); + } + + final result = resultPtr.ref; + final hasToolCall = result.hasToolCall == RAC_TRUE; + + String cleanText = llmOutput; + if (result.cleanText != nullptr) { + cleanText = result.cleanText.toDartString(); + } + + String? toolName; + Map? arguments; + int callId = 0; + + if (hasToolCall) { + if (result.toolName != nullptr) { + toolName = result.toolName.toDartString(); + } + + if (result.argumentsJson != nullptr) { + final argsJson = result.argumentsJson.toDartString(); + try { + arguments = jsonDecode(argsJson) as Map; + } catch (e) { + arguments = {}; + } + } + + callId = result.callId; + } + + freeFn(resultPtr); + + return ToolCallParseResult( + hasToolCall: hasToolCall, + cleanText: cleanText, + toolName: toolName, + arguments: arguments, + callId: callId, + ); + } finally { + calloc.free(outputPtr); + calloc.free(resultPtr); + } + } catch (e) { + _logger.error('parseToolCall failed: $e'); + return ToolCallParseResult( + hasToolCall: false, + cleanText: llmOutput, + callId: 0, + ); + } + } + + // ============================================================================= + // MARK: - Format Tools for Prompt (NO FALLBACK) + + /// Format tool definitions into a system prompt using C++ implementation + /// with a specific format. + /// + /// [toolsJson] JSON array of tool definitions + /// [formatName] Format name ("default", "lfm2") + /// Returns formatted system prompt string + String formatToolsPromptWithFormat(String toolsJson, String formatName) { + if (toolsJson.isEmpty || toolsJson == '[]') { + return ''; + } + + try { + final formatFn = lib.lookupFunction< + Int32 Function(Pointer, Pointer, Pointer>), + int Function(Pointer, Pointer, Pointer>)>( + 'rac_tool_call_format_prompt_json_with_format_name', + ); + + final racFreeFn = lib.lookupFunction), + void Function(Pointer)>('rac_free'); + + final toolsPtr = toolsJson.toNativeUtf8(); + final formatPtr = formatName.toNativeUtf8(); + final promptPtrPtr = calloc>(); + + try { + final rc = formatFn(toolsPtr, formatPtr, promptPtrPtr); + + if (rc != RAC_SUCCESS || promptPtrPtr.value == nullptr) { + _logger.error('formatToolsPromptWithFormat C++ returned error: $rc'); + return formatToolsPrompt(toolsJson); // Fallback to default + } + + final result = promptPtrPtr.value.toDartString(); + racFreeFn(promptPtrPtr.value.cast()); + return result; + } finally { + calloc.free(toolsPtr); + calloc.free(formatPtr); + calloc.free(promptPtrPtr); + } + } catch (e) { + _logger.error('formatToolsPromptWithFormat failed: $e'); + return formatToolsPrompt(toolsJson); // Fallback to default + } + } + + /// Format tool definitions into a system prompt using C++ implementation + /// (uses default format). + /// + /// [toolsJson] JSON array of tool definitions + /// Returns formatted system prompt string + String formatToolsPrompt(String toolsJson) { + if (toolsJson.isEmpty || toolsJson == '[]') { + return ''; + } + + try { + final formatFn = lib.lookupFunction< + Int32 Function(Pointer, Pointer>), + int Function(Pointer, Pointer>)>( + 'rac_tool_call_format_prompt_json', + ); + + final racFreeFn = lib.lookupFunction), + void Function(Pointer)>('rac_free'); + + final toolsPtr = toolsJson.toNativeUtf8(); + final promptPtrPtr = calloc>(); + + try { + final rc = formatFn(toolsPtr, promptPtrPtr); + + if (rc != RAC_SUCCESS || promptPtrPtr.value == nullptr) { + return ''; + } + + final result = promptPtrPtr.value.toDartString(); + racFreeFn(promptPtrPtr.value.cast()); + return result; + } finally { + calloc.free(toolsPtr); + calloc.free(promptPtrPtr); + } + } catch (e) { + _logger.error('formatToolsPrompt failed: $e'); + return ''; + } + } + + // MARK: - Build Initial Prompt (NO FALLBACK) + + /// Build initial prompt with tools and user query using C++ implementation. + /// + /// [userPrompt] The user's question/request + /// [toolsJson] JSON array of tool definitions + /// [optionsJson] Options as JSON (can be empty) + /// Returns complete formatted prompt + String buildInitialPrompt( + String userPrompt, + String toolsJson, { + String? optionsJson, + }) { + try { + final buildFn = lib.lookupFunction< + Int32 Function( + Pointer, + Pointer, + Pointer, + Pointer>, + ), + int Function( + Pointer, + Pointer, + Pointer, + Pointer>, + )>('rac_tool_call_build_initial_prompt'); + + final racFreeFn = lib.lookupFunction), + void Function(Pointer)>('rac_free'); + + final userPtr = userPrompt.toNativeUtf8(); + final toolsPtr = toolsJson.toNativeUtf8(); + final optionsPtr = calloc(); + final promptPtrPtr = calloc>(); + + // Set default options + optionsPtr.ref.maxToolCalls = 5; + optionsPtr.ref.autoExecute = RAC_TRUE; + optionsPtr.ref.temperature = 0.7; + optionsPtr.ref.maxTokens = 1024; + optionsPtr.ref.systemPrompt = nullptr; + optionsPtr.ref.replaceSystemPrompt = RAC_FALSE; + optionsPtr.ref.keepToolsAvailable = RAC_FALSE; + + try { + final rc = buildFn(userPtr, toolsPtr, optionsPtr, promptPtrPtr); + + if (rc != RAC_SUCCESS || promptPtrPtr.value == nullptr) { + return userPrompt; + } + + final result = promptPtrPtr.value.toDartString(); + racFreeFn(promptPtrPtr.value.cast()); + return result; + } finally { + calloc.free(userPtr); + calloc.free(toolsPtr); + calloc.free(optionsPtr); + calloc.free(promptPtrPtr); + } + } catch (e) { + _logger.error('buildInitialPrompt failed: $e'); + return userPrompt; + } + } + + // MARK: - Build Follow-up Prompt (NO FALLBACK) + + /// Build follow-up prompt after tool execution using C++ implementation. + /// + /// [originalPrompt] The original user prompt + /// [toolsPrompt] Formatted tools prompt (can be empty) + /// [toolName] Name of executed tool + /// [toolResultJson] Tool result as JSON + /// [keepToolsAvailable] Whether to keep tools in follow-up + /// Returns follow-up prompt string + String buildFollowupPrompt({ + required String originalPrompt, + String? toolsPrompt, + required String toolName, + required String toolResultJson, + bool keepToolsAvailable = false, + }) { + try { + final buildFn = lib.lookupFunction< + Int32 Function( + Pointer, + Pointer, + Pointer, + Pointer, + Int32, + Pointer>, + ), + int Function( + Pointer, + Pointer, + Pointer, + Pointer, + int, + Pointer>, + )>('rac_tool_call_build_followup_prompt'); + + final racFreeFn = lib.lookupFunction), + void Function(Pointer)>('rac_free'); + + final originalPtr = originalPrompt.toNativeUtf8(); + final toolsPromptPtr = + toolsPrompt != null ? toolsPrompt.toNativeUtf8() : nullptr; + final toolNamePtr = toolName.toNativeUtf8(); + final resultPtr = toolResultJson.toNativeUtf8(); + final promptPtrPtr = calloc>(); + + try { + final rc = buildFn( + originalPtr, + toolsPromptPtr, + toolNamePtr, + resultPtr, + keepToolsAvailable ? RAC_TRUE : RAC_FALSE, + promptPtrPtr, + ); + + if (rc != RAC_SUCCESS || promptPtrPtr.value == nullptr) { + return ''; + } + + final result = promptPtrPtr.value.toDartString(); + racFreeFn(promptPtrPtr.value.cast()); + return result; + } finally { + calloc.free(originalPtr); + if (toolsPromptPtr != nullptr) calloc.free(toolsPromptPtr); + calloc.free(toolNamePtr); + calloc.free(resultPtr); + calloc.free(promptPtrPtr); + } + } catch (e) { + _logger.error('buildFollowupPrompt failed: $e'); + return ''; + } + } + + // MARK: - JSON Normalization (NO FALLBACK) + + /// Normalize JSON by adding quotes around unquoted keys using C++ implementation. + /// + /// [jsonStr] Raw JSON possibly with unquoted keys + /// Returns normalized JSON string + String normalizeJson(String jsonStr) { + try { + final normalizeFn = lib.lookupFunction< + Int32 Function(Pointer, Pointer>), + int Function(Pointer, Pointer>)>( + 'rac_tool_call_normalize_json', + ); + + final racFreeFn = lib.lookupFunction), + void Function(Pointer)>('rac_free'); + + final inputPtr = jsonStr.toNativeUtf8(); + final outputPtrPtr = calloc>(); + + try { + final rc = normalizeFn(inputPtr, outputPtrPtr); + + if (rc != RAC_SUCCESS || outputPtrPtr.value == nullptr) { + return jsonStr; + } + + final result = outputPtrPtr.value.toDartString(); + racFreeFn(outputPtrPtr.value.cast()); + return result; + } finally { + calloc.free(inputPtr); + calloc.free(outputPtrPtr); + } + } catch (e) { + _logger.error('normalizeJson failed: $e'); + return jsonStr; + } + } +} diff --git a/sdk/runanywhere-flutter/packages/runanywhere/lib/native/ffi_types.dart b/sdk/runanywhere-flutter/packages/runanywhere/lib/native/ffi_types.dart index 600ec0833..35ce96fe5 100644 --- a/sdk/runanywhere-flutter/packages/runanywhere/lib/native/ffi_types.dart +++ b/sdk/runanywhere-flutter/packages/runanywhere/lib/native/ffi_types.dart @@ -825,6 +825,25 @@ typedef RacHttpCompleteCallbackNative = Void Function( Pointer callbackUserData, ); +/// HTTP download callback: rac_result_t (*http_download)(const char* url, const char* destination_path, +/// rac_http_progress_callback_fn progress_callback, rac_http_complete_callback_fn complete_callback, +/// void* callback_user_data, char** out_task_id, void* user_data) +typedef RacHttpDownloadCallbackNative = Int32 Function( + Pointer url, + Pointer destinationPath, + Pointer> progressCallback, + Pointer> completeCallback, + Pointer callbackUserData, + Pointer> outTaskId, + Pointer userData, +); + +/// HTTP download cancel callback: rac_result_t (*http_download_cancel)(const char* task_id, void* user_data) +typedef RacHttpDownloadCancelCallbackNative = Int32 Function( + Pointer taskId, + Pointer userData, +); + // ============================================================================= // Structs (using FFI Struct for native memory layout) // ============================================================================= @@ -1012,6 +1031,60 @@ base class RacVadOnnxResultStruct extends Struct { external double probability; } +// ============================================================================= +// Tool Calling FFI Types (from rac_tool_calling.h) +// ============================================================================= + +/// Parsed tool call from LLM output - matches rac_tool_call_t +base class RacToolCallStruct extends Struct { + @Int32() + external int hasToolCall; + + external Pointer toolName; + + external Pointer argumentsJson; + + external Pointer cleanText; + + @Int64() + external int callId; +} + +/// Tool calling options - matches rac_tool_calling_options_t +base class RacToolCallingOptionsStruct extends Struct { + @Int32() + external int maxToolCalls; + + @Int32() + external int autoExecute; + + @Float() + external double temperature; + + @Int32() + external int maxTokens; + + external Pointer systemPrompt; + + @Int32() + external int replaceSystemPrompt; + + @Int32() + external int keepToolsAvailable; + + @Int32() + external int format; +} + +/// Tool parameter type enum values - matches rac_tool_param_type_t +abstract class RacToolParamType { + static const int string = 0; + static const int number = 1; + static const int boolean = 2; + static const int object = 3; + static const int array = 4; +} + // ============================================================================= // Backward Compatibility Aliases // ============================================================================= diff --git a/sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere_tool_calling.dart b/sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere_tool_calling.dart new file mode 100644 index 000000000..aa68569d9 --- /dev/null +++ b/sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere_tool_calling.dart @@ -0,0 +1,407 @@ +/// Tool Calling Extension for RunAnywhere SDK +/// +/// Provides tool calling (function calling) functionality for LLMs. +/// Allows LLMs to request external actions (API calls, device functions, etc.) +/// +/// Matches Swift SDK's RunAnywhere+ToolCalling.swift +library runanywhere_tool_calling; + +import 'dart:convert'; + +import 'package:runanywhere/foundation/logging/sdk_logger.dart'; +import 'package:runanywhere/native/dart_bridge.dart'; +import 'package:runanywhere/native/dart_bridge_tool_calling.dart'; +import 'package:runanywhere/public/runanywhere.dart'; +import 'package:runanywhere/public/types/generation_types.dart'; +import 'package:runanywhere/public/types/tool_calling_types.dart'; + +/// Tool calling extension for RunAnywhere +/// +/// Provides: +/// - Tool registration and management +/// - Tool-enabled generation with automatic execution +/// - Manual tool execution support +extension RunAnywhereToolCalling on RunAnywhere { + // Private static registry - stores tool executors by name + static final Map _toolExecutors = {}; + static final Map _toolDefinitions = {}; + static final _logger = SDKLogger('RunAnywhere.ToolCalling'); + + // ============================================================================ + // MARK: - Tool Registration + // ============================================================================ + + /// Register a tool with the SDK. + /// + /// [definition] Tool definition including name, description, and parameters + /// [executor] Async function to execute when the tool is called + /// + /// Example: + /// ```dart + /// RunAnywhereTools.registerTool( + /// ToolDefinition( + /// name: 'get_weather', + /// description: 'Get current weather for a location', + /// parameters: [ + /// ToolParameter( + /// name: 'location', + /// type: ToolParameterType.string, + /// description: 'City name or coordinates', + /// ), + /// ], + /// ), + /// (args) async { + /// final location = args['location']?.stringValue ?? 'Unknown'; + /// // Call weather API... + /// return {'temperature': NumberToolValue(72), 'condition': StringToolValue('Sunny')}; + /// }, + /// ); + /// ``` + static void registerTool(ToolDefinition definition, ToolExecutor executor) { + _toolDefinitions[definition.name] = definition; + _toolExecutors[definition.name] = executor; + _logger.info('Registered tool: ${definition.name}'); + } + + /// Unregister a tool by name + static void unregisterTool(String toolName) { + _toolDefinitions.remove(toolName); + _toolExecutors.remove(toolName); + _logger.info('Unregistered tool: $toolName'); + } + + /// Get all registered tool definitions + static List getRegisteredTools() { + return List.unmodifiable(_toolDefinitions.values.toList()); + } + + /// Clear all registered tools + static void clearTools() { + _toolDefinitions.clear(); + _toolExecutors.clear(); + _logger.info('Cleared all registered tools'); + } + + // ============================================================================ + // MARK: - Tool Execution + // ============================================================================ + + /// Execute a tool call manually. + /// + /// [toolCall] The tool call to execute + /// Returns ToolResult with success/failure and result data + static Future executeTool(ToolCall toolCall) async { + final executor = _toolExecutors[toolCall.toolName]; + + if (executor == null) { + return ToolResult( + toolName: toolCall.toolName, + success: false, + error: 'Tool not found: ${toolCall.toolName}', + callId: toolCall.callId, + ); + } + + try { + _logger.debug('Executing tool: ${toolCall.toolName}'); + final result = await executor(toolCall.arguments); + _logger.debug('Tool ${toolCall.toolName} completed successfully'); + + return ToolResult( + toolName: toolCall.toolName, + success: true, + result: result, + callId: toolCall.callId, + ); + } catch (e) { + _logger.error('Tool ${toolCall.toolName} failed: $e'); + return ToolResult( + toolName: toolCall.toolName, + success: false, + error: e.toString(), + callId: toolCall.callId, + ); + } + } + + // ============================================================================ + // MARK: - Tool-Enabled Generation + // ============================================================================ + + /// Generate text with tool calling support. + /// + /// This is the main entry point for tool-enabled generation. + /// Handles the full tool calling loop: + /// 1. Format tools into system prompt + /// 2. Generate LLM response + /// 3. Parse tool calls from output + /// 4. Execute tools (if autoExecute is true) + /// 5. Continue generation with tool results + /// 6. Repeat until no more tool calls or max iterations reached + /// + /// [prompt] User's question or request + /// [options] Tool calling options (optional) + /// + /// Example: + /// ```dart + /// final result = await RunAnywhereTools.generateWithTools( + /// 'What is the weather in San Francisco?', + /// ); + /// print(result.text); // "The weather in San Francisco is 72ยฐF and Sunny." + /// print(result.toolCalls); // [ToolCall(name: 'get_weather', ...)] + /// ``` + static Future generateWithTools( + String prompt, { + ToolCallingOptions? options, + }) async { + final opts = options ?? const ToolCallingOptions(); + final tools = opts.tools ?? getRegisteredTools(); + final formatName = opts.formatName; + + if (tools.isEmpty) { + // No tools - just do regular generation + final result = await RunAnywhere.generate(prompt); + return ToolCallingResult( + text: result.text, + toolCalls: [], + toolResults: [], + isComplete: true, + ); + } + + // Build tools JSON + final toolsJson = toolsToJson(tools); + _logger.debug('Tools JSON: $toolsJson'); + _logger.debug('Using tool call format: $formatName'); + + // Build initial prompt with tools using the specified format + final toolsPrompt = DartBridgeToolCalling.shared.formatToolsPromptWithFormat( + toolsJson, + formatName, + ); + + // Build the full prompt with system instructions and user query + final formattedPrompt = '$toolsPrompt\n\nUser: $prompt'; + _logger.debug('Formatted prompt: ${formattedPrompt.substring(0, formattedPrompt.length.clamp(0, 200))}...'); + + // Track all tool calls and results + final allToolCalls = []; + final allToolResults = []; + + var currentPrompt = formattedPrompt; + var iterations = 0; + final maxIterations = opts.maxToolCalls; + + while (iterations < maxIterations) { + iterations++; + + // Lower temperature for more consistent tool calling behavior + final genOptions = LLMGenerationOptions( + maxTokens: opts.maxTokens ?? 1024, + temperature: opts.temperature ?? 0.3, + ); + + // Use streaming like Swift does, then collect all tokens + final streamResult = await RunAnywhere.generateStream(currentPrompt, options: genOptions); + final buffer = StringBuffer(); + await for (final token in streamResult.stream) { + buffer.write(token); + } + final responseText = buffer.toString(); + + _logger.debug('LLM output (iter $iterations): ${responseText.substring(0, responseText.length.clamp(0, 200))}...'); + + // Parse for tool calls using C++ bridge (auto-detection like Swift) + final parseResult = DartBridgeToolCalling.shared.parseToolCall(responseText); + + if (!parseResult.hasToolCall || parseResult.toolName == null) { + // No tool call - return final result + return ToolCallingResult( + text: parseResult.cleanText, + toolCalls: allToolCalls, + toolResults: allToolResults, + isComplete: true, + ); + } + + // Create tool call + final toolCall = ToolCall( + toolName: parseResult.toolName!, + arguments: parseResult.arguments != null + ? dynamicMapToToolValueMap(parseResult.arguments!) + : {}, + callId: parseResult.callId.toString(), + ); + allToolCalls.add(toolCall); + + _logger.info('Tool call detected: ${toolCall.toolName}'); + + if (!opts.autoExecute) { + // Return for manual execution + return ToolCallingResult( + text: parseResult.cleanText, + toolCalls: allToolCalls, + toolResults: allToolResults, + isComplete: false, + ); + } + + // Execute the tool + final toolResult = await executeTool(toolCall); + allToolResults.add(toolResult); + + // Build follow-up prompt with tool result + final resultJson = toolResult.result != null + ? toolResultToJsonString(toolResult.result!) + : '{"error": "${toolResult.error ?? 'Unknown error'}"}'; + + currentPrompt = DartBridgeToolCalling.shared.buildFollowupPrompt( + originalPrompt: prompt, + toolsPrompt: opts.keepToolsAvailable + ? DartBridgeToolCalling.shared.formatToolsPrompt(toolsJson) + : null, + toolName: toolCall.toolName, + toolResultJson: resultJson, + keepToolsAvailable: opts.keepToolsAvailable, + ); + + _logger.debug('Follow-up prompt: ${currentPrompt.substring(0, currentPrompt.length.clamp(0, 200))}...'); + } + + // Max iterations reached - return what we have + _logger.warning('Max tool call iterations ($maxIterations) reached'); + return ToolCallingResult( + text: '', + toolCalls: allToolCalls, + toolResults: allToolResults, + isComplete: true, + ); + } + + /// Continue generation after manual tool execution. + /// + /// Use this when autoExecute is false and you've executed tools manually. + /// + /// [originalPrompt] The original user prompt + /// [toolResult] Result from manual tool execution + /// [options] Tool calling options + static Future continueWithToolResult( + String originalPrompt, + ToolResult toolResult, { + ToolCallingOptions? options, + }) async { + final opts = options ?? const ToolCallingOptions(); + final tools = opts.tools ?? getRegisteredTools(); + final toolsJson = toolsToJson(tools); + + final resultJson = toolResult.result != null + ? toolResultToJsonString(toolResult.result!) + : '{"error": "${toolResult.error ?? 'Unknown error'}"}'; + + final followupPrompt = DartBridgeToolCalling.shared.buildFollowupPrompt( + originalPrompt: originalPrompt, + toolsPrompt: opts.keepToolsAvailable + ? DartBridgeToolCalling.shared.formatToolsPrompt(toolsJson) + : null, + toolName: toolResult.toolName, + toolResultJson: resultJson, + keepToolsAvailable: opts.keepToolsAvailable, + ); + + // Continue with the follow-up + return generateWithTools(followupPrompt, options: opts); + } + + // ============================================================================ + // MARK: - Helper Functions + // ============================================================================ + + /// Format tools for system prompt. + /// + /// Useful for inspecting or customizing tool prompts. + static String formatToolsForPrompt([List? tools]) { + final toolList = tools ?? getRegisteredTools(); + if (toolList.isEmpty) return ''; + + final toolsJson = toolsToJson(toolList); + return DartBridgeToolCalling.shared.formatToolsPrompt(toolsJson); + } + + /// Parse tool call from LLM output. + /// + /// Useful for manual parsing without automatic execution. + static ToolCall? parseToolCall(String llmOutput) { + final result = DartBridgeToolCalling.shared.parseToolCall(llmOutput); + + if (!result.hasToolCall || result.toolName == null) { + return null; + } + + return ToolCall( + toolName: result.toolName!, + arguments: result.arguments != null + ? dynamicMapToToolValueMap(result.arguments!) + : {}, + callId: result.callId.toString(), + ); + } +} + +/// Convenience class for tool calling without extension syntax +/// +/// Use this for simpler imports: +/// ```dart +/// import 'package:runanywhere/public/runanywhere_tool_calling.dart'; +/// +/// RunAnywhereTools.registerTool(...); +/// final result = await RunAnywhereTools.generateWithTools('...'); +/// ``` +class RunAnywhereTools { + RunAnywhereTools._(); + + /// Register a tool with the SDK + static void registerTool(ToolDefinition definition, ToolExecutor executor) => + RunAnywhereToolCalling.registerTool(definition, executor); + + /// Unregister a tool by name + static void unregisterTool(String toolName) => + RunAnywhereToolCalling.unregisterTool(toolName); + + /// Get all registered tool definitions + static List getRegisteredTools() => + RunAnywhereToolCalling.getRegisteredTools(); + + /// Clear all registered tools + static void clearTools() => RunAnywhereToolCalling.clearTools(); + + /// Execute a tool call manually + static Future executeTool(ToolCall toolCall) => + RunAnywhereToolCalling.executeTool(toolCall); + + /// Generate text with tool calling support + static Future generateWithTools( + String prompt, { + ToolCallingOptions? options, + }) => + RunAnywhereToolCalling.generateWithTools(prompt, options: options); + + /// Continue generation after manual tool execution + static Future continueWithToolResult( + String originalPrompt, + ToolResult toolResult, { + ToolCallingOptions? options, + }) => + RunAnywhereToolCalling.continueWithToolResult( + originalPrompt, + toolResult, + options: options, + ); + + /// Format tools for system prompt + static String formatToolsForPrompt([List? tools]) => + RunAnywhereToolCalling.formatToolsForPrompt(tools); + + /// Parse tool call from LLM output + static ToolCall? parseToolCall(String llmOutput) => + RunAnywhereToolCalling.parseToolCall(llmOutput); +} diff --git a/sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/tool_calling_types.dart b/sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/tool_calling_types.dart new file mode 100644 index 000000000..5f4c3fbcd --- /dev/null +++ b/sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/tool_calling_types.dart @@ -0,0 +1,365 @@ +/// Tool Calling Types for RunAnywhere SDK +/// +/// Type definitions for tool calling (function calling) functionality. +/// Allows LLMs to request external actions (API calls, device functions, etc.) +/// +/// Mirrors Swift SDK's ToolCallingTypes.swift +library tool_calling_types; + +import 'dart:convert'; + +// ============================================================================= +// TOOL CALL FORMAT NAMES +// ============================================================================= + +/// Constants for tool call format names. +/// +/// The format logic is handled in C++ commons (single source of truth). +/// Mirrors Swift SDK's ToolCallFormatName enum. +abstract class ToolCallFormatName { + /// JSON format: `{"tool":"name","arguments":{...}}` + /// Use for most general-purpose models (Llama, Qwen, Mistral, etc.) + static const String defaultFormat = 'default'; + + /// Liquid AI format: `<|tool_call_start|>[func(args)]<|tool_call_end|>` + /// Use for LFM2-Tool models + static const String lfm2 = 'lfm2'; +} + +// ============================================================================= +// TOOL VALUE - Type-safe JSON representation +// ============================================================================= + +/// A type-safe representation of JSON values for tool arguments and results. +/// Avoids using `dynamic` while supporting all JSON types. +sealed class ToolValue { + const ToolValue(); + + // Convenience value extraction + String? get stringValue => this is StringToolValue ? (this as StringToolValue).value : null; + double? get numberValue => this is NumberToolValue ? (this as NumberToolValue).value : null; + int? get intValue => numberValue?.toInt(); + bool? get boolValue => this is BoolToolValue ? (this as BoolToolValue).value : null; + List? get arrayValue => + this is ArrayToolValue ? (this as ArrayToolValue).value : null; + Map? get objectValue => + this is ObjectToolValue ? (this as ObjectToolValue).value : null; + bool get isNull => this is NullToolValue; + + /// Convert to JSON-compatible dynamic value + dynamic toJson() => switch (this) { + StringToolValue(value: var v) => v, + NumberToolValue(value: var v) => v, + BoolToolValue(value: var v) => v, + ArrayToolValue(value: var v) => v.map((e) => e.toJson()).toList(), + ObjectToolValue(value: var v) => v.map((k, val) => MapEntry(k, val.toJson())), + NullToolValue() => null, + }; + + /// Create from any JSON-compatible value + static ToolValue from(dynamic value) => switch (value) { + null => const NullToolValue(), + String s => StringToolValue(s), + num n => NumberToolValue(n.toDouble()), + bool b => BoolToolValue(b), + List l => ArrayToolValue(l.map(from).toList()), + Map m => ObjectToolValue(m.map((k, v) => MapEntry(k.toString(), from(v)))), + _ => StringToolValue(value.toString()), + }; +} + +class StringToolValue extends ToolValue { + final String value; + const StringToolValue(this.value); +} + +class NumberToolValue extends ToolValue { + final double value; + const NumberToolValue(this.value); +} + +class BoolToolValue extends ToolValue { + final bool value; + const BoolToolValue(this.value); +} + +class ArrayToolValue extends ToolValue { + final List value; + const ArrayToolValue(this.value); +} + +class ObjectToolValue extends ToolValue { + final Map value; + const ObjectToolValue(this.value); +} + +class NullToolValue extends ToolValue { + const NullToolValue(); +} + +// ============================================================================= +// PARAMETER TYPES +// ============================================================================= + +/// Supported parameter types for tool arguments +enum ToolParameterType { + string('string'), + number('number'), + boolean('boolean'), + object('object'), + array('array'); + + final String value; + const ToolParameterType(this.value); + + static ToolParameterType fromString(String value) => switch (value.toLowerCase()) { + 'string' => ToolParameterType.string, + 'number' => ToolParameterType.number, + 'boolean' => ToolParameterType.boolean, + 'object' => ToolParameterType.object, + 'array' => ToolParameterType.array, + _ => ToolParameterType.string, + }; +} + +/// A single parameter definition for a tool +class ToolParameter { + /// Parameter name + final String name; + + /// Data type of the parameter + final ToolParameterType type; + + /// Human-readable description + final String description; + + /// Whether this parameter is required + final bool required; + + /// Allowed values (for enum-like parameters) + final List? enumValues; + + const ToolParameter({ + required this.name, + required this.type, + required this.description, + this.required = true, + this.enumValues, + }); + + Map toJson() => { + 'name': name, + 'type': type.value, + 'description': description, + 'required': required, + if (enumValues != null) 'enumValues': enumValues, + }; +} + +// ============================================================================= +// TOOL DEFINITION TYPES +// ============================================================================= + +/// Definition of a tool that the LLM can use +class ToolDefinition { + /// Unique name of the tool (e.g., "get_weather") + final String name; + + /// Human-readable description of what the tool does + final String description; + + /// Parameters the tool accepts + final List parameters; + + /// Category for organizing tools (optional) + final String? category; + + const ToolDefinition({ + required this.name, + required this.description, + required this.parameters, + this.category, + }); + + Map toJson() => { + 'name': name, + 'description': description, + 'parameters': parameters.map((p) => p.toJson()).toList(), + if (category != null) 'category': category, + }; +} + +// ============================================================================= +// TOOL CALL TYPES (LLM requesting to use a tool) +// ============================================================================= + +/// A request from the LLM to execute a tool +class ToolCall { + /// Name of the tool to execute + final String toolName; + + /// Arguments to pass to the tool + final Map arguments; + + /// Unique ID for this tool call (for tracking) + final String? callId; + + const ToolCall({ + required this.toolName, + required this.arguments, + this.callId, + }); + + /// Get a string argument by name + String? getString(String key) => arguments[key]?.stringValue; + + /// Get a number argument by name + double? getNumber(String key) => arguments[key]?.numberValue; + + /// Get a bool argument by name + bool? getBool(String key) => arguments[key]?.boolValue; +} + +// ============================================================================= +// TOOL RESULT TYPES (Result after execution) +// ============================================================================= + +/// Result of executing a tool +class ToolResult { + /// Name of the tool that was executed + final String toolName; + + /// Whether execution was successful + final bool success; + + /// Result data (if successful) + final Map? result; + + /// Error message (if failed) + final String? error; + + /// The original call ID (for tracking) + final String? callId; + + const ToolResult({ + required this.toolName, + required this.success, + this.result, + this.error, + this.callId, + }); + + Map toJson() => { + 'toolName': toolName, + 'success': success, + if (result != null) 'result': result!.map((k, v) => MapEntry(k, v.toJson())), + if (error != null) 'error': error, + if (callId != null) 'callId': callId, + }; +} + +// ============================================================================= +// TOOL EXECUTOR TYPES +// ============================================================================= + +/// Function type for tool executors. +/// Takes arguments as strongly-typed ToolValue map, returns result map. +typedef ToolExecutor = Future> Function(Map args); + +// ============================================================================= +// TOOL CALLING OPTIONS +// ============================================================================= + +/// Options for tool-enabled generation +class ToolCallingOptions { + /// Available tools for this generation (if not provided, uses registered tools) + final List? tools; + + /// Maximum number of tool calls allowed in one conversation turn (default: 5) + final int maxToolCalls; + + /// Whether to automatically execute tools or return them for manual execution (default: true) + final bool autoExecute; + + /// Temperature for generation + final double? temperature; + + /// Maximum tokens to generate + final int? maxTokens; + + /// System prompt to use (will be merged with tool instructions by default) + final String? systemPrompt; + + /// If true, replaces the system prompt entirely instead of appending tool instructions + final bool replaceSystemPrompt; + + /// If true, keeps tool definitions available after the first tool call + final bool keepToolsAvailable; + + /// Tool calling format name (e.g., "default", "lfm2") + /// Different models are trained on different tool calling formats. + /// - "default": Standard JSON format for general-purpose models + /// - "lfm2": Pythonic format for LFM2-Tool models + final String formatName; + + const ToolCallingOptions({ + this.tools, + this.maxToolCalls = 5, + this.autoExecute = true, + this.temperature, + this.maxTokens, + this.systemPrompt, + this.replaceSystemPrompt = false, + this.keepToolsAvailable = false, + this.formatName = ToolCallFormatName.defaultFormat, + }); +} + +// ============================================================================= +// TOOL CALLING RESULT TYPES +// ============================================================================= + +/// Result of a generation that may include tool calls +class ToolCallingResult { + /// The final text response + final String text; + + /// Any tool calls the LLM made + final List toolCalls; + + /// Results of executed tools (if autoExecute was true) + final List toolResults; + + /// Whether the response is complete or waiting for tool results + final bool isComplete; + + /// Conversation ID for continuing with tool results + final String? conversationId; + + const ToolCallingResult({ + required this.text, + required this.toolCalls, + required this.toolResults, + required this.isComplete, + this.conversationId, + }); +} + +// ============================================================================= +// HELPER FUNCTIONS +// ============================================================================= + +/// Serialize tools to JSON string +String toolsToJson(List tools) { + return jsonEncode(tools.map((t) => t.toJson()).toList()); +} + +/// Convert Map to JSON string +String toolResultToJsonString(Map result) { + return jsonEncode(result.map((k, v) => MapEntry(k, v.toJson()))); +} + +/// Convert Map to Map +Map dynamicMapToToolValueMap(Map map) { + return map.map((k, v) => MapEntry(k, ToolValue.from(v))); +} diff --git a/sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/types.dart b/sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/types.dart index 4449a9eac..1111ad66c 100644 --- a/sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/types.dart +++ b/sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/types.dart @@ -8,4 +8,5 @@ export 'configuration_types.dart'; export 'download_types.dart'; export 'generation_types.dart'; export 'message_types.dart'; +export 'tool_calling_types.dart'; export 'voice_agent_types.dart'; diff --git a/sdk/runanywhere-flutter/packages/runanywhere/lib/runanywhere.dart b/sdk/runanywhere-flutter/packages/runanywhere/lib/runanywhere.dart index 56393008f..594990b6d 100644 --- a/sdk/runanywhere-flutter/packages/runanywhere/lib/runanywhere.dart +++ b/sdk/runanywhere-flutter/packages/runanywhere/lib/runanywhere.dart @@ -28,4 +28,6 @@ export 'public/extensions/runanywhere_frameworks.dart'; export 'public/extensions/runanywhere_logging.dart'; export 'public/extensions/runanywhere_storage.dart'; export 'public/runanywhere.dart'; +export 'public/runanywhere_tool_calling.dart'; +export 'public/types/tool_calling_types.dart'; export 'public/types/types.dart' hide SupabaseConfig; diff --git a/sdk/runanywhere-kotlin/docs/Documentation.md b/sdk/runanywhere-kotlin/docs/Documentation.md index 8cbdc6a90..aa2177fd7 100644 --- a/sdk/runanywhere-kotlin/docs/Documentation.md +++ b/sdk/runanywhere-kotlin/docs/Documentation.md @@ -1384,7 +1384,6 @@ enum class ModelCategory { SPEECH_RECOGNITION, // STT (voice-to-text) SPEECH_SYNTHESIS, // TTS (text-to-voice) VISION, // Image understanding - IMAGE_GENERATION, // Text-to-image MULTIMODAL, // Multiple modalities AUDIO // Audio processing } diff --git a/sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts b/sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts index e7f8312b0..2d1298ed9 100644 --- a/sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts +++ b/sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts @@ -163,6 +163,14 @@ android { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } + // Prevent duplicate native library conflicts during test builds + jniLibs { + pickFirsts += setOf( + "lib/arm64-v8a/libomp.so", + "lib/arm64-v8a/libc++_shared.so", + "lib/arm64-v8a/librac_commons.so" + ) + } } // ========================================================================== diff --git a/sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts b/sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts index b26dca4dd..897af2861 100644 --- a/sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts +++ b/sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts @@ -172,6 +172,14 @@ android { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } + // Prevent duplicate native library conflicts during test builds + jniLibs { + pickFirsts += setOf( + "lib/arm64-v8a/libomp.so", + "lib/arm64-v8a/libc++_shared.so", + "lib/arm64-v8a/librac_commons.so" + ) + } } // ========================================================================== diff --git a/sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/src/androidMain/kotlin/com/runanywhere/sdk/core/onnx/ONNXAndroidInit.kt b/sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/src/androidMain/kotlin/com/runanywhere/sdk/core/onnx/ONNXAndroidInit.kt new file mode 100644 index 000000000..3be887e9f --- /dev/null +++ b/sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/src/androidMain/kotlin/com/runanywhere/sdk/core/onnx/ONNXAndroidInit.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2026 RunAnywhere SDK + * SPDX-License-Identifier: Apache-2.0 + * + * Android-specific initialization for ONNX module. + */ + +package com.runanywhere.sdk.core.onnx + +import android.content.Context +import android.util.Log +import java.lang.ref.WeakReference + +/** + * Android-specific initialization for ONNX module. + * + * Usage: + * ```kotlin + * AndroidPlatformContext.initialize(this) + * ONNXAndroid.initialize(this) + * ONNX.register() + * ``` + */ +object ONNXAndroid { + private const val TAG = "ONNXAndroid" + + @Volatile + private var contextRef: WeakReference? = null + + @Volatile + private var isInitialized = false + + /** + * Initialize the ONNX Android module. + * + * @param context Application context + */ + @JvmStatic + fun initialize(context: Context) { + if (isInitialized) { + Log.d(TAG, "ONNXAndroid already initialized") + return + } + + Log.i(TAG, "Initializing ONNX Android module") + + contextRef = WeakReference(context.applicationContext) + isInitialized = true + Log.i(TAG, "ONNX Android module initialized") + } + + @JvmStatic + fun getContext(): Context? = contextRef?.get() + + @JvmStatic + fun isInitialized(): Boolean = isInitialized +} diff --git a/sdk/runanywhere-kotlin/scripts/build-kotlin.sh b/sdk/runanywhere-kotlin/scripts/build-kotlin.sh index 7059cdc2b..7b6ca5e0e 100755 --- a/sdk/runanywhere-kotlin/scripts/build-kotlin.sh +++ b/sdk/runanywhere-kotlin/scripts/build-kotlin.sh @@ -288,6 +288,26 @@ copy_jni_libs() { local COMMONS_BUILD="${COMMONS_DIR}/build/android/unified" local SHERPA_ONNX_LIBS="${COMMONS_DIR}/third_party/sherpa-onnx-android/jniLibs" + # ========================================================================= + # Safety check: Remove stale .so from example apps. + # Example apps get native libraries transitively via SDK module dependencies. + # Having .so directly in app/src/main/jniLibs/ overrides the SDK module + # versions and can cause ABI mismatch crashes (e.g., SIGSEGV in Sherpa-ONNX). + # ========================================================================= + local EXAMPLE_APPS_DIR="${SDK_ROOT}/../examples/android" + if [ -d "${EXAMPLE_APPS_DIR}" ]; then + for app_jnilibs in "${EXAMPLE_APPS_DIR}"/*/app/src/main/jniLibs; do + if [ -d "$app_jnilibs" ]; then + local stale_count=$(find "$app_jnilibs" -name "*.so" 2>/dev/null | wc -l | tr -d ' ') + if [ "$stale_count" -gt 0 ]; then + log_warn "Found ${stale_count} stale .so in $(basename $(dirname $(dirname $(dirname $(dirname "$app_jnilibs")))))/app/src/main/jniLibs/" + log_warn "Removing to prevent ABI mismatch โ€” app gets native libs from SDK modules" + rm -rf "$app_jnilibs" + fi + fi + done + fi + # Clean output directories if [ "$CLEAN_BUILD" = true ]; then log_step "Cleaning JNI directories..." @@ -352,7 +372,7 @@ copy_jni_libs() { fi # ======================================================================= - # LlamaCPP Module: Backend + JNI bridge + # LlamaCPP Module: Backend + JNI bridge + shared dependencies # ======================================================================= # Copy backend library if [ -f "${COMMONS_DIST}/llamacpp/${ABI}/librac_backend_llamacpp.so" ]; then @@ -372,6 +392,15 @@ copy_jni_libs() { log_info "LlamaCPP: librac_backend_llamacpp_jni.so (from build)" fi + # Copy shared dependencies (must stay in sync with main SDK) + # These are duplicated in each module so they work as standalone AARs. + # Gradle pickFirsts resolves duplicates at APK merge time. + for shared_lib in librac_commons.so libc++_shared.so libomp.so; do + if [ -f "${MAIN_JNILIBS_DIR}/${ABI}/${shared_lib}" ]; then + cp "${MAIN_JNILIBS_DIR}/${ABI}/${shared_lib}" "${LLAMACPP_JNILIBS_DIR}/${ABI}/" + fi + done + # ======================================================================= # ONNX Module: ONNX Runtime + Sherpa-ONNX + JNI bridge # ======================================================================= @@ -409,6 +438,13 @@ copy_jni_libs() { fi done fi + + # Copy shared dependencies (must stay in sync with main SDK) + for shared_lib in librac_commons.so libc++_shared.so libomp.so; do + if [ -f "${MAIN_JNILIBS_DIR}/${ABI}/${shared_lib}" ]; then + cp "${MAIN_JNILIBS_DIR}/${ABI}/${shared_lib}" "${ONNX_JNILIBS_DIR}/${ABI}/" + fi + done done log_info "JNI libraries installed" diff --git a/sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/LLM/ToolCallingTypes.kt b/sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/LLM/ToolCallingTypes.kt new file mode 100644 index 000000000..5d123c58f --- /dev/null +++ b/sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/LLM/ToolCallingTypes.kt @@ -0,0 +1,301 @@ +/* + * Copyright 2026 RunAnywhere SDK + * SPDX-License-Identifier: Apache-2.0 + * + * Type definitions for Tool Calling functionality. + * Allows LLMs to request external actions (API calls, device functions, etc.) + * + * Mirrors Swift SDK's ToolCallingTypes.swift + */ + +package com.runanywhere.sdk.public.extensions.LLM + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement + +// ============================================================================= +// TOOL VALUE - Type-safe JSON representation +// ============================================================================= + +/** + * A type-safe representation of JSON values for tool arguments and results. + * Avoids using `Any` while supporting all JSON types. + */ +@Serializable +sealed class ToolValue { + @Serializable + data class StringValue(val value: String) : ToolValue() + + @Serializable + data class NumberValue(val value: Double) : ToolValue() + + @Serializable + data class BoolValue(val value: Boolean) : ToolValue() + + @Serializable + data class ArrayValue(val value: List) : ToolValue() + + @Serializable + data class ObjectValue(val value: Map) : ToolValue() + + @Serializable + object NullValue : ToolValue() + + // Convenience value extraction + val stringValue: String? get() = (this as? StringValue)?.value + val numberValue: Double? get() = (this as? NumberValue)?.value + val intValue: Int? get() = numberValue?.toInt() + val boolValue: Boolean? get() = (this as? BoolValue)?.value + val arrayValue: List? get() = (this as? ArrayValue)?.value + val objectValue: Map? get() = (this as? ObjectValue)?.value + val isNull: Boolean get() = this is NullValue + + companion object { + fun string(value: String) = StringValue(value) + fun number(value: Double) = NumberValue(value) + fun number(value: Int) = NumberValue(value.toDouble()) + fun bool(value: Boolean) = BoolValue(value) + fun array(value: List) = ArrayValue(value) + fun obj(value: Map) = ObjectValue(value) + fun nullValue() = NullValue + + /** + * Convert Any to ToolValue + */ + fun from(value: Any?): ToolValue = when (value) { + null -> NullValue + is String -> StringValue(value) + is Number -> NumberValue(value.toDouble()) + is Boolean -> BoolValue(value) + is List<*> -> ArrayValue(value.map { from(it) }) + is Map<*, *> -> ObjectValue(value.entries.associate { (k, v) -> + k.toString() to from(v) + }) + else -> StringValue(value.toString()) + } + } +} + +// ============================================================================= +// PARAMETER TYPES +// ============================================================================= + +/** + * Supported parameter types for tool arguments + */ +enum class ToolParameterType(val value: String) { + STRING("string"), + NUMBER("number"), + BOOLEAN("boolean"), + OBJECT("object"), + ARRAY("array"); + + companion object { + fun fromString(value: String): ToolParameterType = when (value.lowercase()) { + "string" -> STRING + "number" -> NUMBER + "boolean" -> BOOLEAN + "object" -> OBJECT + "array" -> ARRAY + else -> STRING + } + } +} + +/** + * A single parameter definition for a tool + */ +data class ToolParameter( + /** Parameter name */ + val name: String, + /** Data type of the parameter */ + val type: ToolParameterType, + /** Human-readable description */ + val description: String, + /** Whether this parameter is required */ + val required: Boolean = true, + /** Allowed values (for enum-like parameters) */ + val enumValues: List? = null +) + +// ============================================================================= +// TOOL DEFINITION TYPES +// ============================================================================= + +/** + * Definition of a tool that the LLM can use + */ +data class ToolDefinition( + /** Unique name of the tool (e.g., "get_weather") */ + val name: String, + /** Human-readable description of what the tool does */ + val description: String, + /** Parameters the tool accepts */ + val parameters: List, + /** Category for organizing tools (optional) */ + val category: String? = null +) + +// ============================================================================= +// TOOL CALL TYPES (LLM requesting to use a tool) +// ============================================================================= + +/** + * A request from the LLM to execute a tool + */ +data class ToolCall( + /** Name of the tool to execute */ + val toolName: String, + /** Arguments to pass to the tool */ + val arguments: Map, + /** Unique ID for this tool call (for tracking) */ + val callId: String? = null +) { + /** Get a string argument by name */ + fun getString(key: String): String? = arguments[key]?.stringValue + + /** Get a number argument by name */ + fun getNumber(key: String): Double? = arguments[key]?.numberValue + + /** Get a bool argument by name */ + fun getBool(key: String): Boolean? = arguments[key]?.boolValue +} + +// ============================================================================= +// TOOL RESULT TYPES (Result after execution) +// ============================================================================= + +/** + * Result of executing a tool + */ +data class ToolResult( + /** Name of the tool that was executed */ + val toolName: String, + /** Whether execution was successful */ + val success: Boolean, + /** Result data (if successful) */ + val result: Map? = null, + /** Error message (if failed) */ + val error: String? = null, + /** The original call ID (for tracking) */ + val callId: String? = null +) + +// ============================================================================= +// TOOL EXECUTOR TYPES +// ============================================================================= + +/** + * Function type for tool executors. + * Takes arguments as strongly-typed ToolValue map, returns result map. + */ +typealias ToolExecutor = suspend (Map) -> Map + +/** + * A registered tool with its definition and executor + */ +internal data class RegisteredTool( + val definition: ToolDefinition, + val executor: ToolExecutor +) + +// ============================================================================= +// TOOL CALL FORMAT NAMES +// ============================================================================= + +/** + * Format names for tool calling output (internal string constants). + * Used for C++ bridge communication. + */ +internal object ToolCallFormatName { + const val DEFAULT = "default" + const val LFM2 = "lfm2" +} + +/** + * Tool calling format types. + * Each format specifies how tool calls are formatted in the LLM prompt. + * + * The format logic is handled in C++ commons (single source of truth). + */ +sealed class ToolCallFormat { + /** + * Default format using XML-style tags. + * JSON format: `{"tool":"name","arguments":{...}}` + * Use for most general-purpose models (Llama, Qwen, Mistral, etc.) + */ + data object Default : ToolCallFormat() + + /** + * LFM2 format for Liquid AI models. + * Liquid AI format: `<|tool_call_start|>[func(args)]<|tool_call_end|>` + * Use for LFM2-Tool models. + */ + data object LFM2 : ToolCallFormat() + + /** Get the string representation for C++ bridge */ + fun toFormatName(): String = when (this) { + is Default -> ToolCallFormatName.DEFAULT + is LFM2 -> ToolCallFormatName.LFM2 + } + + companion object { + /** Convert from format name string (for deserialization) */ + fun fromFormatName(name: String?): ToolCallFormat = when (name) { + ToolCallFormatName.LFM2 -> LFM2 + else -> Default + } + } +} + +// ============================================================================= +// TOOL CALLING OPTIONS +// ============================================================================= + +/** + * Options for tool-enabled generation + */ +data class ToolCallingOptions( + /** Available tools for this generation (if not provided, uses registered tools) */ + val tools: List? = null, + /** Maximum number of tool calls allowed in one conversation turn (default: 5) */ + val maxToolCalls: Int = 5, + /** Whether to automatically execute tools or return them for manual execution (default: true) */ + val autoExecute: Boolean = true, + /** Temperature for generation */ + val temperature: Float? = null, + /** Maximum tokens to generate */ + val maxTokens: Int? = null, + /** System prompt to use (will be merged with tool instructions by default) */ + val systemPrompt: String? = null, + /** If true, replaces the system prompt entirely instead of appending tool instructions */ + val replaceSystemPrompt: Boolean = false, + /** If true, keeps tool definitions available after the first tool call */ + val keepToolsAvailable: Boolean = false, + /** + * Format for tool calls. + * Use [ToolCallFormat.LFM2] for LFM2-Tool models (Liquid AI). + * Default: [ToolCallFormat.Default] which uses JSON-based format suitable for most models. + */ + val format: ToolCallFormat = ToolCallFormat.Default +) + +// ============================================================================= +// TOOL CALLING RESULT TYPES +// ============================================================================= + +/** + * Result of a generation that may include tool calls + */ +data class ToolCallingResult( + /** The final text response */ + val text: String, + /** Any tool calls the LLM made */ + val toolCalls: List, + /** Results of executed tools (if autoExecute was true) */ + val toolResults: List, + /** Whether the response is complete or waiting for tool results */ + val isComplete: Boolean, + /** Conversation ID for continuing with tool results */ + val conversationId: String? = null +) diff --git a/sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/Models/ModelTypes.kt b/sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/Models/ModelTypes.kt index 3cc67ed5d..a2300e82d 100644 --- a/sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/Models/ModelTypes.kt +++ b/sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/Models/ModelTypes.kt @@ -125,7 +125,6 @@ enum class ModelCategory( SPEECH_RECOGNITION("speech-recognition"), // Voice-to-text models (ASR) SPEECH_SYNTHESIS("speech-synthesis"), // Text-to-voice models (TTS) VISION("vision"), // Image understanding models - IMAGE_GENERATION("image-generation"), // Text-to-image models MULTIMODAL("multimodal"), // Models that handle multiple modalities AUDIO("audio"), // Audio processing (diarization, etc.) ; diff --git a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeModelRegistry.kt b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeModelRegistry.kt index bf77629f2..ce2b80ee5 100644 --- a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeModelRegistry.kt +++ b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeModelRegistry.kt @@ -39,8 +39,8 @@ object CppBridgeModelRegistry { const val SPEECH_SYNTHESIS = 2 // RAC_MODEL_CATEGORY_SPEECH_SYNTHESIS const val AUDIO = 3 // RAC_MODEL_CATEGORY_AUDIO const val VISION = 4 // RAC_MODEL_CATEGORY_VISION - const val IMAGE_GENERATION = 5 // RAC_MODEL_CATEGORY_IMAGE_GENERATION const val MULTIMODAL = 6 // RAC_MODEL_CATEGORY_MULTIMODAL + // 5 = IMAGE_GENERATION (diffusion) not supported on Kotlin/Android; not exposed in API } /** diff --git a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgePlatform.kt b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgePlatform.kt index 909fa1877..c59fdfeff 100644 --- a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgePlatform.kt +++ b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgePlatform.kt @@ -57,11 +57,9 @@ object CppBridgePlatform { /** Platform embedding service */ const val EMBEDDING = 4 - /** Platform image generation service */ - const val IMAGE_GENERATION = 5 - /** Platform vision/image understanding service */ const val VISION = 6 + // 5 = IMAGE_GENERATION (diffusion) not supported on Kotlin/Android; not exposed /** * Get a human-readable name for the service type. @@ -73,7 +71,6 @@ object CppBridgePlatform { TTS -> "TTS" STT -> "STT" EMBEDDING -> "EMBEDDING" - IMAGE_GENERATION -> "IMAGE_GENERATION" VISION -> "VISION" else -> "UNKNOWN($type)" } @@ -1326,8 +1323,9 @@ object CppBridgePlatform { serviceAvailability[ServiceType.TTS] = AvailabilityStatus.UNKNOWN serviceAvailability[ServiceType.STT] = AvailabilityStatus.UNKNOWN serviceAvailability[ServiceType.EMBEDDING] = AvailabilityStatus.UNKNOWN - serviceAvailability[ServiceType.IMAGE_GENERATION] = AvailabilityStatus.UNKNOWN serviceAvailability[ServiceType.VISION] = AvailabilityStatus.UNKNOWN + // 5 = IMAGE_GENERATION (diffusion) not supported on Android + serviceAvailability[5] = AvailabilityStatus.UNKNOWN } /** diff --git a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgePlatformAdapter.kt b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgePlatformAdapter.kt index 1e96f1f7c..2ec14a2c4 100644 --- a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgePlatformAdapter.kt +++ b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgePlatformAdapter.kt @@ -10,8 +10,17 @@ package com.runanywhere.sdk.foundation.bridge.extensions +import java.util.Base64 import com.runanywhere.sdk.foundation.SDKLogger +import com.runanywhere.sdk.foundation.errors.CommonsErrorCode +import com.runanywhere.sdk.native.bridge.RunAnywhereBridge import java.io.File +import java.io.FileOutputStream +import java.net.HttpURLConnection +import java.net.URL +import java.util.concurrent.Executors +import java.util.concurrent.Future +import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.ConcurrentHashMap /** @@ -56,6 +65,35 @@ object CppBridgePlatformAdapter { */ private val inMemoryStorage = ConcurrentHashMap() + /** + * Active HTTP download tasks for platform adapter downloads. + */ + private data class HttpDownloadTask( + val taskId: String, + val url: String, + val destinationPath: String, + val cancelFlag: AtomicBoolean = AtomicBoolean(false), + ) { + @Volatile + var connection: HttpURLConnection? = null + + @Volatile + var future: Future<*>? = null + } + + private val httpDownloadTasks = ConcurrentHashMap() + + private val httpDownloadExecutor = + Executors.newCachedThreadPool { runnable -> + Thread(runnable, "runanywhere-http-download").apply { + isDaemon = true + } + } + + private const val RAC_HTTP_ERROR_INVALID_ARGUMENT = -259 + private const val RAC_HTTP_ERROR_DOWNLOAD_FAILED = -153 + private const val RAC_HTTP_ERROR_CANCELLED = -380 + /** * Tag for logging. */ @@ -97,22 +135,16 @@ object CppBridgePlatformAdapter { return } - // Register all callbacks with C++ via JNI - // The actual JNI registration happens in native code using RegisterNatives() - // or via the native library initialization - - // TODO: Call native registration - // nativeRegisterPlatformAdapter( - // logCallback = ::logCallback, - // fileExistsCallback = ::fileExistsCallback, - // fileReadCallback = ::fileReadCallback, - // fileWriteCallback = ::fileWriteCallback, - // fileDeleteCallback = ::fileDeleteCallback, - // secureGetCallback = ::secureGetCallback, - // secureSetCallback = ::secureSetCallback, - // secureDeleteCallback = ::secureDeleteCallback, - // nowMsCallback = ::nowMsCallback - // ) + try { + val result = RunAnywhereBridge.racSetPlatformAdapter(this) + if (result != CommonsErrorCode.RAC_SUCCESS) { + logCallback(LogLevel.ERROR, TAG, "Failed to set platform adapter: $result") + return + } + } catch (e: UnsatisfiedLinkError) { + logCallback(LogLevel.ERROR, TAG, "Failed to register platform adapter: ${e.message}") + return + } isRegistered = true } @@ -403,6 +435,193 @@ object CppBridgePlatformAdapter { return System.currentTimeMillis() } + // ======================================================================== + // HTTP DOWNLOAD CALLBACKS (Platform Adapter) + // ======================================================================== + + /** + * Download a file from a URL to a destination path. + * + * This is an asynchronous download used by the C++ platform adapter. + * Returns RAC_SUCCESS if the download was scheduled, or an error code otherwise. + */ + fun httpDownload(url: String, destinationPath: String, taskId: String): Int { + if (url.isBlank() || destinationPath.isBlank() || taskId.isBlank()) { + logCallback(LogLevel.ERROR, "HTTP", "Download invalid arguments (taskId=$taskId)") + return RAC_HTTP_ERROR_INVALID_ARGUMENT + } + + val task = HttpDownloadTask(taskId = taskId, url = url, destinationPath = destinationPath) + if (httpDownloadTasks.putIfAbsent(taskId, task) != null) { + logCallback(LogLevel.WARN, "HTTP", "Duplicate download taskId=$taskId") + return RAC_HTTP_ERROR_INVALID_ARGUMENT + } + + return try { + val future = + httpDownloadExecutor.submit { + performHttpDownload(task) + } + task.future = future + CommonsErrorCode.RAC_SUCCESS + } catch (e: Exception) { + httpDownloadTasks.remove(taskId) + logCallback(LogLevel.ERROR, "HTTP", "Failed to schedule download for $url: ${e.message}") + RAC_HTTP_ERROR_DOWNLOAD_FAILED + } + } + + /** + * Cancel a download task. + */ + fun httpDownloadCancel(taskId: String): Boolean { + val task = httpDownloadTasks[taskId] ?: return false + task.cancelFlag.set(true) + task.connection?.disconnect() + return true + } + + private fun performHttpDownload(task: HttpDownloadTask) { + var result = RAC_HTTP_ERROR_DOWNLOAD_FAILED + var finalPath: String? = null + var tempFile: File? = null + + try { + if (task.cancelFlag.get()) { + result = RAC_HTTP_ERROR_CANCELLED + return + } + + val connection = URL(task.url).openConnection() as HttpURLConnection + task.connection = connection + connection.instanceFollowRedirects = true + connection.connectTimeout = 30_000 + connection.readTimeout = 60_000 + connection.requestMethod = "GET" + connection.connect() + + val status = connection.responseCode + if (status !in 200..299) { + logCallback(LogLevel.ERROR, "HTTP", "Download failed with status $status for ${task.url}") + result = RAC_HTTP_ERROR_DOWNLOAD_FAILED + return + } + + val totalBytes = connection.contentLengthLong.let { if (it > 0) it else 0L } + val destFile = File(task.destinationPath) + destFile.parentFile?.mkdirs() + val temp = File(destFile.parentFile, destFile.name + ".part") + tempFile = temp + if (temp.exists()) { + temp.delete() + } + + var downloaded = 0L + var lastReported = 0L + val reportThreshold = 256 * 1024L + + connection.inputStream.use { input -> + FileOutputStream(temp).use { output -> + val buffer = ByteArray(8192) + while (true) { + if (task.cancelFlag.get()) { + result = RAC_HTTP_ERROR_CANCELLED + return + } + val read = input.read(buffer) + if (read <= 0) break + output.write(buffer, 0, read) + downloaded += read + if (downloaded - lastReported >= reportThreshold) { + RunAnywhereBridge.racHttpDownloadReportProgress( + task.taskId, + downloaded, + totalBytes, + ) + lastReported = downloaded + } + } + } + } + + if (task.cancelFlag.get()) { + result = RAC_HTTP_ERROR_CANCELLED + return + } + + if (temp.exists()) { + if (destFile.exists()) { + destFile.delete() + } + val moved = temp.renameTo(destFile) + if (!moved) { + temp.copyTo(destFile, overwrite = true) + temp.delete() + } + } + + RunAnywhereBridge.racHttpDownloadReportProgress(task.taskId, downloaded, totalBytes) + finalPath = destFile.absolutePath + result = CommonsErrorCode.RAC_SUCCESS + } catch (e: Exception) { + if (task.cancelFlag.get()) { + result = RAC_HTTP_ERROR_CANCELLED + } else { + logCallback(LogLevel.ERROR, "HTTP", "Download failed for ${task.url}: ${e.message}") + result = RAC_HTTP_ERROR_DOWNLOAD_FAILED + } + } finally { + task.connection?.disconnect() + task.connection = null + httpDownloadTasks.remove(task.taskId) + + if (result != CommonsErrorCode.RAC_SUCCESS) { + tempFile?.let { + if (it.exists()) { + it.delete() + } + } + } + + RunAnywhereBridge.racHttpDownloadReportComplete(task.taskId, result, finalPath) + } + } + + // ======================================================================== + // INSTANCE METHODS REQUIRED BY JNI PLATFORM ADAPTER + // ======================================================================== + + fun log(level: Int, tag: String, message: String) { + logCallback(level, tag, message) + } + + fun fileExists(path: String): Boolean = fileExistsCallback(path) + + fun fileRead(path: String): ByteArray? = fileReadCallback(path) + + fun fileWrite(path: String, data: ByteArray): Boolean = fileWriteCallback(path, data) + + fun fileDelete(path: String): Boolean = fileDeleteCallback(path) + + fun secureGet(key: String): String? { + val value = secureGetCallback(key) ?: return null + return Base64.getEncoder().encodeToString(value) + } + + fun secureSet(key: String, value: String): Boolean { + return try { + val bytes = Base64.getDecoder().decode(value) + secureSetCallback(key, bytes) + } catch (e: Exception) { + logCallback(LogLevel.ERROR, "SecureStorage", "secureSet decode failed: ${e.message}") + false + } + } + + fun secureDelete(key: String): Boolean = secureDeleteCallback(key) + + fun nowMs(): Long = nowMsCallback() + // ======================================================================== // JNI NATIVE DECLARATIONS // ======================================================================== diff --git a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeToolCalling.kt b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeToolCalling.kt new file mode 100644 index 000000000..125ea411c --- /dev/null +++ b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeToolCalling.kt @@ -0,0 +1,313 @@ +/* + * Copyright 2026 RunAnywhere SDK + * SPDX-License-Identifier: Apache-2.0 + * + * C++ bridge for tool calling functionality. + * + * *** SINGLE SOURCE OF TRUTH FOR TOOL CALLING LOGIC *** + * All parsing and prompt formatting is done in C++ (rac_tool_calling.h). + * This bridge is a THIN WRAPPER - no parsing logic in Kotlin. + * + * Platform SDKs handle ONLY: + * - Tool registry (Kotlin closures) + * - Tool execution (Kotlin async calls) + */ + +package com.runanywhere.sdk.foundation.bridge.extensions + +import com.runanywhere.sdk.foundation.SDKLogger +import com.runanywhere.sdk.native.bridge.RunAnywhereBridge +import com.runanywhere.sdk.public.extensions.LLM.* +import kotlinx.serialization.json.* + +/** + * Tool calling bridge to C++ implementation. + * + * *** ALL PARSING LOGIC IS IN C++ - NO KOTLIN FALLBACKS *** + */ +object CppBridgeToolCalling { + private const val TAG = "CppBridgeToolCalling" + private val logger = SDKLogger(TAG) + + /** + * Parsed tool call result from C++ + */ + data class ParseResult( + val hasToolCall: Boolean, + val cleanText: String, + val toolName: String?, + val argumentsJson: String?, + val callId: Long + ) + + // ======================================================================== + // PARSE TOOL CALL (NO FALLBACK) + // ======================================================================== + + /** + * Parse LLM output for tool calls using C++ implementation. + * + * *** THIS IS THE ONLY PARSING IMPLEMENTATION - NO KOTLIN FALLBACK *** + * + * @param llmOutput Raw LLM output text + * @return Parsed result with tool call info + */ + fun parseToolCall(llmOutput: String): ParseResult { + val resultJson = RunAnywhereBridge.racToolCallParse(llmOutput) + ?: return ParseResult( + hasToolCall = false, + cleanText = llmOutput, + toolName = null, + argumentsJson = null, + callId = 0 + ) + + return try { + val json = Json.parseToJsonElement(resultJson).jsonObject + ParseResult( + hasToolCall = json["hasToolCall"]?.jsonPrimitive?.boolean ?: false, + cleanText = json["cleanText"]?.jsonPrimitive?.content ?: llmOutput, + toolName = json["toolName"]?.jsonPrimitive?.contentOrNull, + argumentsJson = json["argumentsJson"]?.toString(), + callId = json["callId"]?.jsonPrimitive?.longOrNull ?: 0 + ) + } catch (e: Exception) { + logger.error("Failed to parse tool call result: ${e.message}") + ParseResult( + hasToolCall = false, + cleanText = llmOutput, + toolName = null, + argumentsJson = null, + callId = 0 + ) + } + } + + /** + * Parse LLM output and return a ToolCall object if found. + * + * @param llmOutput Raw LLM output text + * @return Pair of (cleanText, toolCall) where toolCall is null if none found + */ + fun parseToolCallToObject(llmOutput: String): Pair { + val result = parseToolCall(llmOutput) + + if (!result.hasToolCall || result.toolName == null) { + return Pair(result.cleanText, null) + } + + val arguments = parseArgumentsJson(result.argumentsJson ?: "{}") + + return Pair( + result.cleanText, + ToolCall( + toolName = result.toolName, + arguments = arguments, + callId = "call_${result.callId}" + ) + ) + } + + // ======================================================================== + // FORMAT TOOLS FOR PROMPT (NO FALLBACK) + // ======================================================================== + + /** + * Format tool definitions into a system prompt using C++ implementation. + * + * @param tools List of tool definitions + * @param format Tool calling format type. See [ToolCallFormat]. + * @return Formatted system prompt string + */ + fun formatToolsForPrompt( + tools: List, + format: ToolCallFormat = ToolCallFormat.Default + ): String { + if (tools.isEmpty()) return "" + + val toolsJson = serializeToolsToJson(tools) + // Convert to string at JNI boundary - C++ handles the format logic + val formatString = format.toFormatName() + return RunAnywhereBridge.racToolCallFormatPromptJsonWithFormatName(toolsJson, formatString) ?: "" + } + + // ======================================================================== + // BUILD INITIAL PROMPT (NO FALLBACK) + // ======================================================================== + + /** + * Build the initial prompt with tools and user query using C++ implementation. + * + * @param userPrompt The user's question/request + * @param tools List of tool definitions + * @param options Tool calling options + * @return Complete formatted prompt + */ + fun buildInitialPrompt( + userPrompt: String, + tools: List, + options: ToolCallingOptions + ): String { + val toolsJson = serializeToolsToJson(tools) + val optionsJson = serializeOptionsToJson(options) + + return RunAnywhereBridge.racToolCallBuildInitialPrompt( + userPrompt, + toolsJson, + optionsJson + ) ?: userPrompt + } + + // ======================================================================== + // BUILD FOLLOW-UP PROMPT (NO FALLBACK) + // ======================================================================== + + /** + * Build follow-up prompt after tool execution using C++ implementation. + * + * @param originalPrompt The original user prompt + * @param toolsPrompt The formatted tools prompt (null if not keeping tools) + * @param toolName Name of the tool that was executed + * @param toolResultJson JSON string of the tool result + * @param keepToolsAvailable Whether to include tool definitions + * @return Follow-up prompt string + */ + fun buildFollowupPrompt( + originalPrompt: String, + toolsPrompt: String?, + toolName: String, + toolResultJson: String, + keepToolsAvailable: Boolean + ): String { + return RunAnywhereBridge.racToolCallBuildFollowupPrompt( + originalPrompt, + toolsPrompt, + toolName, + toolResultJson, + keepToolsAvailable + ) ?: "" + } + + // ======================================================================== + // JSON NORMALIZATION (NO FALLBACK) + // ======================================================================== + + /** + * Normalize JSON by adding quotes around unquoted keys using C++ implementation. + * + * @param jsonStr Raw JSON string possibly with unquoted keys + * @return Normalized JSON string with all keys quoted + */ + fun normalizeJson(jsonStr: String): String { + return RunAnywhereBridge.racToolCallNormalizeJson(jsonStr) ?: jsonStr + } + + // ======================================================================== + // PRIVATE HELPERS + // ======================================================================== + + /** + * Parse arguments JSON string to Map + */ + private fun parseArgumentsJson(json: String): Map { + return try { + val element = Json.parseToJsonElement(json) + if (element is JsonObject) { + element.mapValues { (_, v) -> jsonElementToToolValue(v) } + } else { + emptyMap() + } + } catch (e: Exception) { + logger.error("Failed to parse arguments JSON: ${e.message}") + emptyMap() + } + } + + /** + * Convert JsonElement to ToolValue + */ + private fun jsonElementToToolValue(element: JsonElement): ToolValue = when (element) { + is JsonPrimitive -> when { + element.isString -> ToolValue.string(element.content) + element.booleanOrNull != null -> ToolValue.bool(element.boolean) + element.doubleOrNull != null -> ToolValue.number(element.double) + else -> ToolValue.string(element.content) + } + is JsonArray -> ToolValue.array(element.map { jsonElementToToolValue(it) }) + is JsonObject -> ToolValue.obj(element.mapValues { (_, v) -> jsonElementToToolValue(v) }) + JsonNull -> ToolValue.nullValue() + } + + /** + * Serialize tool definitions to JSON array string + */ + private fun serializeToolsToJson(tools: List): String { + val jsonArray = buildJsonArray { + tools.forEach { tool -> + addJsonObject { + put("name", tool.name) + put("description", tool.description) + putJsonArray("parameters") { + tool.parameters.forEach { param -> + addJsonObject { + put("name", param.name) + put("type", param.type.value) + put("description", param.description) + put("required", param.required) + param.enumValues?.let { values -> + putJsonArray("enumValues") { + values.forEach { add(it) } + } + } + } + } + } + tool.category?.let { put("category", it) } + } + } + } + return jsonArray.toString() + } + + /** + * Serialize options to JSON string + */ + private fun serializeOptionsToJson(options: ToolCallingOptions): String { + val jsonObj = buildJsonObject { + put("maxToolCalls", options.maxToolCalls) + put("autoExecute", options.autoExecute) + options.temperature?.let { put("temperature", it) } + options.maxTokens?.let { put("maxTokens", it) } + options.systemPrompt?.let { put("systemPrompt", it) } + put("replaceSystemPrompt", options.replaceSystemPrompt) + put("keepToolsAvailable", options.keepToolsAvailable) + put("format", options.format.toFormatName()) // Convert to string at serialization boundary + } + return jsonObj.toString() + } + + /** + * Convert ToolValue to JSON string + */ + fun toolValueToJsonString(value: Map): String { + val jsonObj = buildJsonObject { + value.forEach { (k, v) -> + put(k, toolValueToJsonElement(v)) + } + } + return jsonObj.toString() + } + + private fun toolValueToJsonElement(value: ToolValue): JsonElement = when (value) { + is ToolValue.StringValue -> JsonPrimitive(value.value) + is ToolValue.NumberValue -> JsonPrimitive(value.value) + is ToolValue.BoolValue -> JsonPrimitive(value.value) + is ToolValue.ArrayValue -> buildJsonArray { + value.value.forEach { add(toolValueToJsonElement(it)) } + } + is ToolValue.ObjectValue -> buildJsonObject { + value.value.forEach { (k, v) -> put(k, toolValueToJsonElement(v)) } + } + ToolValue.NullValue -> JsonNull + } +} diff --git a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/TTSRouter.kt b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/TTSRouter.kt new file mode 100644 index 000000000..04cfb3c98 --- /dev/null +++ b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/TTSRouter.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2026 RunAnywhere SDK + * SPDX-License-Identifier: Apache-2.0 + * + * TTS Router - Routes TTS operations to the appropriate backend. + * + * Backend: + * - CppBridgeTTS: Sherpa-ONNX on CPU + */ + +package com.runanywhere.sdk.foundation.bridge.extensions + +import com.runanywhere.sdk.foundation.SDKLogger +import com.runanywhere.sdk.foundation.errors.SDKError +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +/** + * Routes TTS operations to CppBridgeTTS (Sherpa-ONNX). + */ +object TTSRouter { + private const val TAG = "TTSRouter" + private val logger = SDKLogger(TAG) + + /** + * Backend type currently in use. + */ + sealed class Backend { + data object SherpaOnnx : Backend() { + override fun toString() = "SherpaOnnx (CppBridgeTTS)" + } + } + + @Volatile + private var _currentBackend: Backend? = null + + @Volatile + private var _loadedModelId: String? = null + + @Volatile + private var _loadedModelName: String? = null + + private val lock = Any() + private val loadMutex = Mutex() + + val currentBackend: Backend? + get() = _currentBackend + + val backendName: String + get() = _currentBackend?.toString() ?: "None" + + val isLoaded: Boolean + get() = synchronized(lock) { + when (_currentBackend) { + is Backend.SherpaOnnx -> CppBridgeTTS.isLoaded + null -> false + } + } + + fun getLoadedModelId(): String? = synchronized(lock) { + when (_currentBackend) { + is Backend.SherpaOnnx -> CppBridgeTTS.getLoadedModelId() + null -> null + } + } + + /** + * Load a TTS model via CppBridgeTTS (Sherpa-ONNX). + */ + suspend fun loadModel( + modelPath: String, + modelId: String, + modelName: String? + ): Result = loadMutex.withLock { + logger.info("Loading TTS model: $modelId from $modelPath") + + unloadInternal() + + logger.info("Using SherpaOnnx backend for model: $modelId") + val result = loadWithSherpaOnnx(modelPath, modelId, modelName) + if (result == 0) { + _currentBackend = Backend.SherpaOnnx + _loadedModelId = modelId + _loadedModelName = modelName + logger.info("SherpaOnnx model loaded successfully: $modelId") + Result.success(Unit) + } else { + val errorMsg = "Failed to load TTS model (error: $result)" + logger.error(errorMsg) + Result.failure(SDKError.tts(errorMsg)) + } + } + + private fun loadWithSherpaOnnx( + modelPath: String, + modelId: String, + modelName: String? + ): Int { + logger.info("Loading TTS model with SherpaOnnx: $modelId") + return CppBridgeTTS.loadModel(modelPath, modelId, modelName) + } + + suspend fun synthesize( + text: String, + config: CppBridgeTTS.SynthesisConfig + ): Result { + return when (_currentBackend) { + is Backend.SherpaOnnx -> synthesizeWithSherpaOnnx(text, config) + null -> Result.failure(SDKError.tts("No TTS model loaded")) + } + } + + private fun synthesizeWithSherpaOnnx( + text: String, + config: CppBridgeTTS.SynthesisConfig + ): Result { + logger.debug("Synthesizing with SherpaOnnx: \"${text.take(50)}...\"") + return try { + val result = CppBridgeTTS.synthesize(text, config) + Result.success(result) + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun synthesizeStream( + text: String, + config: CppBridgeTTS.SynthesisConfig, + callback: CppBridgeTTS.StreamCallback + ): Result { + return when (_currentBackend) { + is Backend.SherpaOnnx -> { + try { + val result = CppBridgeTTS.synthesizeStream(text, config, callback) + Result.success(result) + } catch (e: Exception) { + Result.failure(e) + } + } + null -> Result.failure(SDKError.tts("No TTS model loaded")) + } + } + + fun getAvailableVoices(): List { + return when (_currentBackend) { + is Backend.SherpaOnnx -> CppBridgeTTS.getAvailableVoices() + null -> emptyList() + } + } + + fun cancel() { + when (_currentBackend) { + is Backend.SherpaOnnx -> CppBridgeTTS.cancel() + null -> { /* No-op */ } + } + } + + fun unload() { + synchronized(lock) { + unloadInternal() + } + } + + private fun unloadInternal() { + when (_currentBackend) { + is Backend.SherpaOnnx -> { + CppBridgeTTS.unload() + logger.info("SherpaOnnx model unloaded") + } + null -> { /* Already unloaded */ } + } + + _currentBackend = null + _loadedModelId = null + _loadedModelName = null + } +} diff --git a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/native/bridge/RunAnywhereBridge.kt b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/native/bridge/RunAnywhereBridge.kt index 5c93b2a44..ff7cb5f9f 100644 --- a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/native/bridge/RunAnywhereBridge.kt +++ b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/native/bridge/RunAnywhereBridge.kt @@ -336,6 +336,16 @@ object RunAnywhereBridge { progressCallback: Any?, ) + // ======================================================================== + // HTTP DOWNLOAD (platform adapter callbacks) + // ======================================================================== + + @JvmStatic + external fun racHttpDownloadReportProgress(taskId: String, downloadedBytes: Long, totalBytes: Long): Int + + @JvmStatic + external fun racHttpDownloadReportComplete(taskId: String, result: Int, downloadedPath: String?): Int + // ======================================================================== // BACKEND REGISTRATION // ======================================================================== @@ -855,6 +865,97 @@ object RunAnywhereBridge { baseUrl: String?, ): Int + // ======================================================================== + // TOOL CALLING API (rac_tool_calling.h) + // Mirrors Swift SDK's CppBridge+ToolCalling.swift + // ======================================================================== + + /** + * Parse LLM output for tool calls. + * + * @param llmOutput Raw LLM output text + * @return JSON string with parsed result, or null on error + */ + @JvmStatic + external fun racToolCallParse(llmOutput: String): String? + + /** + * Format tool definitions into system prompt. + * + * @param toolsJson JSON array of tool definitions + * @return Formatted prompt string, or null on error + */ + @JvmStatic + external fun racToolCallFormatPromptJson(toolsJson: String): String? + + /** + * Format tool definitions into system prompt with specified format (int). + * + * @param toolsJson JSON array of tool definitions + * @param format Tool calling format (0=AUTO, 1=DEFAULT, 2=LFM2, 3=OPENAI) + * @return Formatted prompt string, or null on error + */ + @JvmStatic + external fun racToolCallFormatPromptJsonWithFormat(toolsJson: String, format: Int): String? + + /** + * Format tool definitions into system prompt with format specified by name. + * + * *** PREFERRED API - Uses string format name *** + * + * Valid format names (case-insensitive): "auto", "default", "lfm2", "openai" + * C++ is single source of truth for format validation. + * + * @param toolsJson JSON array of tool definitions + * @param formatName Format name string (e.g., "lfm2", "default") + * @return Formatted prompt string, or null on error + */ + @JvmStatic + external fun racToolCallFormatPromptJsonWithFormatName(toolsJson: String, formatName: String): String? + + /** + * Build initial prompt with tools and user query. + * + * @param userPrompt The user's question/request + * @param toolsJson JSON array of tool definitions + * @param optionsJson Options as JSON (nullable) + * @return Complete formatted prompt, or null on error + */ + @JvmStatic + external fun racToolCallBuildInitialPrompt( + userPrompt: String, + toolsJson: String, + optionsJson: String?, + ): String? + + /** + * Build follow-up prompt after tool execution. + * + * @param originalPrompt The original user prompt + * @param toolsPrompt Formatted tools prompt (nullable) + * @param toolName Name of the tool that was executed + * @param toolResultJson JSON string of the tool result + * @param keepToolsAvailable Whether to include tool definitions + * @return Follow-up prompt, or null on error + */ + @JvmStatic + external fun racToolCallBuildFollowupPrompt( + originalPrompt: String, + toolsPrompt: String?, + toolName: String, + toolResultJson: String, + keepToolsAvailable: Boolean, + ): String? + + /** + * Normalize JSON by adding quotes around unquoted keys. + * + * @param jsonStr Raw JSON string possibly with unquoted keys + * @return Normalized JSON string, or null on error + */ + @JvmStatic + external fun racToolCallNormalizeJson(jsonStr: String): String? + // ======================================================================== // CONSTANTS // ======================================================================== diff --git a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/LLM/RunAnywhereToolCalling.kt b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/LLM/RunAnywhereToolCalling.kt new file mode 100644 index 000000000..89a239d7b --- /dev/null +++ b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/LLM/RunAnywhereToolCalling.kt @@ -0,0 +1,351 @@ +/* + * Copyright 2026 RunAnywhere SDK + * SPDX-License-Identifier: Apache-2.0 + * + * Public API for tool calling (function calling) with LLMs. + * Allows LLMs to request external actions (API calls, device functions, etc.) + * + * ARCHITECTURE: + * - CppBridgeToolCalling: C++ bridge for parsing tags (SINGLE SOURCE OF TRUTH) + * - This file: Tool registration, executor storage, orchestration + * - Orchestration: generate โ†’ parse (C++) โ†’ execute โ†’ loop + * + * *** ALL PARSING LOGIC IS IN C++ (rac_tool_calling.h) - NO KOTLIN FALLBACKS *** + * + * Mirrors Swift SDK's RunAnywhere+ToolCalling.swift + */ + +package com.runanywhere.sdk.public.extensions.LLM + +import com.runanywhere.sdk.public.RunAnywhere +import com.runanywhere.sdk.public.extensions.generateStream +import com.runanywhere.sdk.foundation.SDKLogger +import com.runanywhere.sdk.foundation.bridge.extensions.CppBridgeToolCalling +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +/** + * Thread-safe tool registry for tool registration and lookup. + */ +private object ToolRegistry { + private val mutex = Mutex() + private val tools = mutableMapOf() + + suspend fun register(definition: ToolDefinition, executor: ToolExecutor) = mutex.withLock { + tools[definition.name] = RegisteredTool(definition, executor) + } + + suspend fun unregister(toolName: String) = mutex.withLock { + tools.remove(toolName) + } + + suspend fun getAll(): List = mutex.withLock { + tools.values.map { it.definition } + } + + suspend fun get(toolName: String): RegisteredTool? = mutex.withLock { + tools[toolName] + } + + suspend fun clear() = mutex.withLock { + tools.clear() + } +} + +/** + * Tool calling extension for RunAnywhere. + */ +object RunAnywhereToolCalling { + private const val TAG = "ToolCalling" + private val logger = SDKLogger(TAG) + + // ======================================================================== + // TOOL REGISTRATION + // ======================================================================== + + /** + * Register a tool that the LLM can use. + * + * Tools are stored in-memory and available for all subsequent generateWithTools calls. + * Executors run in Kotlin and have full access to Kotlin/Android APIs. + * + * @param definition Tool definition (name, description, parameters) + * @param executor Suspend function that executes the tool + */ + suspend fun registerTool( + definition: ToolDefinition, + executor: ToolExecutor + ) { + ToolRegistry.register(definition, executor) + logger.info("Registered tool: ${definition.name}") + } + + /** + * Unregister a tool by name. + * + * @param toolName The name of the tool to remove + */ + suspend fun unregisterTool(toolName: String) { + ToolRegistry.unregister(toolName) + logger.info("Unregistered tool: $toolName") + } + + /** + * Get all registered tool definitions. + * + * @return List of registered tool definitions + */ + suspend fun getRegisteredTools(): List { + return ToolRegistry.getAll() + } + + /** + * Clear all registered tools. + */ + suspend fun clearTools() { + ToolRegistry.clear() + logger.info("Cleared all registered tools") + } + + // ======================================================================== + // TOOL EXECUTION + // ======================================================================== + + /** + * Execute a tool call. + * + * Looks up the tool in the registry and invokes its executor with the provided arguments. + * Returns a ToolResult with success/failure status. + * + * @param toolCall The tool call to execute + * @return Result of the tool execution + */ + suspend fun executeTool(toolCall: ToolCall): ToolResult { + val tool = ToolRegistry.get(toolCall.toolName) + + if (tool == null) { + return ToolResult( + toolName = toolCall.toolName, + success = false, + error = "Unknown tool: ${toolCall.toolName}", + callId = toolCall.callId + ) + } + + return try { + val result = tool.executor(toolCall.arguments) + ToolResult( + toolName = toolCall.toolName, + success = true, + result = result, + callId = toolCall.callId + ) + } catch (e: Exception) { + logger.error("Tool execution failed: ${e.message}") + ToolResult( + toolName = toolCall.toolName, + success = false, + error = e.message ?: "Unknown error", + callId = toolCall.callId + ) + } + } + + // ======================================================================== + // GENERATE WITH TOOLS + // ======================================================================== + + /** + * Generates a response with tool calling support. + * + * Orchestrates a generate โ†’ parse โ†’ execute โ†’ loop cycle: + * 1. Builds a system prompt describing available tools (C++) + * 2. Generates LLM response + * 3. Parses output for `` tags (C++ - SINGLE SOURCE OF TRUTH) + * 4. If tool call found and autoExecute is true, executes and continues + * 5. Repeats until no more tool calls or maxToolCalls reached + * + * @param prompt The user's prompt + * @param options Tool calling options + * @return Result containing final text, all tool calls made, and their results + */ + suspend fun generateWithTools( + prompt: String, + options: ToolCallingOptions? = null + ): ToolCallingResult { + // Ensure SDK is initialized + require(RunAnywhere.isInitialized) { "SDK not initialized" } + + val opts = options ?: ToolCallingOptions() + val registeredTools = ToolRegistry.getAll() + val tools = opts.tools ?: registeredTools + + // Build system prompt using C++ (SINGLE SOURCE OF TRUTH) + val systemPrompt = buildToolSystemPrompt(tools, opts) + var fullPrompt = if (systemPrompt.isEmpty()) prompt else "$systemPrompt\n\nUser: $prompt" + + val allToolCalls = mutableListOf() + val allToolResults = mutableListOf() + var finalText = "" + + repeat(opts.maxToolCalls) { iteration -> + logger.debug("Tool calling iteration $iteration") + + // Generate response + val responseText = generateAndCollect(fullPrompt, opts.temperature, opts.maxTokens) + + // Parse for tool calls using C++ (SINGLE SOURCE OF TRUTH - NO FALLBACK) + val (cleanText, toolCall) = CppBridgeToolCalling.parseToolCallToObject(responseText) + finalText = cleanText + + if (toolCall == null) { + logger.debug("No tool call found, generation complete") + return ToolCallingResult( + text = finalText, + toolCalls = allToolCalls, + toolResults = allToolResults, + isComplete = true + ) + } + + allToolCalls.add(toolCall) + logger.info("Found tool call: ${toolCall.toolName}") + + if (!opts.autoExecute) { + return ToolCallingResult( + text = finalText, + toolCalls = allToolCalls, + toolResults = emptyList(), + isComplete = false + ) + } + + // Execute tool + val result = executeTool(toolCall) + allToolResults.add(result) + logger.info("Tool ${toolCall.toolName} executed: ${if (result.success) "success" else "failed"}") + + // Build follow-up prompt using C++ (SINGLE SOURCE OF TRUTH) + val toolResultJson = CppBridgeToolCalling.toolValueToJsonString( + result.result ?: mapOf("error" to ToolValue.string(result.error ?: "Unknown error")) + ) + + fullPrompt = CppBridgeToolCalling.buildFollowupPrompt( + originalPrompt = prompt, + toolsPrompt = if (opts.keepToolsAvailable) CppBridgeToolCalling.formatToolsForPrompt(tools, opts.format) else null, + toolName = toolCall.toolName, + toolResultJson = toolResultJson, + keepToolsAvailable = opts.keepToolsAvailable + ) + } + + return ToolCallingResult( + text = finalText, + toolCalls = allToolCalls, + toolResults = allToolResults, + isComplete = true + ) + } + + /** + * Continue generation after manual tool execution. + * + * Use this when autoExecute is false. After receiving a ToolCallingResult + * with isComplete: false, execute the tool yourself, then call this to continue. + * + * @param previousPrompt The original user prompt + * @param toolCall The tool call that was executed + * @param toolResult The result of executing the tool + * @param options Tool calling options for the continuation + * @return Result of the continued generation + */ + suspend fun continueWithToolResult( + previousPrompt: String, + toolCall: ToolCall, + toolResult: ToolResult, + options: ToolCallingOptions? = null + ): ToolCallingResult { + val resultJson = CppBridgeToolCalling.toolValueToJsonString( + toolResult.result ?: mapOf("error" to ToolValue.string(toolResult.error ?: "Unknown error")) + ) + + // Build follow-up prompt using C++ (SINGLE SOURCE OF TRUTH) + val tools = options?.tools ?: ToolRegistry.getAll() + val toolsPrompt = if (options?.keepToolsAvailable == true) { + CppBridgeToolCalling.formatToolsForPrompt(tools, options.format) + } else null + + val continuedPrompt = CppBridgeToolCalling.buildFollowupPrompt( + originalPrompt = previousPrompt, + toolsPrompt = toolsPrompt, + toolName = toolCall.toolName, + toolResultJson = resultJson, + keepToolsAvailable = options?.keepToolsAvailable ?: false + ) + + val continuationOptions = ToolCallingOptions( + tools = options?.tools, + maxToolCalls = maxOf(0, (options?.maxToolCalls ?: 5) - 1), + autoExecute = options?.autoExecute ?: true, + temperature = options?.temperature, + maxTokens = options?.maxTokens, + systemPrompt = options?.systemPrompt, + replaceSystemPrompt = options?.replaceSystemPrompt ?: false, + keepToolsAvailable = options?.keepToolsAvailable ?: false, + format = options?.format ?: ToolCallFormat.Default + ) + + return generateWithTools(continuedPrompt, continuationOptions) + } + + // ======================================================================== + // PRIVATE HELPERS + // ======================================================================== + + /** + * Builds the system prompt with tool definitions using C++ implementation. + */ + private fun buildToolSystemPrompt( + tools: List, + options: ToolCallingOptions + ): String { + // Use C++ implementation for prompt formatting (SINGLE SOURCE OF TRUTH) + // Pass the format from options to generate model-specific instructions + val toolsPrompt = CppBridgeToolCalling.formatToolsForPrompt(tools, options.format) + + return when { + options.replaceSystemPrompt && options.systemPrompt != null -> { + options.systemPrompt + } + options.systemPrompt != null -> { + "${options.systemPrompt}\n\n$toolsPrompt" + } + else -> { + toolsPrompt + } + } + } + + /** + * Generate text using streaming and collect all tokens into a single string. + */ + private suspend fun generateAndCollect( + prompt: String, + temperature: Float?, + maxTokens: Int? + ): String { + val genOptions = LLMGenerationOptions( + maxTokens = maxTokens ?: 1024, + temperature = temperature ?: 0.7f + ) + + val tokenFlow = RunAnywhere.generateStream(prompt, genOptions) + + val responseText = StringBuilder() + tokenFlow.collect { token -> + responseText.append(token) + } + + return responseText.toString() + } +} diff --git a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+ModelManagement.jvmAndroid.kt b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+ModelManagement.jvmAndroid.kt index 3f916b0c3..67adb4cee 100644 --- a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+ModelManagement.jvmAndroid.kt +++ b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+ModelManagement.jvmAndroid.kt @@ -874,10 +874,26 @@ actual suspend fun RunAnywhere.loadSTTModel(modelId: String) { model.localPath ?: throw SDKError.model("Model '$modelId' is not downloaded") - // Pass modelPath, modelId, and modelName separately for correct telemetry - val result = CppBridgeSTT.loadModel(localPath, modelId, model.name) + // Run native load on IO thread to avoid ANR and native crashes on main thread + val result = withContext(Dispatchers.IO) { + val dir = File(localPath) + if (!dir.exists()) { + return@withContext -1 + } + if (!dir.isDirectory) { + modelsLogger.error("STT model path is not a directory (expected extracted model dir): $localPath") + return@withContext -1 + } + // C++ backend expects directory with encoder.onnx, decoder.onnx, tokens.txt + val hasEncoder = dir.listFiles()?.any { it.name.contains("encoder") && it.name.endsWith(".onnx") } == true + if (!hasEncoder) { + modelsLogger.error("STT model directory missing encoder.onnx: $localPath. Re-download the model.") + return@withContext -1 + } + CppBridgeSTT.loadModel(localPath, modelId, model.name) + } if (result != 0) { - throw SDKError.stt("Failed to load STT model '$modelId' (error code: $result)") + throw SDKError.stt("Failed to load STT model '$modelId' (error code: $result). Ensure the model is extracted and contains encoder.onnx, decoder.onnx, tokens.txt.") } } diff --git a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+Storage.jvmAndroid.kt b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+Storage.jvmAndroid.kt index 17e2bdb58..622ac9f39 100644 --- a/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+Storage.jvmAndroid.kt +++ b/sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+Storage.jvmAndroid.kt @@ -217,8 +217,9 @@ private fun convertToModelStorageMetrics( CppBridgeModelRegistry.ModelCategory.SPEECH_SYNTHESIS -> ModelCategory.SPEECH_SYNTHESIS CppBridgeModelRegistry.ModelCategory.AUDIO -> ModelCategory.AUDIO CppBridgeModelRegistry.ModelCategory.VISION -> ModelCategory.VISION - CppBridgeModelRegistry.ModelCategory.IMAGE_GENERATION -> ModelCategory.IMAGE_GENERATION CppBridgeModelRegistry.ModelCategory.MULTIMODAL -> ModelCategory.MULTIMODAL + // 5 = IMAGE_GENERATION (diffusion) not supported on Kotlin/Android; treat as LANGUAGE + 5 -> ModelCategory.LANGUAGE else -> ModelCategory.LANGUAGE } diff --git a/sdk/runanywhere-react-native/.gitignore b/sdk/runanywhere-react-native/.gitignore index 54aa8aa29..89a7142d6 100644 --- a/sdk/runanywhere-react-native/.gitignore +++ b/sdk/runanywhere-react-native/.gitignore @@ -11,6 +11,7 @@ nitrogen/generated/ # iOS ios/build/ **/ios/Binaries/ +**/ios/xcframeworks/ *.xcframework.zip # NOTE: iOS xcframeworks are now bundled in npm package - DO NOT gitignore them diff --git a/sdk/runanywhere-react-native/package-lock.json b/sdk/runanywhere-react-native/package-lock.json index 1aca334b7..6236c3a6b 100644 --- a/sdk/runanywhere-react-native/package-lock.json +++ b/sdk/runanywhere-react-native/package-lock.json @@ -1,12 +1,12 @@ { "name": "runanywhere-react-native-monorepo", - "version": "0.2.0", + "version": "0.17.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "runanywhere-react-native-monorepo", - "version": "0.2.0", + "version": "0.17.0", "license": "MIT", "workspaces": [ "packages/*" @@ -14,8 +14,8 @@ "devDependencies": { "@commitlint/config-conventional": "^17.0.2", "@evilmartians/lefthook": "^1.5.0", - "@types/node": "^20.10.0", - "@types/react": "^18.2.44", + "@types/node": "^24.10.0", + "@types/react": "~19.1.0", "@typescript-eslint/eslint-plugin": "^8.50.0", "@typescript-eslint/parser": "^8.50.0", "commitlint": "^17.0.2", @@ -25,18 +25,19 @@ "eslint-plugin-prettier": "^5.0.1", "lerna": "^8.0.0", "prettier": "^3.0.3", - "react": "18.2.0", - "react-native": "0.74.0", - "typescript": "^5.2.2" + "react": "19.2.0", + "react-native": "0.83.1", + "typescript": "~5.9.2" }, "engines": { "node": ">=18" } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -51,24 +52,28 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.5", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -88,19 +93,21 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -109,20 +116,11 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", @@ -137,6 +135,7 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { "version": "5.1.1", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -146,6 +145,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -153,108 +153,22 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { "version": "3.1.1", + "dev": true, "license": "ISC" }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.6", @@ -268,6 +182,7 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.28.6", @@ -281,70 +196,19 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-plugin-utils": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -352,6 +216,7 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -359,41 +224,34 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.27.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helpers": { - "version": "7.28.4", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -402,213 +260,189 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.13.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", - "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/traverse": "^7.28.6" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.27.1", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -617,12 +451,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { "node": ">=6.9.0" }, @@ -630,3621 +467,1992 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.27.1", + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.27.1", + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", - "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "node_modules/@babel/traverse--for-generate-function-map": { + "name": "@babel/traverse", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", + "node_modules/@commitlint/cli": { + "version": "17.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@commitlint/format": "^17.8.1", + "@commitlint/lint": "^17.8.1", + "@commitlint/load": "^17.8.1", + "@commitlint/read": "^17.8.1", + "@commitlint/types": "^17.8.1", + "execa": "^5.0.0", + "lodash.isfunction": "^3.0.9", + "resolve-from": "5.0.0", + "resolve-global": "1.0.0", + "yargs": "^17.0.0" }, - "engines": { - "node": ">=6.9.0" + "bin": { + "commitlint": "cli.js" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", + "node_modules/@commitlint/config-conventional": { + "version": "17.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "conventional-changelog-conventionalcommits": "^6.1.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", + "node_modules/@commitlint/config-validator": { + "version": "17.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@commitlint/types": "^17.8.1", + "ajv": "^8.11.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", + "node_modules/@commitlint/config-validator/node_modules/ajv": { + "version": "8.17.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", + "node_modules/@commitlint/ensure": { + "version": "17.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@commitlint/types": "^17.8.1", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", + "node_modules/@commitlint/execute-rule": { + "version": "17.8.1", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", + "node_modules/@commitlint/format": { + "version": "17.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@commitlint/types": "^17.8.1", + "chalk": "^4.1.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", + "node_modules/@commitlint/is-ignored": { + "version": "17.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@commitlint/types": "^17.8.1", + "semver": "7.5.4" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "license": "MIT", - "peer": true, + "node_modules/@commitlint/is-ignored/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "yallist": "^4.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=10" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "license": "MIT", + "node_modules/@commitlint/is-ignored/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "lru-cache": "^6.0.0" }, - "engines": { - "node": ">=6.9.0" + "bin": { + "semver": "bin/semver.js" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=10" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz", - "integrity": "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==", + "node_modules/@commitlint/lint": { + "version": "17.8.1", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@commitlint/is-ignored": "^17.8.1", + "@commitlint/parse": "^17.8.1", + "@commitlint/rules": "^17.8.1", + "@commitlint/types": "^17.8.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", - "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "node_modules/@commitlint/load": { + "version": "17.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1" + "@commitlint/config-validator": "^17.8.1", + "@commitlint/execute-rule": "^17.8.1", + "@commitlint/resolve-extends": "^17.8.1", + "@commitlint/types": "^17.8.1", + "@types/node": "20.5.1", + "chalk": "^4.1.0", + "cosmiconfig": "^8.0.0", + "cosmiconfig-typescript-loader": "^4.0.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0", + "resolve-from": "^5.0.0", + "ts-node": "^10.8.1", + "typescript": "^4.6.4 || ^5.2.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "node_modules/@commitlint/load/node_modules/@types/node": { + "version": "20.5.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@commitlint/load/node_modules/cosmiconfig-typescript-loader": { + "version": "4.4.0", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" + "node": ">=v14.21.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/node": "*", + "cosmiconfig": ">=7", + "ts-node": ">=10", + "typescript": ">=4" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", - "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "node_modules/@commitlint/load/node_modules/ts-node": { + "version": "10.9.2", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", - "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" }, - "engines": { - "node": ">=6.9.0" + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", - "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "node_modules/@commitlint/message": { + "version": "17.8.1", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", - "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "node_modules/@commitlint/parse": { + "version": "17.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/traverse": "^7.28.6" + "@commitlint/types": "^17.8.1", + "conventional-changelog-angular": "^6.0.0", + "conventional-commits-parser": "^4.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", - "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "node_modules/@commitlint/read": { + "version": "17.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/template": "^7.28.6" + "@commitlint/top-level": "^17.8.1", + "@commitlint/types": "^17.8.1", + "fs-extra": "^11.0.0", + "git-raw-commits": "^2.0.11", + "minimist": "^1.2.6" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", + "node_modules/@commitlint/read/node_modules/fs-extra": { + "version": "11.3.2", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=14.14" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", - "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "node_modules/@commitlint/read/node_modules/git-raw-commits": { + "version": "2.0.11", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "dargs": "^7.0.0", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" }, - "engines": { - "node": ">=6.9.0" + "bin": { + "git-raw-commits": "cli.js" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=10" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "node_modules/@commitlint/read/node_modules/through2": { + "version": "4.0.2", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "readable-stream": "3" } }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", - "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", + "node_modules/@commitlint/resolve-extends": { + "version": "17.8.1", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@commitlint/config-validator": "^17.8.1", + "@commitlint/types": "^17.8.1", + "import-fresh": "^3.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "node_modules/@commitlint/rules": { + "version": "17.8.1", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@commitlint/ensure": "^17.8.1", + "@commitlint/message": "^17.8.1", + "@commitlint/to-lines": "^17.8.1", + "@commitlint/types": "^17.8.1", + "execa": "^5.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", - "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "node_modules/@commitlint/to-lines": { + "version": "17.8.1", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", - "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "node_modules/@commitlint/top-level": { + "version": "17.8.1", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "find-up": "^5.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "node_modules/@commitlint/top-level/node_modules/find-up": { + "version": "5.0.0", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.27.1", + "node_modules/@commitlint/top-level/node_modules/locate-path": { + "version": "6.0.0", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-flow": "^7.27.1" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "node_modules/@commitlint/top-level/node_modules/p-limit": { + "version": "3.1.0", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", + "node_modules/@commitlint/top-level/node_modules/p-locate": { + "version": "5.0.0", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", - "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "node_modules/@commitlint/types": { + "version": "17.8.1", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "chalk": "^4.1.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=v14" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=12" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", - "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "node_modules/@emnapi/core": { + "version": "1.7.1", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", - "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", - "license": "MIT", - "peer": true, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "*" } }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", - "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "node_modules/@eslint/js": { + "version": "8.57.1", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", - "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "node_modules/@evilmartians/lefthook": { + "version": "1.13.6", + "cpu": [ + "x64", + "arm64", + "ia32" + ], + "dev": true, + "hasInstallScript": true, "license": "MIT", - "peer": true, + "os": [ + "darwin", + "linux", + "win32" + ], + "bin": { + "lefthook": "bin/index.js" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.6" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=10.10.0" } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", - "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", - "license": "MIT", - "peer": true, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "*" } }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", - "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" + "node": ">=12.22" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hutson/parse-repository-url": { + "version": "3.0.2", + "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", - "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", - "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.1", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=0.10.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "20 || >=22" } }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.28.0", + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "20 || >=22" } }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.27.1", - "license": "MIT", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/types": "^7.27.1" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=12" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" + "node": ">=12" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" + "node": ">=12" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz", - "integrity": "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", - "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=12" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=12" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.5", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "semver": "^6.3.1" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=12" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">=12" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "license": "MIT", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=8" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", - "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "sprintf-js": "~1.0.2" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=8" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@jest/types": "^29.6.3" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.5", + "node_modules/@jest/environment": { + "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", - "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", - "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", - "license": "MIT", - "peer": true, + "node_modules/@jest/transform/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/@babel/preset-env": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", - "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", + "node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.28.6", - "@babel/plugin-syntax-import-attributes": "^7.28.6", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.6", - "@babel/plugin-transform-async-to-generator": "^7.28.6", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.6", - "@babel/plugin-transform-class-properties": "^7.28.6", - "@babel/plugin-transform-class-static-block": "^7.28.6", - "@babel/plugin-transform-classes": "^7.28.6", - "@babel/plugin-transform-computed-properties": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.28.6", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.6", - "@babel/plugin-transform-exponentiation-operator": "^7.28.6", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.28.6", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.28.5", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", - "@babel/plugin-transform-numeric-separator": "^7.28.6", - "@babel/plugin-transform-object-rest-spread": "^7.28.6", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.28.6", - "@babel/plugin-transform-optional-chaining": "^7.28.6", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.28.6", - "@babel/plugin-transform-private-property-in-object": "^7.28.6", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.6", - "@babel/plugin-transform-regexp-modifiers": "^7.28.6", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.28.6", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.28.6", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", - "semver": "^6.3.1" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/preset-flow": { - "version": "7.27.1", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-flow-strip-types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register": { - "version": "7.28.3", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.6", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@commitlint/cli": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/format": "^17.8.1", - "@commitlint/lint": "^17.8.1", - "@commitlint/load": "^17.8.1", - "@commitlint/read": "^17.8.1", - "@commitlint/types": "^17.8.1", - "execa": "^5.0.0", - "lodash.isfunction": "^3.0.9", - "resolve-from": "5.0.0", - "resolve-global": "1.0.0", - "yargs": "^17.0.0" - }, - "bin": { - "commitlint": "cli.js" - }, - "engines": { - "node": ">=v14" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@commitlint/config-conventional": { - "version": "17.8.1", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { - "conventional-changelog-conventionalcommits": "^6.1.0" - }, - "engines": { - "node": ">=v14" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@commitlint/config-validator": { - "version": "17.8.1", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", "dev": true, "license": "MIT", - "dependencies": { - "@commitlint/types": "^17.8.1", - "ajv": "^8.11.0" - }, "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/config-validator/node_modules/ajv": { - "version": "8.17.1", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "node": ">=6.0.0" } }, - "node_modules/@commitlint/ensure": { - "version": "17.8.1", + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^17.8.1", - "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1", - "lodash.snakecase": "^4.1.1", - "lodash.startcase": "^4.4.0", - "lodash.upperfirst": "^4.3.1" - }, - "engines": { - "node": ">=v14" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@commitlint/execute-rule": { - "version": "17.8.1", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", "dev": true, - "license": "MIT", - "engines": { - "node": ">=v14" - } + "license": "MIT" }, - "node_modules/@commitlint/format": { - "version": "17.8.1", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^17.8.1", - "chalk": "^4.1.0" - }, - "engines": { - "node": ">=v14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@commitlint/is-ignored": { - "version": "17.8.1", + "node_modules/@lerna/create": { + "version": "8.2.4", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^17.8.1", - "semver": "7.5.4" + "@npmcli/arborist": "7.5.4", + "@npmcli/package-json": "5.2.0", + "@npmcli/run-script": "8.1.0", + "@nx/devkit": ">=17.1.2 < 21", + "@octokit/plugin-enterprise-rest": "6.0.1", + "@octokit/rest": "20.1.2", + "aproba": "2.0.0", + "byte-size": "8.1.1", + "chalk": "4.1.0", + "clone-deep": "4.0.1", + "cmd-shim": "6.0.3", + "color-support": "1.1.3", + "columnify": "1.6.0", + "console-control-strings": "^1.1.0", + "conventional-changelog-core": "5.0.1", + "conventional-recommended-bump": "7.0.1", + "cosmiconfig": "9.0.0", + "dedent": "1.5.3", + "execa": "5.0.0", + "fs-extra": "^11.2.0", + "get-stream": "6.0.0", + "git-url-parse": "14.0.0", + "glob-parent": "6.0.2", + "graceful-fs": "4.2.11", + "has-unicode": "2.0.1", + "ini": "^1.3.8", + "init-package-json": "6.0.3", + "inquirer": "^8.2.4", + "is-ci": "3.0.1", + "is-stream": "2.0.0", + "js-yaml": "4.1.0", + "libnpmpublish": "9.0.9", + "load-json-file": "6.2.0", + "make-dir": "4.0.0", + "minimatch": "3.0.5", + "multimatch": "5.0.0", + "node-fetch": "2.6.7", + "npm-package-arg": "11.0.2", + "npm-packlist": "8.0.2", + "npm-registry-fetch": "^17.1.0", + "nx": ">=17.1.2 < 21", + "p-map": "4.0.0", + "p-map-series": "2.1.0", + "p-queue": "6.6.2", + "p-reduce": "^2.1.0", + "pacote": "^18.0.6", + "pify": "5.0.0", + "read-cmd-shim": "4.0.0", + "resolve-from": "5.0.0", + "rimraf": "^4.4.1", + "semver": "^7.3.4", + "set-blocking": "^2.0.0", + "signal-exit": "3.0.7", + "slash": "^3.0.0", + "ssri": "^10.0.6", + "string-width": "^4.2.3", + "tar": "6.2.1", + "temp-dir": "1.0.0", + "through": "2.3.8", + "tinyglobby": "0.2.12", + "upath": "2.0.1", + "uuid": "^10.0.0", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "5.0.1", + "wide-align": "1.1.5", + "write-file-atomic": "5.0.1", + "write-pkg": "4.0.0", + "yargs": "17.7.2", + "yargs-parser": "21.1.1" }, "engines": { - "node": ">=v14" + "node": ">=18.0.0" } - }, - "node_modules/@commitlint/is-ignored/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@commitlint/is-ignored/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@commitlint/lint": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/is-ignored": "^17.8.1", - "@commitlint/parse": "^17.8.1", - "@commitlint/rules": "^17.8.1", - "@commitlint/types": "^17.8.1" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/load": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/config-validator": "^17.8.1", - "@commitlint/execute-rule": "^17.8.1", - "@commitlint/resolve-extends": "^17.8.1", - "@commitlint/types": "^17.8.1", - "@types/node": "20.5.1", - "chalk": "^4.1.0", - "cosmiconfig": "^8.0.0", - "cosmiconfig-typescript-loader": "^4.0.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "lodash.uniq": "^4.5.0", - "resolve-from": "^5.0.0", - "ts-node": "^10.8.1", - "typescript": "^4.6.4 || ^5.2.2" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/load/node_modules/@types/node": { - "version": "20.5.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@commitlint/load/node_modules/cosmiconfig-typescript-loader": { - "version": "4.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=v14.21.3" - }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=7", - "ts-node": ">=10", - "typescript": ">=4" - } - }, - "node_modules/@commitlint/load/node_modules/ts-node": { - "version": "10.9.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/@commitlint/message": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/parse": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^17.8.1", - "conventional-changelog-angular": "^6.0.0", - "conventional-commits-parser": "^4.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/read": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/top-level": "^17.8.1", - "@commitlint/types": "^17.8.1", - "fs-extra": "^11.0.0", - "git-raw-commits": "^2.0.11", - "minimist": "^1.2.6" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/read/node_modules/fs-extra": { - "version": "11.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@commitlint/read/node_modules/git-raw-commits": { - "version": "2.0.11", - "dev": true, - "license": "MIT", - "dependencies": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@commitlint/read/node_modules/through2": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/@commitlint/resolve-extends": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/config-validator": "^17.8.1", - "@commitlint/types": "^17.8.1", - "import-fresh": "^3.0.0", - "lodash.mergewith": "^4.6.2", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/rules": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/ensure": "^17.8.1", - "@commitlint/message": "^17.8.1", - "@commitlint/to-lines": "^17.8.1", - "@commitlint/types": "^17.8.1", - "execa": "^5.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/to-lines": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/top-level": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/top-level/node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/top-level/node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/top-level/node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/top-level/node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/types": { - "version": "17.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@emnapi/core": { - "version": "1.7.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@evilmartians/lefthook": { - "version": "1.13.6", - "cpu": [ - "x64", - "arm64", - "ia32" - ], - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "os": [ - "darwin", - "linux", - "win32" - ], - "bin": { - "lefthook": "bin/index.js" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@hutson/parse-repository-url": { - "version": "3.0.2", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { - "version": "0.7.1", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/string-locale-compare": { - "version": "1.1.0", - "dev": true, - "license": "ISC" - }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/@jest/create-cache-key-function": { - "version": "29.7.0", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@types/node": { - "version": "24.10.2", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@jest/environment/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@types/node": { - "version": "24.10.2", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types/node_modules/@types/node": { - "version": "24.10.2", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@jest/types/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@lerna/create": { - "version": "8.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@npmcli/arborist": "7.5.4", - "@npmcli/package-json": "5.2.0", - "@npmcli/run-script": "8.1.0", - "@nx/devkit": ">=17.1.2 < 21", - "@octokit/plugin-enterprise-rest": "6.0.1", - "@octokit/rest": "20.1.2", - "aproba": "2.0.0", - "byte-size": "8.1.1", - "chalk": "4.1.0", - "clone-deep": "4.0.1", - "cmd-shim": "6.0.3", - "color-support": "1.1.3", - "columnify": "1.6.0", - "console-control-strings": "^1.1.0", - "conventional-changelog-core": "5.0.1", - "conventional-recommended-bump": "7.0.1", - "cosmiconfig": "9.0.0", - "dedent": "1.5.3", - "execa": "5.0.0", - "fs-extra": "^11.2.0", - "get-stream": "6.0.0", - "git-url-parse": "14.0.0", - "glob-parent": "6.0.2", - "graceful-fs": "4.2.11", - "has-unicode": "2.0.1", - "ini": "^1.3.8", - "init-package-json": "6.0.3", - "inquirer": "^8.2.4", - "is-ci": "3.0.1", - "is-stream": "2.0.0", - "js-yaml": "4.1.0", - "libnpmpublish": "9.0.9", - "load-json-file": "6.2.0", - "make-dir": "4.0.0", - "minimatch": "3.0.5", - "multimatch": "5.0.0", - "node-fetch": "2.6.7", - "npm-package-arg": "11.0.2", - "npm-packlist": "8.0.2", - "npm-registry-fetch": "^17.1.0", - "nx": ">=17.1.2 < 21", - "p-map": "4.0.0", - "p-map-series": "2.1.0", - "p-queue": "6.6.2", - "p-reduce": "^2.1.0", - "pacote": "^18.0.6", - "pify": "5.0.0", - "read-cmd-shim": "4.0.0", - "resolve-from": "5.0.0", - "rimraf": "^4.4.1", - "semver": "^7.3.4", - "set-blocking": "^2.0.0", - "signal-exit": "3.0.7", - "slash": "^3.0.0", - "ssri": "^10.0.6", - "string-width": "^4.2.3", - "tar": "6.2.1", - "temp-dir": "1.0.0", - "through": "2.3.8", - "tinyglobby": "0.2.12", - "upath": "2.0.1", - "uuid": "^10.0.0", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "5.0.1", - "wide-align": "1.1.5", - "write-file-atomic": "5.0.1", - "write-pkg": "4.0.0", - "yargs": "17.7.2", - "yargs-parser": "21.1.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@lerna/create/node_modules/@npmcli/package-json": { - "version": "5.2.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@lerna/create/node_modules/chalk": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@lerna/create/node_modules/cosmiconfig": { - "version": "9.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lerna/create/node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@lerna/create/node_modules/execa": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@lerna/create/node_modules/execa/node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/execa/node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/get-stream": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/is-stream": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@lerna/create/node_modules/js-yaml": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@lerna/create/node_modules/make-dir": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/minimatch": { - "version": "3.0.5", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@lerna/create/node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.12", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@lerna/create/node_modules/minipass": { - "version": "4.2.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/@lerna/create/node_modules/node-fetch": { - "version": "2.6.7", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/@lerna/create/node_modules/normalize-package-data": { - "version": "6.0.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@lerna/create/node_modules/npm-package-arg": { - "version": "11.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@lerna/create/node_modules/pify": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/rimraf": { - "version": "4.4.1", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^9.2.0" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@lerna/create/node_modules/rimraf/node_modules/glob": { - "version": "9.3.5", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@lerna/create/node_modules/rimraf/node_modules/minimatch": { - "version": "8.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@lerna/create/node_modules/tinyglobby": { - "version": "0.2.12", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.3", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@emnapi/core": "^1.1.0", - "@emnapi/runtime": "^1.1.0", - "@tybys/wasm-util": "^0.9.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/agent": { - "version": "2.2.2", - "dev": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/arborist": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^3.1.1", - "@npmcli/installed-package-contents": "^2.1.0", - "@npmcli/map-workspaces": "^3.0.2", - "@npmcli/metavuln-calculator": "^7.1.1", - "@npmcli/name-from-folder": "^2.0.0", - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.1.0", - "@npmcli/query": "^3.1.0", - "@npmcli/redact": "^2.0.0", - "@npmcli/run-script": "^8.1.0", - "bin-links": "^4.0.4", - "cacache": "^18.0.3", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^7.0.2", - "json-parse-even-better-errors": "^3.0.2", - "json-stringify-nice": "^1.1.4", - "lru-cache": "^10.2.2", - "minimatch": "^9.0.4", - "nopt": "^7.2.1", - "npm-install-checks": "^6.2.0", - "npm-package-arg": "^11.0.2", - "npm-pick-manifest": "^9.0.1", - "npm-registry-fetch": "^17.0.1", - "pacote": "^18.0.6", - "parse-conflict-json": "^3.0.0", - "proc-log": "^4.2.0", - "proggy": "^2.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^3.0.1", - "read-package-json-fast": "^3.0.2", - "semver": "^7.3.7", - "ssri": "^10.0.6", - "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" - }, - "bin": { - "arborist": "bin/index.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/fs": { - "version": "3.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/git": { - "version": "5.0.8", + }, + "node_modules/@lerna/create/node_modules/@npmcli/package-json": { + "version": "5.2.0", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "ini": "^4.1.3", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", "proc-log": "^4.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^4.0.0" + "semver": "^7.5.3" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@npmcli/git/node_modules/ini": { - "version": "4.1.3", + "node_modules/@lerna/create/node_modules/chalk": { + "version": "4.1.0", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", + "node_modules/@lerna/create/node_modules/cosmiconfig": { + "version": "9.0.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@npmcli/map-workspaces": { - "version": "3.0.6", + "node_modules/@lerna/create/node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.1", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/name-from-folder": "^2.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0", - "read-package-json-fast": "^3.0.0" + "argparse": "^2.0.1" }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@npmcli/metavuln-calculator": { - "version": "7.1.1", + "node_modules/@lerna/create/node_modules/execa": { + "version": "5.0.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "cacache": "^18.0.0", - "json-parse-even-better-errors": "^3.0.0", - "pacote": "^18.0.0", - "proc-log": "^4.1.0", - "semver": "^7.3.5" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@npmcli/name-from-folder": { - "version": "2.0.0", + "node_modules/@lerna/create/node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", + "node_modules/@lerna/create/node_modules/execa/node_modules/is-stream": { + "version": "2.0.1", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@npmcli/package-json": { - "version": "5.2.1", + "node_modules/@lerna/create/node_modules/get-stream": { + "version": "6.0.0", "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", - "semver": "^7.5.3" - }, + "license": "MIT", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@npmcli/package-json/node_modules/normalize-package-data": { - "version": "6.0.2", + "node_modules/@lerna/create/node_modules/is-stream": { + "version": "2.0.0", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, + "license": "MIT", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@npmcli/promise-spawn": { - "version": "7.0.2", + "node_modules/@lerna/create/node_modules/js-yaml": { + "version": "4.1.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "which": "^4.0.0" + "argparse": "^2.0.1" }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@npmcli/query": { - "version": "3.1.0", + "node_modules/@lerna/create/node_modules/make-dir": { + "version": "4.0.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.10" + "semver": "^7.5.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/redact": { - "version": "2.0.1", - "dev": true, - "license": "ISC", - "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@npmcli/run-script": { - "version": "8.1.0", + "node_modules/@lerna/create/node_modules/minimatch": { + "version": "3.0.5", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "proc-log": "^4.0.0", - "which": "^4.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "*" } }, - "node_modules/@nx/devkit": { - "version": "20.8.3", + "node_modules/@lerna/create/node_modules/minimatch/node_modules/brace-expansion": { + "version": "1.1.12", "dev": true, "license": "MIT", "dependencies": { - "ejs": "^3.1.7", - "enquirer": "~2.3.6", - "ignore": "^5.0.4", - "minimatch": "9.0.3", - "semver": "^7.5.3", - "tmp": "~0.2.1", - "tslib": "^2.3.0", - "yargs-parser": "21.1.1" - }, - "peerDependencies": { - "nx": ">= 19 <= 21" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@nx/devkit/node_modules/minimatch": { - "version": "9.0.3", + "node_modules/@lerna/create/node_modules/minipass": { + "version": "4.2.8", "dev": true, "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/@lerna/create/node_modules/node-fetch": { + "version": "2.6.7", + "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "4.x || >=6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/@nx/nx-darwin-arm64": { - "version": "20.8.3", - "cpu": [ - "arm64" - ], + "node_modules/@lerna/create/node_modules/normalize-package-data": { + "version": "6.0.2", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, "engines": { - "node": ">= 10" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@octokit/auth-token": { - "version": "4.0.0", + "node_modules/@lerna/create/node_modules/npm-package-arg": { + "version": "11.0.2", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, "engines": { - "node": ">= 18" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@octokit/core": { - "version": "5.2.2", + "node_modules/@lerna/create/node_modules/pify": { + "version": "5.0.0", "dev": true, "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.1.0", - "@octokit/request": "^8.4.1", - "@octokit/request-error": "^5.1.1", - "@octokit/types": "^13.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - }, "engines": { - "node": ">= 18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@octokit/endpoint": { - "version": "9.0.6", + "node_modules/@lerna/create/node_modules/rimraf": { + "version": "4.4.1", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" + "glob": "^9.2.0" + }, + "bin": { + "rimraf": "dist/cjs/src/bin.js" }, "engines": { - "node": ">= 18" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@octokit/graphql": { - "version": "7.1.1", + "node_modules/@lerna/create/node_modules/rimraf/node_modules/glob": { + "version": "9.3.5", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@octokit/request": "^8.4.1", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^6.0.0" + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, "engines": { - "node": ">= 18" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-enterprise-rest": { - "version": "6.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.4.4-cjs.2", + "node_modules/@lerna/create/node_modules/rimraf/node_modules/minimatch": { + "version": "8.0.4", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@octokit/types": "^13.7.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 18" + "node": ">=16 || 14 >=14.17" }, - "peerDependencies": { - "@octokit/core": "5" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@octokit/plugin-request-log": { - "version": "4.0.1", + "node_modules/@lerna/create/node_modules/tinyglobby": { + "version": "0.2.12", "dev": true, "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, "engines": { - "node": ">= 18" + "node": ">=12.0.0" }, - "peerDependencies": { - "@octokit/core": "5" + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.3.2-cjs.1", + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.4", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.8.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "^5" + "@emnapi/core": "^1.1.0", + "@emnapi/runtime": "^1.1.0", + "@tybys/wasm-util": "^0.9.0" } }, - "node_modules/@octokit/request": { - "version": "8.4.1", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", "dev": true, "license": "MIT", "dependencies": { - "@octokit/endpoint": "^9.0.6", - "@octokit/request-error": "^5.1.1", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">= 18" + "node": ">= 8" } }, - "node_modules/@octokit/request-error": { - "version": "5.1.1", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", "dev": true, "license": "MIT", - "dependencies": { - "@octokit/types": "^13.1.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - }, "engines": { - "node": ">= 18" + "node": ">= 8" } }, - "node_modules/@octokit/rest": { - "version": "20.1.2", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", "dev": true, "license": "MIT", "dependencies": { - "@octokit/core": "^5.0.2", - "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", - "@octokit/plugin-request-log": "^4.0.0", - "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">= 18" + "node": ">= 8" } }, - "node_modules/@octokit/types": { - "version": "13.10.0", + "node_modules/@npmcli/agent": { + "version": "2.2.2", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", + "node_modules/@npmcli/arborist": { + "version": "7.5.4", "dev": true, - "license": "MIT", - "optional": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.1", + "@npmcli/installed-package-contents": "^2.1.0", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.1.1", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.1.0", + "@npmcli/query": "^3.1.0", + "@npmcli/redact": "^2.0.0", + "@npmcli/run-script": "^8.1.0", + "bin-links": "^4.0.4", + "cacache": "^18.0.3", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.2", + "json-parse-even-better-errors": "^3.0.2", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^7.2.1", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.0.1", + "npm-registry-fetch": "^17.0.1", + "pacote": "^18.0.6", + "parse-conflict-json": "^3.0.0", + "proc-log": "^4.2.0", + "proggy": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.6", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, "engines": { - "node": ">=14" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", + "node_modules/@npmcli/fs": { + "version": "3.1.1", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" }, - "funding": { - "url": "https://opencollective.com/pkgr" + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli": { - "version": "13.6.4", - "license": "MIT", + "node_modules/@npmcli/git": { + "version": "5.0.8", + "dev": true, + "license": "ISC", "dependencies": { - "@react-native-community/cli-clean": "13.6.4", - "@react-native-community/cli-config": "13.6.4", - "@react-native-community/cli-debugger-ui": "13.6.4", - "@react-native-community/cli-doctor": "13.6.4", - "@react-native-community/cli-hermes": "13.6.4", - "@react-native-community/cli-server-api": "13.6.4", - "@react-native-community/cli-tools": "13.6.4", - "@react-native-community/cli-types": "13.6.4", - "chalk": "^4.1.2", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "prompts": "^2.4.2", - "semver": "^7.5.2" - }, - "bin": { - "react-native": "build/bin.js" + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native-community/cli-clean": { - "version": "13.6.4", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "13.6.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2" - } - }, - "node_modules/@react-native-community/cli-config": { - "version": "13.6.4", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "13.6.4", - "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^4.3.0", - "fast-glob": "^3.3.2", - "joi": "^17.2.1" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-config/node_modules/argparse": { - "version": "1.0.10", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" + "node_modules/@npmcli/git/node_modules/ini": { + "version": "4.1.3", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-config/node_modules/cosmiconfig": { - "version": "5.2.1", - "license": "MIT", + "node_modules/@npmcli/installed-package-contents": { + "version": "2.1.0", + "dev": true, + "license": "ISC", "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" }, "engines": { - "node": ">=4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-config/node_modules/import-fresh": { - "version": "2.0.0", - "license": "MIT", + "node_modules/@npmcli/map-workspaces": { + "version": "3.0.6", + "dev": true, + "license": "ISC", "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" }, "engines": { - "node": ">=4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-config/node_modules/js-yaml": { - "version": "3.14.2", - "license": "MIT", + "node_modules/@npmcli/metavuln-calculator": { + "version": "7.1.1", + "dev": true, + "license": "ISC", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^18.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-config/node_modules/parse-json": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, + "node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "dev": true, + "license": "ISC", "engines": { - "node": ">=4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-config/node_modules/resolve-from": { + "node_modules/@npmcli/node-gyp": { "version": "3.0.0", - "license": "MIT", + "dev": true, + "license": "ISC", "engines": { - "node": ">=4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "13.6.4", - "license": "MIT", + "node_modules/@npmcli/package-json": { + "version": "5.2.1", + "dev": true, + "license": "ISC", "dependencies": { - "serve-static": "^1.13.1" + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-doctor": { - "version": "13.6.4", - "license": "MIT", + "node_modules/@npmcli/package-json/node_modules/normalize-package-data": { + "version": "6.0.2", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@react-native-community/cli-config": "13.6.4", - "@react-native-community/cli-platform-android": "13.6.4", - "@react-native-community/cli-platform-apple": "13.6.4", - "@react-native-community/cli-platform-ios": "13.6.4", - "@react-native-community/cli-tools": "13.6.4", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.10.0", - "execa": "^5.0.0", - "hermes-profile-transformer": "^0.0.6", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "semver": "^7.5.2", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { - "version": "4.1.1", - "license": "MIT", + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, "engines": { - "node": ">=6" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/envinfo": { - "version": "7.21.0", - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" }, "engines": { - "node": ">=4" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { - "version": "5.2.0", - "license": "MIT", + "node_modules/@npmcli/query": { + "version": "3.1.0", + "dev": true, + "license": "ISC", "dependencies": { - "ansi-regex": "^4.1.0" + "postcss-selector-parser": "^6.0.10" }, "engines": { - "node": ">=6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-hermes": { - "version": "13.6.4", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-platform-android": "13.6.4", - "@react-native-community/cli-tools": "13.6.4", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6" + "node_modules/@npmcli/redact": { + "version": "2.0.1", + "dev": true, + "license": "ISC", + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "13.6.4", - "license": "MIT", + "node_modules/@npmcli/run-script": { + "version": "8.1.0", + "dev": true, + "license": "ISC", "dependencies": { - "@react-native-community/cli-tools": "13.6.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.2.4", - "logkitty": "^0.7.1" + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@react-native-community/cli-platform-apple": { - "version": "13.6.4", + "node_modules/@nx/devkit": { + "version": "20.8.3", + "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "13.6.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.0.12", - "ora": "^5.4.1" + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 19 <= 21" } }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "13.6.4", - "license": "MIT", + "node_modules/@nx/devkit/node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "license": "ISC", "dependencies": { - "@react-native-community/cli-platform-apple": "13.6.4" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@react-native-community/cli-server-api": { - "version": "13.6.4", + "node_modules/@nx/nx-darwin-arm64": { + "version": "20.8.3", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@react-native-community/cli-debugger-ui": "13.6.4", - "@react-native-community/cli-tools": "13.6.4", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { - "version": "26.6.2", + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "dev": true, "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, "engines": { - "node": ">= 10.14.2" + "node": ">= 18" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/node": { - "version": "24.10.2", + "node_modules/@octokit/core": { + "version": "5.2.2", + "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { - "version": "15.0.20", + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "dev": true, "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { - "version": "26.6.2", + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">= 10" + "node": ">= 18" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { - "version": "17.0.2", - "license": "MIT" - }, - "node_modules/@react-native-community/cli-server-api/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, - "node_modules/@react-native-community/cli-tools": { - "version": "13.6.4", - "license": "MIT", - "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^7.5.2", - "shell-quote": "^1.7.3", - "sudo-prompt": "^9.0.0" - } + "node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "dev": true, + "license": "MIT" }, - "node_modules/@react-native-community/cli-tools/node_modules/find-up": { - "version": "5.0.0", + "node_modules/@octokit/plugin-enterprise-rest": { + "version": "6.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.4.4-cjs.2", + "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "@octokit/types": "^13.7.0" }, "engines": { - "node": ">=10" + "node": ">= 18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@octokit/core": "5" } }, - "node_modules/@react-native-community/cli-tools/node_modules/is-wsl": { - "version": "1.1.0", + "node_modules/@octokit/plugin-request-log": { + "version": "4.0.1", + "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" } }, - "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { - "version": "6.0.0", + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.3.2-cjs.1", + "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "@octokit/types": "^13.8.0" }, "engines": { - "node": ">=10" + "node": ">= 18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@octokit/core": "^5" } }, - "node_modules/@react-native-community/cli-tools/node_modules/open": { - "version": "6.4.0", + "node_modules/@octokit/request": { + "version": "8.4.1", + "dev": true, "license": "MIT", "dependencies": { - "is-wsl": "^1.1.0" + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">= 18" } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-limit": { - "version": "3.1.0", + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 18" } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { - "version": "5.0.0", + "node_modules/@octokit/rest": { + "version": "20.1.2", + "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "@octokit/core": "^5.0.2", + "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 18" } }, - "node_modules/@react-native-community/cli-types": { - "version": "13.6.4", + "node_modules/@octokit/types": { + "version": "13.10.0", + "dev": true, "license": "MIT", "dependencies": { - "joi": "^17.2.1" + "@octokit/openapi-types": "^24.2.0" } }, - "node_modules/@react-native-community/cli/node_modules/fs-extra": { - "version": "8.1.0", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, + "optional": true, "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/jsonfile": { - "version": "4.0.0", - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "node": ">=14" } }, - "node_modules/@react-native-community/cli/node_modules/universalify": { - "version": "0.1.2", + "node_modules/@pkgr/core": { + "version": "0.2.9", + "dev": true, "license": "MIT", "engines": { - "node": ">= 4.0.0" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" } }, "node_modules/@react-native/assets-registry": { - "version": "0.74.81", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.74.81", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.1.tgz", + "integrity": "sha512-AT7/T6UwQqO39bt/4UL5EXvidmrddXrt0yJa7ENXndAv+8yBzMsZn6fyiax6+ERMt9GLzAECikv3lj22cn2wJA==", + "dev": true, "license": "MIT", - "dependencies": { - "@react-native/codegen": "0.74.81" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/babel-preset": { - "version": "0.74.81", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.74.81", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" + "node": ">= 20.19.4" } }, "node_modules/@react-native/codegen": { - "version": "0.74.81", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.1.tgz", + "integrity": "sha512-FpRxenonwH+c2a5X5DZMKUD7sCudHxB3eSQPgV9R+uxd28QWslyAWrpnJM/Az96AEksHnymDzEmzq2HLX5nb+g==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", "glob": "^7.1.1", - "hermes-parser": "0.19.1", + "hermes-parser": "0.32.0", "invariant": "^2.2.4", - "jscodeshift": "^0.14.0", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1" + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { - "@babel/preset-env": "^7.1.6" + "@babel/core": "*" } }, "node_modules/@react-native/codegen/node_modules/brace-expansion": { "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -4253,6 +2461,10 @@ }, "node_modules/@react-native/codegen/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -4271,6 +2483,9 @@ }, "node_modules/@react-native/codegen/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -4279,79 +2494,90 @@ "node": "*" } }, - "node_modules/@react-native/codegen/node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.74.81", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.1.tgz", + "integrity": "sha512-FqR1ftydr08PYlRbrDF06eRiiiGOK/hNmz5husv19sK6iN5nHj1SMaCIVjkH/a5vryxEddyFhU6PzO/uf4kOHg==", + "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-server-api": "13.6.4", - "@react-native-community/cli-tools": "13.6.4", - "@react-native/dev-middleware": "0.74.81", - "@react-native/metro-babel-transformer": "0.74.81", - "chalk": "^4.0.0", - "execa": "^5.1.1", - "metro": "^0.80.3", - "metro-config": "^0.80.3", - "metro-core": "^0.80.3", - "node-fetch": "^2.2.0", - "querystring": "^0.2.1", - "readline": "^1.3.0" + "@react-native/dev-middleware": "0.83.1", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.83.3", + "metro-config": "^0.83.3", + "metro-core": "^0.83.3", + "semver": "^7.1.3" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@react-native-community/cli": "*", + "@react-native/metro-config": "*" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } } }, "node_modules/@react-native/debugger-frontend": { - "version": "0.74.81", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.1.tgz", + "integrity": "sha512-01Rn3goubFvPjHXONooLmsW0FLxJDKIUJNOlOS0cPtmmTIx9YIjxhe/DxwHXGk7OnULd7yl3aYy7WlBsEd5Xmg==", + "dev": true, "license": "BSD-3-Clause", "engines": { - "node": ">=18" + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/debugger-shell": { + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.1.tgz", + "integrity": "sha512-d+0w446Hxth5OP/cBHSSxOEpbj13p2zToUy6e5e3tTERNJ8ueGlW7iGwGTrSymNDgXXFjErX+dY4P4/3WokPIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "fb-dotslash": "0.5.8" + }, + "engines": { + "node": ">= 20.19.4" } }, "node_modules/@react-native/dev-middleware": { - "version": "0.74.81", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.1.tgz", + "integrity": "sha512-QJaSfNRzj3Lp7MmlCRgSBlt1XZ38xaBNXypXAp/3H3OdFifnTZOeYOpFmcpjcXYnDqkxetuwZg8VL65SQhB8dg==", + "dev": true, "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.74.81", - "@rnx-kit/chromium-edge-launcher": "^1.0.0", + "@react-native/debugger-frontend": "0.83.1", + "@react-native/debugger-shell": "0.83.1", "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", + "debug": "^4.4.0", + "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", - "selfsigned": "^2.4.1", - "serve-static": "^1.13.1", - "temp-dir": "^2.0.0", - "ws": "^6.2.2" + "serve-static": "^1.16.2", + "ws": "^7.5.10" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "node": ">= 20.19.4" } }, - "node_modules/@react-native/dev-middleware/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, "node_modules/@react-native/dev-middleware/node_modules/open": { "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0", @@ -4364,66 +2590,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/dev-middleware/node_modules/temp-dir": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "6.2.3", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, "node_modules/@react-native/gradle-plugin": { - "version": "0.74.81", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.83.1.tgz", + "integrity": "sha512-6ESDnwevp1CdvvxHNgXluil5OkqbjkJAkVy7SlpFsMGmVhrSxNAgD09SSRxMNdKsnLtzIvMsFCzyHLsU/S4PtQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 20.19.4" } }, "node_modules/@react-native/js-polyfills": { - "version": "0.74.81", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.74.81", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.1.tgz", + "integrity": "sha512-qgPpdWn/c5laA+3WoJ6Fak8uOm7CG50nBsLlPsF8kbT7rUHIVB9WaP6+GPsoKV/H15koW7jKuLRoNVT7c3Ht3w==", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.74.81", - "hermes-parser": "0.19.1", - "nullthrows": "^1.1.1" - }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" + "node": ">= 20.19.4" } }, "node_modules/@react-native/normalize-colors": { - "version": "0.74.81", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.1.tgz", + "integrity": "sha512-84feABbmeWo1kg81726UOlMKAhcQyFXYz2SjRKYkS78QmfhVDhJ2o/ps1VjhFfBz0i/scDwT1XNv9GwmRIghkg==", + "dev": true, "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { - "version": "0.74.81", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.83.1.tgz", + "integrity": "sha512-MdmoAbQUTOdicCocm5XAFDJWsswxk7hxa6ALnm6Y88p01HFML0W593hAn6qOt9q6IM1KbAcebtH6oOd4gcQy8w==", + "dev": true, "license": "MIT", "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { - "@types/react": "^18.2.6", + "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, @@ -4433,32 +2641,6 @@ } } }, - "node_modules/@rnx-kit/chromium-edge-launcher": { - "version": "1.0.0", - "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.0.0", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=14.15" - } - }, - "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/@types/node": { - "version": "18.19.130", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/undici-types": { - "version": "5.26.5", - "license": "MIT" - }, "node_modules/@runanywhere/core": { "resolved": "packages/core", "link": true @@ -4471,21 +2653,6 @@ "resolved": "packages/onnx", "link": true }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "license": "BSD-3-Clause" - }, "node_modules/@sigstore/bundle": { "version": "2.3.2", "dev": true, @@ -4556,10 +2723,12 @@ }, "node_modules/@sinclair/typebox": { "version": "0.27.8", + "dev": true, "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.1", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" @@ -4567,6 +2736,7 @@ }, "node_modules/@sinonjs/fake-timers": { "version": "10.3.0", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" @@ -4636,20 +2806,77 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.9.0", + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.4.0" + "@types/node": "*" } }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", + "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -4657,6 +2884,7 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.4", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -4673,56 +2901,38 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.29", + "version": "24.10.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.12.tgz", + "integrity": "sha512-68e+T28EbdmLSTkPgs3+UacC6rzmqrcWFPQs1C8mwJhI/r5Uxr0yEuQotczNRROd1gq30NGxee+fo0rSIxpyAw==", "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.14", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node-forge/node_modules/@types/node": { - "version": "24.10.2", - "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, - "node_modules/@types/node-forge/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "dev": true, "license": "MIT" }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "devOptional": true, - "license": "MIT" - }, "node_modules/@types/react": { - "version": "18.3.27", - "devOptional": true, + "version": "19.1.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz", + "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", + "dev": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" + "csstype": "^3.0.2" } }, "node_modules/@types/stack-utils": { "version": "2.0.3", + "dev": true, "license": "MIT" }, "node_modules/@types/yargs": { "version": "17.0.35", + "dev": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -4730,6 +2940,7 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.3", + "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -5014,6 +3225,7 @@ }, "node_modules/abort-controller": { "version": "3.0.0", + "dev": true, "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" @@ -5024,6 +3236,9 @@ }, "node_modules/accepts": { "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -5035,6 +3250,9 @@ }, "node_modules/accepts/node_modules/negotiator": { "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -5042,6 +3260,7 @@ }, "node_modules/acorn": { "version": "8.15.0", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5130,6 +3349,7 @@ }, "node_modules/anser": { "version": "1.4.10", + "dev": true, "license": "MIT" }, "node_modules/ansi-colors": { @@ -5165,34 +3385,9 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "license": "MIT", - "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - } - }, - "node_modules/ansi-fragments/node_modules/ansi-regex": { - "version": "4.1.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-fragments/node_modules/strip-ansi": { - "version": "5.2.0", - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5200,6 +3395,7 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -5213,6 +3409,9 @@ }, "node_modules/anymatch": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -5224,6 +3423,9 @@ }, "node_modules/anymatch/node_modules/picomatch": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -5232,10 +3434,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "license": "MIT" - }, "node_modules/aproba": { "version": "2.0.0", "dev": true, @@ -5282,34 +3480,14 @@ }, "node_modules/asap": { "version": "2.0.6", + "dev": true, "license": "MIT" }, - "node_modules/ast-types": { - "version": "0.15.2", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/astral-regex": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/async": { "version": "3.2.6", "dev": true, "license": "MIT" }, - "node_modules/async-limiter": { - "version": "1.0.1", - "license": "MIT" - }, "node_modules/asynckit": { "version": "0.4.0", "dev": true, @@ -5325,68 +3503,123 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/babel-core": { - "version": "7.0.0-bridge.0", + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.8.0" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "license": "MIT", + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", + "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "hermes-parser": "0.32.0" } }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, - "node_modules/babel-plugin-transform-flow-enums": { - "version": "0.0.2", + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/plugin-syntax-flow": "^7.12.1" + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/balanced-match": { "version": "1.0.2", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", + "dev": true, "funding": [ { "type": "github", @@ -5405,6 +3638,7 @@ }, "node_modules/baseline-browser-mapping": { "version": "2.8.32", + "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -5431,6 +3665,7 @@ }, "node_modules/bl": { "version": "4.1.0", + "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -5448,6 +3683,7 @@ }, "node_modules/braces": { "version": "3.0.3", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -5458,6 +3694,7 @@ }, "node_modules/browserslist": { "version": "4.28.0", + "dev": true, "funding": [ { "type": "opencollective", @@ -5489,6 +3726,9 @@ }, "node_modules/bser": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" @@ -5496,6 +3736,7 @@ }, "node_modules/buffer": { "version": "5.7.1", + "dev": true, "funding": [ { "type": "github", @@ -5518,6 +3759,7 @@ }, "node_modules/buffer-from": { "version": "1.1.2", + "dev": true, "license": "MIT" }, "node_modules/byte-size": { @@ -5528,13 +3770,6 @@ "node": ">=12.17" } }, - "node_modules/bytes": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cacache": { "version": "18.0.4", "dev": true, @@ -5569,33 +3804,6 @@ "node": ">= 0.4" } }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-callsite/node_modules/callsites": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/callsites": { "version": "3.1.0", "dev": true, @@ -5606,6 +3814,7 @@ }, "node_modules/camelcase": { "version": "5.3.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5629,6 +3838,7 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001757", + "dev": true, "funding": [ { "type": "opencollective", @@ -5647,6 +3857,7 @@ }, "node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5674,6 +3885,9 @@ }, "node_modules/chrome-launcher": { "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/node": "*", @@ -5688,19 +3902,24 @@ "node": ">=12.13.0" } }, - "node_modules/chrome-launcher/node_modules/@types/node": { - "version": "24.10.2", - "license": "MIT", + "node_modules/chromium-edge-launcher": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", + "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "undici-types": "~7.16.0" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" } }, - "node_modules/chrome-launcher/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, "node_modules/ci-info": { "version": "3.9.0", + "dev": true, "funding": [ { "type": "github", @@ -5739,6 +3958,7 @@ }, "node_modules/cli-cursor": { "version": "3.1.0", + "dev": true, "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" @@ -5749,6 +3969,7 @@ }, "node_modules/cli-spinners": { "version": "2.9.2", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5767,6 +3988,7 @@ }, "node_modules/cliui": { "version": "8.0.1", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -5779,6 +4001,7 @@ }, "node_modules/clone": { "version": "1.0.4", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -5786,6 +4009,7 @@ }, "node_modules/clone-deep": { "version": "4.0.1", + "dev": true, "license": "MIT", "dependencies": { "is-plain-object": "^2.0.4", @@ -5811,6 +4035,7 @@ }, "node_modules/color-convert": { "version": "2.0.1", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5821,6 +4046,7 @@ }, "node_modules/color-name": { "version": "1.1.4", + "dev": true, "license": "MIT" }, "node_modules/color-support": { @@ -5831,10 +4057,6 @@ "color-support": "bin.js" } }, - "node_modules/colorette": { - "version": "1.4.0", - "license": "MIT" - }, "node_modules/columnify": { "version": "1.6.0", "dev": true, @@ -5858,15 +4080,14 @@ "node": ">= 0.8" } }, - "node_modules/command-exists": { - "version": "1.2.9", - "license": "MIT" - }, "node_modules/commander": { - "version": "9.5.0", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || >=14" + "node": ">=18" } }, "node_modules/commitlint": { @@ -5889,10 +4110,6 @@ "dev": true, "license": "ISC" }, - "node_modules/commondir": { - "version": "1.0.1", - "license": "MIT" - }, "node_modules/compare-func": { "version": "2.0.0", "dev": true, @@ -5902,52 +4119,9 @@ "dot-prop": "^5.1.0" } }, - "node_modules/compressible": { - "version": "2.0.18", - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compressible/node_modules/mime-db": { - "version": "1.54.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", + "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -5966,6 +4140,9 @@ }, "node_modules/connect": { "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -5979,6 +4156,9 @@ }, "node_modules/connect/node_modules/debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -5986,6 +4166,9 @@ }, "node_modules/connect/node_modules/ms": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, "license": "MIT" }, "node_modules/console-control-strings": { @@ -6188,21 +4371,14 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, - "node_modules/core-js-compat": { - "version": "3.47.0", - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.3", + "dev": true, "license": "MIT" }, "node_modules/cosmiconfig": { @@ -6237,6 +4413,7 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6249,10 +4426,12 @@ }, "node_modules/cross-spawn/node_modules/isexe": { "version": "2.0.0", + "dev": true, "license": "ISC" }, "node_modules/cross-spawn/node_modules/which": { "version": "2.0.2", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -6277,7 +4456,7 @@ }, "node_modules/csstype": { "version": "3.2.3", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/dargs": { @@ -6296,12 +4475,9 @@ "node": "*" } }, - "node_modules/dayjs": { - "version": "1.11.19", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -6317,6 +4493,7 @@ }, "node_modules/decamelize": { "version": "1.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6360,18 +4537,12 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "dev": true, + "license": "MIT" }, "node_modules/defaults": { "version": "1.0.4", + "dev": true, "license": "MIT", "dependencies": { "clone": "^1.0.2" @@ -6682,12 +4853,11 @@ "node": ">=0.4.0" } }, - "node_modules/denodeify": { - "version": "1.2.1", - "license": "MIT" - }, "node_modules/depd": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -6700,6 +4870,9 @@ }, "node_modules/destroy": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8", @@ -6819,6 +4992,9 @@ }, "node_modules/ee-first": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, "license": "MIT" }, "node_modules/ejs": { @@ -6837,14 +5013,19 @@ }, "node_modules/electron-to-chromium": { "version": "1.5.262", + "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { "version": "8.0.0", + "dev": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -6852,6 +5033,7 @@ }, "node_modules/encoding": { "version": "0.1.13", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -6903,6 +5085,7 @@ }, "node_modules/error-ex": { "version": "1.3.4", + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -6910,22 +5093,14 @@ }, "node_modules/error-stack-parser": { "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, "license": "MIT", "dependencies": { "stackframe": "^1.3.4" } }, - "node_modules/errorhandler": { - "version": "1.5.1", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "dev": true, @@ -6969,6 +5144,7 @@ }, "node_modules/escalade": { "version": "3.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6976,10 +5152,14 @@ }, "node_modules/escape-html": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -7211,6 +5391,7 @@ }, "node_modules/esprima": { "version": "4.0.1", + "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -7252,6 +5433,7 @@ }, "node_modules/esutils": { "version": "2.0.3", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -7259,6 +5441,9 @@ }, "node_modules/etag": { "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -7266,6 +5451,7 @@ }, "node_modules/event-target-shim": { "version": "5.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7278,6 +5464,7 @@ }, "node_modules/execa": { "version": "5.1.1", + "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -7299,6 +5486,7 @@ }, "node_modules/exponential-backoff": { "version": "3.1.3", + "dev": true, "license": "Apache-2.0" }, "node_modules/fast-deep-equal": { @@ -7313,6 +5501,7 @@ }, "node_modules/fast-glob": { "version": "3.3.3", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -7327,6 +5516,7 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -7360,31 +5550,32 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-xml-parser": { - "version": "4.5.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.1.1" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastq": { "version": "1.19.1", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "bin": { + "dotslash": "bin/dotslash" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" @@ -7460,6 +5651,7 @@ }, "node_modules/fill-range": { "version": "7.1.1", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -7470,6 +5662,9 @@ }, "node_modules/finalhandler": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -7486,6 +5681,9 @@ }, "node_modules/finalhandler/node_modules/debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -7493,87 +5691,14 @@ }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, "license": "MIT" }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.3.0", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/statuses": { - "version": "1.5.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/find-up": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/p-locate": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/path-exists": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/find-up": { "version": "4.1.0", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -7611,15 +5736,11 @@ }, "node_modules/flow-enums-runtime": { "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "dev": true, "license": "MIT" }, - "node_modules/flow-parser": { - "version": "0.293.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/follow-redirects": { "version": "1.15.11", "dev": true, @@ -7682,6 +5803,9 @@ }, "node_modules/fresh": { "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -7746,10 +5870,15 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", + "dev": true, "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -7761,6 +5890,7 @@ }, "node_modules/function-bind": { "version": "1.1.2", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7768,6 +5898,9 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -7775,6 +5908,7 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7814,6 +5948,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-pkg-repo": { "version": "4.2.1", "dev": true, @@ -7913,6 +6057,7 @@ }, "node_modules/get-stream": { "version": "6.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -8105,6 +6250,7 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", + "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -8142,6 +6288,7 @@ }, "node_modules/has-flag": { "version": "4.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8179,6 +6326,7 @@ }, "node_modules/hasown": { "version": "2.0.2", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -8187,32 +6335,28 @@ "node": ">= 0.4" } }, + "node_modules/hermes-compiler": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.14.0.tgz", + "integrity": "sha512-clxa193o+GYYwykWVFfpHduCATz8fR5jvU7ngXpfKHj+E9hr9vjLNtdLSEe8MUbObvVexV3wcyxQ00xTPIrB1Q==", + "dev": true, + "license": "MIT" + }, "node_modules/hermes-estree": { - "version": "0.19.1", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "dev": true, "license": "MIT" }, "node_modules/hermes-parser": { - "version": "0.19.1", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.19.1" - } - }, - "node_modules/hermes-profile-transformer": { - "version": "0.0.6", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "dev": true, "license": "MIT", "dependencies": { - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/hermes-profile-transformer/node_modules/source-map": { - "version": "0.7.6", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" + "hermes-estree": "0.32.0" } }, "node_modules/hosted-git-info": { @@ -8232,15 +6376,32 @@ "license": "BSD-2-Clause" }, "node_modules/http-errors": { - "version": "2.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -8271,6 +6432,7 @@ }, "node_modules/human-signals": { "version": "2.1.0", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.17.0" @@ -8278,6 +6440,7 @@ }, "node_modules/iconv-lite": { "version": "0.6.3", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -8289,6 +6452,7 @@ }, "node_modules/ieee754": { "version": "1.2.1", + "dev": true, "funding": [ { "type": "github", @@ -8326,6 +6490,9 @@ }, "node_modules/image-size": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "dev": true, "license": "MIT", "dependencies": { "queue": "6.0.2" @@ -8380,6 +6547,7 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -8395,6 +6563,7 @@ }, "node_modules/inflight": { "version": "1.0.6", + "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -8403,6 +6572,7 @@ }, "node_modules/inherits": { "version": "2.0.4", + "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -8472,6 +6642,9 @@ }, "node_modules/invariant": { "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" @@ -8487,6 +6660,7 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", + "dev": true, "license": "MIT" }, "node_modules/is-ci": { @@ -8502,6 +6676,7 @@ }, "node_modules/is-core-module": { "version": "2.16.1", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -8513,15 +6688,9 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-directory": { - "version": "0.3.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-docker": { "version": "2.2.1", + "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -8535,6 +6704,7 @@ }, "node_modules/is-extglob": { "version": "2.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8542,6 +6712,7 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8549,6 +6720,7 @@ }, "node_modules/is-glob": { "version": "4.0.3", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -8559,6 +6731,7 @@ }, "node_modules/is-interactive": { "version": "1.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8571,6 +6744,7 @@ }, "node_modules/is-number": { "version": "7.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -8616,6 +6790,7 @@ }, "node_modules/is-plain-object": { "version": "2.0.4", + "dev": true, "license": "MIT", "dependencies": { "isobject": "^3.0.1" @@ -8634,6 +6809,7 @@ }, "node_modules/is-stream": { "version": "2.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8655,6 +6831,7 @@ }, "node_modules/is-unicode-supported": { "version": "0.1.0", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -8665,6 +6842,7 @@ }, "node_modules/is-wsl": { "version": "2.2.0", + "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0" @@ -8675,6 +6853,7 @@ }, "node_modules/isarray": { "version": "1.0.0", + "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -8687,11 +6866,49 @@ }, "node_modules/isobject": { "version": "3.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "dev": true, @@ -8738,6 +6955,7 @@ }, "node_modules/jest-environment-node": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", @@ -8751,26 +6969,43 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node/node_modules/@types/node": { - "version": "24.10.2", + "node_modules/jest-get-type": { + "version": "29.6.3", + "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, - "node_modules/jest-get-type": { - "version": "29.6.3", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, "node_modules/jest-message-util": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", @@ -8789,6 +7024,7 @@ }, "node_modules/jest-mock": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -8799,19 +7035,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/node_modules/@types/node": { - "version": "24.10.2", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, "node_modules/jest-util": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -8825,15 +7061,9 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/@types/node": { - "version": "24.10.2", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, "node_modules/jest-util/node_modules/picomatch": { "version": "2.3.1", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -8842,12 +7072,11 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jest-util/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, "node_modules/jest-validate": { "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -8863,6 +7092,9 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -8873,6 +7105,9 @@ }, "node_modules/jest-worker": { "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -8884,15 +7119,11 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-worker/node_modules/@types/node": { - "version": "24.10.2", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -8904,23 +7135,9 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-worker/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, - "node_modules/joi": { - "version": "17.13.3", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -8934,56 +7151,16 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsc-android": { - "version": "250231.0.0", - "license": "BSD-2-Clause" - }, "node_modules/jsc-safe-url": { "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "dev": true, "license": "0BSD" }, - "node_modules/jscodeshift": { - "version": "0.14.0", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.21.0", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - } - }, - "node_modules/jscodeshift/node_modules/write-file-atomic": { - "version": "2.4.3", - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, "node_modules/jsesc": { "version": "3.1.0", + "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -8999,6 +7176,7 @@ }, "node_modules/json-parse-better-errors": { "version": "1.0.2", + "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -9034,6 +7212,7 @@ }, "node_modules/json5": { "version": "2.2.3", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -9101,18 +7280,12 @@ }, "node_modules/kind-of": { "version": "6.0.3", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/kleur": { - "version": "3.0.3", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/lerna": { "version": "8.2.4", "dev": true, @@ -9522,6 +7695,9 @@ }, "node_modules/leven": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9598,6 +7774,9 @@ }, "node_modules/lighthouse-logger": { "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "debug": "^2.6.9", @@ -9606,6 +7785,9 @@ }, "node_modules/lighthouse-logger/node_modules/debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -9613,6 +7795,9 @@ }, "node_modules/lighthouse-logger/node_modules/ms": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, "license": "MIT" }, "node_modules/lines-and-columns": { @@ -9652,6 +7837,7 @@ }, "node_modules/locate-path": { "version": "5.0.0", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -9670,10 +7856,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "license": "MIT" - }, "node_modules/lodash.isfunction": { "version": "3.0.9", "dev": true, @@ -9716,6 +7898,9 @@ }, "node_modules/lodash.throttle": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "dev": true, "license": "MIT" }, "node_modules/lodash.uniq": { @@ -9730,6 +7915,7 @@ }, "node_modules/log-symbols": { "version": "4.1.0", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -9742,76 +7928,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/logkitty": { - "version": "0.7.1", - "license": "MIT", - "dependencies": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "bin": { - "logkitty": "bin/logkitty.js" - } - }, - "node_modules/logkitty/node_modules/cliui": { - "version": "6.0.0", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/logkitty/node_modules/wrap-ansi": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/y18n": { - "version": "4.0.3", - "license": "ISC" - }, - "node_modules/logkitty/node_modules/yargs": { - "version": "15.4.1", - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/yargs-parser": { - "version": "18.1.3", - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/loose-envify": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -9827,6 +7948,7 @@ }, "node_modules/make-dir": { "version": "2.1.0", + "dev": true, "license": "MIT", "dependencies": { "pify": "^4.0.1", @@ -9838,6 +7960,7 @@ }, "node_modules/make-dir/node_modules/pify": { "version": "4.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9845,6 +7968,7 @@ }, "node_modules/make-dir/node_modules/semver": { "version": "5.7.2", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver" @@ -9879,6 +8003,9 @@ }, "node_modules/makeerror": { "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" @@ -9897,6 +8024,9 @@ }, "node_modules/marky": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "dev": true, "license": "Apache-2.0" }, "node_modules/math-intrinsics": { @@ -9909,6 +8039,7 @@ }, "node_modules/memoize-one": { "version": "5.2.1", + "dev": true, "license": "MIT" }, "node_modules/meow": { @@ -9956,58 +8087,61 @@ }, "node_modules/merge-stream": { "version": "2.0.0", + "dev": true, "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/metro": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.3.tgz", + "integrity": "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", + "@babel/code-frame": "^7.24.7", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", - "debug": "^2.2.0", - "denodeify": "^1.2.1", + "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.23.1", + "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", - "jest-worker": "^29.6.3", + "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.80.12", - "metro-cache": "0.80.12", - "metro-cache-key": "0.80.12", - "metro-config": "0.80.12", - "metro-core": "0.80.12", - "metro-file-map": "0.80.12", - "metro-resolver": "0.80.12", - "metro-runtime": "0.80.12", - "metro-source-map": "0.80.12", - "metro-symbolicate": "0.80.12", - "metro-transform-plugins": "0.80.12", - "metro-transform-worker": "0.80.12", + "metro-babel-transformer": "0.83.3", + "metro-cache": "0.83.3", + "metro-cache-key": "0.83.3", + "metro-config": "0.83.3", + "metro-core": "0.83.3", + "metro-file-map": "0.83.3", + "metro-resolver": "0.83.3", + "metro-runtime": "0.83.3", + "metro-source-map": "0.83.3", + "metro-symbolicate": "0.83.3", + "metro-transform-plugins": "0.83.3", + "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", - "strip-ansi": "^6.0.0", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" @@ -10016,326 +8150,269 @@ "metro": "src/cli.js" }, "engines": { - "node": ">=18" + "node": ">=20.19.4" } }, "node_modules/metro-babel-transformer": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.3.tgz", + "integrity": "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.20.0", + "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.23.1", + "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.23.1", - "license": "MIT" - }, - "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.23.1", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.23.1" + "node": ">=20.19.4" } }, "node_modules/metro-cache": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.3.tgz", + "integrity": "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==", + "dev": true, "license": "MIT", "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", - "metro-core": "0.80.12" + "https-proxy-agent": "^7.0.5", + "metro-core": "0.83.3" }, "engines": { - "node": ">=18" + "node": ">=20.19.4" } }, "node_modules/metro-cache-key": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.3.tgz", + "integrity": "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw==", + "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": ">=20.19.4" } }, "node_modules/metro-config": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.3.tgz", + "integrity": "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==", + "dev": true, "license": "MIT", "dependencies": { "connect": "^3.6.5", - "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", - "jest-validate": "^29.6.3", - "metro": "0.80.12", - "metro-cache": "0.80.12", - "metro-core": "0.80.12", - "metro-runtime": "0.80.12" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/metro-config/node_modules/argparse": { - "version": "1.0.10", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/metro-config/node_modules/cosmiconfig": { - "version": "5.2.1", - "license": "MIT", - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/metro-config/node_modules/import-fresh": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/metro-config/node_modules/js-yaml": { - "version": "3.14.2", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/metro-config/node_modules/parse-json": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "jest-validate": "^29.7.0", + "metro": "0.83.3", + "metro-cache": "0.83.3", + "metro-core": "0.83.3", + "metro-runtime": "0.83.3", + "yaml": "^2.6.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/metro-config/node_modules/resolve-from": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=20.19.4" } }, "node_modules/metro-core": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.3.tgz", + "integrity": "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==", + "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.80.12" + "metro-resolver": "0.83.3" }, "engines": { - "node": ">=18" + "node": ">=20.19.4" } }, "node_modules/metro-file-map": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.3.tgz", + "integrity": "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==", + "dev": true, "license": "MIT", "dependencies": { - "anymatch": "^3.0.3", - "debug": "^2.2.0", + "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", - "jest-worker": "^29.6.3", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", - "node-abort-controller": "^3.1.1", "nullthrows": "^1.1.1", "walker": "^1.0.7" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/metro-file-map/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "node": ">=20.19.4" } }, - "node_modules/metro-file-map/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, "node_modules/metro-minify-terser": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.3.tgz", + "integrity": "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==", + "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.4" } }, "node_modules/metro-resolver": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.3.tgz", + "integrity": "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==", + "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": ">=20.19.4" } }, "node_modules/metro-runtime": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.3.tgz", + "integrity": "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": ">=20.19.4" } }, "node_modules/metro-source-map": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.3.tgz", + "integrity": "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", + "@babel/traverse": "^7.25.3", + "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", + "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.80.12", + "metro-symbolicate": "0.83.3", "nullthrows": "^1.1.1", - "ob1": "0.80.12", + "ob1": "0.83.3", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.4" } }, "node_modules/metro-source-map/node_modules/source-map": { "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/metro-symbolicate": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.3.tgz", + "integrity": "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw==", + "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.80.12", + "metro-source-map": "0.83.3", "nullthrows": "^1.1.1", "source-map": "^0.5.6", - "through2": "^2.0.1", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" }, "engines": { - "node": ">=18" + "node": ">=20.19.4" } }, "node_modules/metro-symbolicate/node_modules/source-map": { "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/metro-transform-plugins": { - "version": "0.80.12", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "flow-enums-runtime": "^0.0.6", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/metro-transform-worker": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.3.tgz", + "integrity": "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", - "metro": "0.80.12", - "metro-babel-transformer": "0.80.12", - "metro-cache": "0.80.12", - "metro-cache-key": "0.80.12", - "metro-minify-terser": "0.80.12", - "metro-source-map": "0.80.12", - "metro-transform-plugins": "0.80.12", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro/node_modules/ci-info": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/metro/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "node": ">=20.19.4" } }, - "node_modules/metro/node_modules/hermes-estree": { - "version": "0.23.1", - "license": "MIT" - }, - "node_modules/metro/node_modules/hermes-parser": { - "version": "0.23.1", + "node_modules/metro-transform-worker": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.3.tgz", + "integrity": "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA==", + "dev": true, "license": "MIT", "dependencies": { - "hermes-estree": "0.23.1" + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "metro": "0.83.3", + "metro-babel-transformer": "0.83.3", + "metro-cache": "0.83.3", + "metro-cache-key": "0.83.3", + "metro-minify-terser": "0.83.3", + "metro-source-map": "0.83.3", + "metro-transform-plugins": "0.83.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=20.19.4" } }, - "node_modules/metro/node_modules/ms": { + "node_modules/metro/node_modules/ci-info": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true, "license": "MIT" }, "node_modules/metro/node_modules/source-map": { "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -10343,6 +8420,7 @@ }, "node_modules/micromatch": { "version": "4.0.8", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -10354,6 +8432,7 @@ }, "node_modules/micromatch/node_modules/picomatch": { "version": "2.3.1", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -10363,17 +8442,21 @@ } }, "node_modules/mime": { - "version": "2.6.0", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, "license": "MIT", "bin": { "mime": "cli.js" }, "engines": { - "node": ">=4.0.0" + "node": ">=4" } }, "node_modules/mime-db": { "version": "1.52.0", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -10381,6 +8464,7 @@ }, "node_modules/mime-types": { "version": "2.1.35", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -10391,6 +8475,7 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10420,6 +8505,7 @@ }, "node_modules/minimist": { "version": "1.2.8", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10564,6 +8650,7 @@ }, "node_modules/mkdirp": { "version": "1.0.4", + "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -10582,6 +8669,7 @@ }, "node_modules/ms": { "version": "2.1.3", + "dev": true, "license": "MIT" }, "node_modules/multimatch": { @@ -10645,6 +8733,7 @@ }, "node_modules/negotiator": { "version": "0.6.4", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -10652,6 +8741,7 @@ }, "node_modules/neo-async": { "version": "2.6.2", + "dev": true, "license": "MIT" }, "node_modules/nitrogen": { @@ -10791,70 +8881,6 @@ "node": "^20.19.0 || ^22.12.0 || >=23" } }, - "node_modules/nocache": { - "version": "3.0.4", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/node-abort-controller": { - "version": "3.1.1", - "license": "MIT" - }, - "node_modules/node-dir": { - "version": "0.1.17", - "license": "MIT", - "dependencies": { - "minimatch": "^3.0.2" - }, - "engines": { - "node": ">= 0.10.5" - } - }, - "node_modules/node-dir/node_modules/brace-expansion": { - "version": "1.1.12", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/node-dir/node_modules/minimatch": { - "version": "3.1.2", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-forge": { - "version": "1.3.3", - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/node-gyp": { "version": "10.3.1", "dev": true, @@ -10880,6 +8906,9 @@ }, "node_modules/node-int64": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, "license": "MIT" }, "node_modules/node-machine-id": { @@ -10889,19 +8918,9 @@ }, "node_modules/node-releases": { "version": "2.0.27", + "dev": true, "license": "MIT" }, - "node_modules/node-stream-zip": { - "version": "1.15.0", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, "node_modules/nopt": { "version": "7.2.1", "dev": true, @@ -10954,6 +8973,9 @@ }, "node_modules/normalize-path": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11048,6 +9070,7 @@ }, "node_modules/npm-run-path": { "version": "4.0.1", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.0.0" @@ -11058,6 +9081,9 @@ }, "node_modules/nullthrows": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true, "license": "MIT" }, "node_modules/nx": { @@ -11196,24 +9222,23 @@ } }, "node_modules/ob1": { - "version": "0.80.12", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.3.tgz", + "integrity": "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==", + "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node": ">=20.19.4" } }, "node_modules/on-finished": { - "version": "2.4.1", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -11222,15 +9247,9 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -11238,6 +9257,7 @@ }, "node_modules/onetime": { "version": "5.1.2", + "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -11283,6 +9303,7 @@ }, "node_modules/ora": { "version": "5.4.1", + "dev": true, "license": "MIT", "dependencies": { "bl": "^4.1.0", @@ -11312,6 +9333,7 @@ }, "node_modules/p-limit": { "version": "2.3.0", + "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -11325,6 +9347,7 @@ }, "node_modules/p-locate": { "version": "4.1.0", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -11422,6 +9445,7 @@ }, "node_modules/p-try": { "version": "2.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -11540,6 +9564,9 @@ }, "node_modules/parseurl": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -11552,6 +9579,7 @@ }, "node_modules/path-exists": { "version": "4.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11559,6 +9587,7 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11566,6 +9595,7 @@ }, "node_modules/path-key": { "version": "3.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11573,6 +9603,7 @@ }, "node_modules/path-parse": { "version": "1.0.7", + "dev": true, "license": "MIT" }, "node_modules/path-scurry": { @@ -11600,6 +9631,7 @@ }, "node_modules/picocolors": { "version": "1.1.1", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -11623,6 +9655,9 @@ }, "node_modules/pirates": { "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -11686,6 +9721,7 @@ }, "node_modules/pretty-format": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -11698,6 +9734,7 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -11716,6 +9753,7 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", + "dev": true, "license": "MIT" }, "node_modules/proggy": { @@ -11728,6 +9766,7 @@ }, "node_modules/promise": { "version": "8.3.0", + "dev": true, "license": "MIT", "dependencies": { "asap": "~2.0.6" @@ -11766,17 +9805,6 @@ "node": ">=10" } }, - "node_modules/prompts": { - "version": "2.4.2", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/promzard": { "version": "1.0.2", "dev": true, @@ -11806,15 +9834,11 @@ "node": ">=6" } }, - "node_modules/querystring": { - "version": "0.2.1", - "license": "MIT", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/queue": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dev": true, "license": "MIT", "dependencies": { "inherits": "~2.0.3" @@ -11822,6 +9846,7 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", + "dev": true, "funding": [ { "type": "github", @@ -11848,23 +9873,29 @@ }, "node_modules/range-parser": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/react": { - "version": "18.2.0", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "dev": true, "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-devtools-core": { - "version": "5.3.2", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", + "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", + "dev": true, "license": "MIT", "dependencies": { "shell-quote": "^1.6.1", @@ -11873,59 +9904,61 @@ }, "node_modules/react-is": { "version": "18.3.1", + "dev": true, "license": "MIT" }, "node_modules/react-native": { - "version": "0.74.0", - "license": "MIT", - "dependencies": { - "@jest/create-cache-key-function": "^29.6.3", - "@react-native-community/cli": "13.6.4", - "@react-native-community/cli-platform-android": "13.6.4", - "@react-native-community/cli-platform-ios": "13.6.4", - "@react-native/assets-registry": "0.74.81", - "@react-native/codegen": "0.74.81", - "@react-native/community-cli-plugin": "0.74.81", - "@react-native/gradle-plugin": "0.74.81", - "@react-native/js-polyfills": "0.74.81", - "@react-native/normalize-colors": "0.74.81", - "@react-native/virtualized-lists": "0.74.81", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.83.1.tgz", + "integrity": "sha512-mL1q5HPq5cWseVhWRLl+Fwvi5z1UO+3vGOpjr+sHFwcUletPRZ5Kv+d0tUfqHmvi73/53NjlQqX1Pyn4GguUfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/create-cache-key-function": "^29.7.0", + "@react-native/assets-registry": "0.83.1", + "@react-native/codegen": "0.83.1", + "@react-native/community-cli-plugin": "0.83.1", + "@react-native/gradle-plugin": "0.83.1", + "@react-native/js-polyfills": "0.83.1", + "@react-native/normalize-colors": "0.83.1", + "@react-native/virtualized-lists": "0.83.1", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", + "babel-jest": "^29.7.0", + "babel-plugin-syntax-hermes-parser": "0.32.0", "base64-js": "^1.5.1", - "chalk": "^4.0.0", - "event-target-shim": "^5.0.1", + "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", + "glob": "^7.1.1", + "hermes-compiler": "0.14.0", "invariant": "^2.2.4", - "jest-environment-node": "^29.6.3", - "jsc-android": "^250231.0.0", + "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.80.3", - "metro-source-map": "^0.80.3", - "mkdirp": "^0.5.1", + "metro-runtime": "^0.83.3", + "metro-source-map": "^0.83.3", "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", + "pretty-format": "^29.7.0", "promise": "^8.3.0", - "react-devtools-core": "^5.0.0", + "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", - "react-shallow-renderer": "^16.15.0", "regenerator-runtime": "^0.13.2", - "scheduler": "0.24.0-canary-efb381bbf-20230505", + "scheduler": "0.27.0", + "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", - "ws": "^6.2.2", + "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "react-native": "cli.js" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { - "@types/react": "^18.2.6", - "react": "18.2.0" + "@types/react": "^19.1.1", + "react": "^19.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -11943,90 +9976,62 @@ "react-native": "*" } }, - "node_modules/react-native/node_modules/@jest/types": { - "version": "26.6.2", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/react-native/node_modules/@types/node": { - "version": "24.10.2", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/react-native/node_modules/@types/yargs": { - "version": "15.0.20", + "node_modules/react-native/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/react-native/node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", + "node_modules/react-native/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", "dependencies": { - "minimist": "^1.2.6" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/react-native/node_modules/pretty-format": { - "version": "26.6.2", - "license": "MIT", + "node_modules/react-native/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 10" - } - }, - "node_modules/react-native/node_modules/react-is": { - "version": "17.0.2", - "license": "MIT" - }, - "node_modules/react-native/node_modules/undici-types": { - "version": "7.16.0", - "license": "MIT" - }, - "node_modules/react-native/node_modules/ws": { - "version": "6.2.3", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" + "node": "*" } }, "node_modules/react-refresh": { "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/react-shallow-renderer": { - "version": "16.15.0", - "license": "MIT", - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/read": { "version": "3.0.1", "dev": true, @@ -12204,6 +10209,7 @@ }, "node_modules/readable-stream": { "version": "3.6.2", + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -12214,23 +10220,6 @@ "node": ">= 6" } }, - "node_modules/readline": { - "version": "1.3.0", - "license": "BSD" - }, - "node_modules/recast": { - "version": "0.21.5", - "license": "MIT", - "dependencies": { - "ast-types": "0.15.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, "node_modules/redent": { "version": "3.0.0", "dev": true, @@ -12243,55 +10232,14 @@ "node": ">=8" } }, - "node_modules/regenerate": { - "version": "1.4.2", - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/regenerator-runtime": { "version": "0.13.11", + "dev": true, "license": "MIT" }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.13.0", - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, "node_modules/require-directory": { "version": "2.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12305,12 +10253,9 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "license": "ISC" - }, "node_modules/resolve": { "version": "1.22.11", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -12367,6 +10312,7 @@ }, "node_modules/restore-cursor": { "version": "3.1.0", + "dev": true, "license": "MIT", "dependencies": { "onetime": "^5.1.0", @@ -12386,6 +10332,7 @@ }, "node_modules/reusify": { "version": "1.1.0", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -12394,6 +10341,7 @@ }, "node_modules/rimraf": { "version": "3.0.2", + "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -12407,6 +10355,7 @@ }, "node_modules/rimraf/node_modules/brace-expansion": { "version": "1.1.12", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -12415,6 +10364,7 @@ }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -12433,6 +10383,7 @@ }, "node_modules/rimraf/node_modules/minimatch": { "version": "3.1.2", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -12451,6 +10402,7 @@ }, "node_modules/run-parallel": { "version": "1.2.0", + "dev": true, "funding": [ { "type": "github", @@ -12480,6 +10432,7 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", + "dev": true, "funding": [ { "type": "github", @@ -12498,29 +10451,19 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/scheduler": { - "version": "0.24.0-canary-efb381bbf-20230505", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" }, "node_modules/semver": { "version": "7.7.3", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12530,22 +10473,25 @@ } }, "node_modules/send": { - "version": "0.19.0", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -12553,6 +10499,9 @@ }, "node_modules/send/node_modules/debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -12560,33 +10509,65 @@ }, "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, "license": "MIT" }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, "license": "MIT", - "bin": { - "mime": "cli.js" + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" }, "engines": { - "node": ">=4" + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, "node_modules/serialize-error": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/serve-static": { - "version": "1.16.2", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -12594,6 +10575,9 @@ }, "node_modules/serve-static/node_modules/encodeurl": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -12601,14 +10585,19 @@ }, "node_modules/set-blocking": { "version": "2.0.0", + "dev": true, "license": "ISC" }, "node_modules/setprototypeof": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, "license": "ISC" }, "node_modules/shallow-clone": { "version": "3.0.1", + "dev": true, "license": "MIT", "dependencies": { "kind-of": "^6.0.2" @@ -12619,6 +10608,7 @@ }, "node_modules/shebang-command": { "version": "2.0.0", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -12629,6 +10619,7 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12636,6 +10627,9 @@ }, "node_modules/shell-quote": { "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -12646,6 +10640,7 @@ }, "node_modules/signal-exit": { "version": "3.0.7", + "dev": true, "license": "ISC" }, "node_modules/sigstore": { @@ -12664,57 +10659,14 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "license": "MIT" - }, "node_modules/slash": { "version": "3.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "3.2.1", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "1.9.3", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.3", - "license": "MIT" - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "dev": true, @@ -12763,6 +10715,7 @@ }, "node_modules/source-map": { "version": "0.6.1", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -12770,6 +10723,9 @@ }, "node_modules/source-map-support": { "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -12825,6 +10781,7 @@ }, "node_modules/sprintf-js": { "version": "1.0.3", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/ssri": { @@ -12840,6 +10797,7 @@ }, "node_modules/stack-utils": { "version": "2.0.6", + "dev": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" @@ -12850,6 +10808,7 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12857,10 +10816,14 @@ }, "node_modules/stackframe": { "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, "license": "MIT" }, "node_modules/stacktrace-parser": { "version": "0.1.11", + "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.7.1" @@ -12871,20 +10834,25 @@ }, "node_modules/stacktrace-parser/node_modules/type-fest": { "version": "0.7.1", + "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=8" } }, "node_modules/statuses": { - "version": "2.0.1", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/string_decoder": { "version": "1.3.0", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -12892,6 +10860,7 @@ }, "node_modules/string-width": { "version": "4.2.3", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -12918,6 +10887,7 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12948,6 +10918,7 @@ }, "node_modules/strip-final-newline": { "version": "2.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -12975,22 +10946,9 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "1.1.2", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/sudo-prompt": { - "version": "9.2.1", - "license": "MIT" - }, "node_modules/supports-color": { "version": "7.2.0", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -13001,6 +10959,7 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -13084,34 +11043,72 @@ "node": ">=8" } }, - "node_modules/temp": { - "version": "0.8.4", + "node_modules/temp-dir": { + "version": "1.0.0", + "dev": true, "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "rimraf": "~2.6.2" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" }, "engines": { - "node": ">=6.0.0" + "node": ">=10" } }, - "node_modules/temp-dir": { - "version": "1.0.0", + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/temp/node_modules/brace-expansion": { + "node_modules/test-exclude/node_modules/brace-expansion": { "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "node_modules/temp/node_modules/glob": { + "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -13128,8 +11125,11 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/temp/node_modules/minimatch": { + "node_modules/test-exclude/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -13138,36 +11138,6 @@ "node": "*" } }, - "node_modules/temp/node_modules/rimraf": { - "version": "2.6.3", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/terser": { - "version": "5.44.1", - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "license": "MIT" - }, "node_modules/text-extensions": { "version": "1.9.0", "dev": true, @@ -13183,6 +11153,9 @@ }, "node_modules/throat": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true, "license": "MIT" }, "node_modules/through": { @@ -13192,6 +11165,7 @@ }, "node_modules/through2": { "version": "2.0.5", + "dev": true, "license": "MIT", "dependencies": { "readable-stream": "~2.3.6", @@ -13200,6 +11174,7 @@ }, "node_modules/through2/node_modules/readable-stream": { "version": "2.3.8", + "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -13213,10 +11188,12 @@ }, "node_modules/through2/node_modules/safe-buffer": { "version": "5.1.2", + "dev": true, "license": "MIT" }, "node_modules/through2/node_modules/string_decoder": { "version": "1.1.1", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -13247,10 +11224,14 @@ }, "node_modules/tmpl": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -13261,6 +11242,9 @@ }, "node_modules/toidentifier": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.6" @@ -13268,6 +11252,7 @@ }, "node_modules/tr46": { "version": "0.0.3", + "dev": true, "license": "MIT" }, "node_modules/treeverse": { @@ -13321,6 +11306,7 @@ }, "node_modules/tslib": { "version": "2.8.1", + "dev": true, "license": "0BSD" }, "node_modules/tuf-js": { @@ -13349,6 +11335,7 @@ }, "node_modules/type-detect": { "version": "4.0.8", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -13395,42 +11382,12 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/unique-filename": { "version": "3.0.0", "dev": true, @@ -13468,6 +11425,9 @@ }, "node_modules/unpipe": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -13484,6 +11444,7 @@ }, "node_modules/update-browserslist-db": { "version": "1.1.4", + "dev": true, "funding": [ { "type": "opencollective", @@ -13520,10 +11481,14 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", + "dev": true, "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -13563,15 +11528,11 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/vary": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vlq": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "dev": true, "license": "MIT" }, "node_modules/walk-up-path": { @@ -13581,6 +11542,9 @@ }, "node_modules/walker": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" @@ -13588,6 +11552,7 @@ }, "node_modules/wcwidth": { "version": "1.0.1", + "dev": true, "license": "MIT", "dependencies": { "defaults": "^1.0.3" @@ -13595,14 +11560,17 @@ }, "node_modules/webidl-conversions": { "version": "3.0.1", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/whatwg-fetch": { "version": "3.6.20", + "dev": true, "license": "MIT" }, "node_modules/whatwg-url": { "version": "5.0.0", + "dev": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", @@ -13623,10 +11591,6 @@ "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/which-module": { - "version": "2.0.1", - "license": "ISC" - }, "node_modules/wide-align": { "version": "1.1.5", "dev": true, @@ -13650,6 +11614,7 @@ }, "node_modules/wrap-ansi": { "version": "7.0.0", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -13682,6 +11647,7 @@ }, "node_modules/wrappy": { "version": "1.0.2", + "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { @@ -13764,6 +11730,9 @@ }, "node_modules/ws": { "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.3.0" @@ -13783,6 +11752,7 @@ }, "node_modules/xtend": { "version": "4.0.2", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4" @@ -13790,6 +11760,7 @@ }, "node_modules/y18n": { "version": "5.0.8", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -13802,6 +11773,7 @@ }, "node_modules/yaml": { "version": "2.8.2", + "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -13815,6 +11787,7 @@ }, "node_modules/yargs": { "version": "17.7.2", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -13831,6 +11804,7 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -13846,6 +11820,7 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -13864,22 +11839,22 @@ }, "packages/core": { "name": "@runanywhere/core", - "version": "0.16.11", + "version": "0.17.6", "license": "MIT", "devDependencies": { - "@types/react": "^18.2.44", + "@types/react": "~19.1.0", "nitrogen": "^0.31.10", "react-native-nitro-modules": "^0.31.10", - "typescript": "^5.2.2" + "typescript": "~5.9.2" }, "peerDependencies": { - "react": "*", - "react-native": "*", - "react-native-blob-util": "^0.19.0", - "react-native-device-info": "^11.0.0", - "react-native-fs": "^2.20.0", - "react-native-nitro-modules": "^0.31.3", - "react-native-zip-archive": "^6.1.0" + "react": ">=18.0.0", + "react-native": ">=0.74.0", + "react-native-blob-util": ">=0.19.0", + "react-native-device-info": ">=11.0.0", + "react-native-fs": ">=2.20.0", + "react-native-nitro-modules": ">=0.31.3", + "react-native-zip-archive": ">=6.1.0" }, "peerDependenciesMeta": { "react-native-blob-util": { @@ -13898,34 +11873,34 @@ }, "packages/llamacpp": { "name": "@runanywhere/llamacpp", - "version": "0.16.11", + "version": "0.17.6", "license": "MIT", "devDependencies": { "nitrogen": "^0.31.10", "react-native-nitro-modules": "^0.31.10", - "typescript": "^5.2.2" + "typescript": "~5.9.2" }, "peerDependencies": { - "@runanywhere/core": "^0.16.0", - "react": "*", - "react-native": "*", - "react-native-nitro-modules": "^0.31.3" + "@runanywhere/core": ">=0.16.0", + "react": ">=18.0.0", + "react-native": ">=0.74.0", + "react-native-nitro-modules": ">=0.31.3" } }, "packages/onnx": { "name": "@runanywhere/onnx", - "version": "0.16.11", + "version": "0.17.6", "license": "MIT", "devDependencies": { "nitrogen": "^0.31.10", "react-native-nitro-modules": "^0.31.10", - "typescript": "^5.2.2" + "typescript": "~5.9.2" }, "peerDependencies": { - "@runanywhere/core": "^0.16.0", - "react": "*", - "react-native": "*", - "react-native-nitro-modules": "^0.31.3" + "@runanywhere/core": ">=0.16.0", + "react": ">=18.0.0", + "react-native": ">=0.74.0", + "react-native-nitro-modules": ">=0.31.3" } } } diff --git a/sdk/runanywhere-react-native/packages/core/RunAnywhereCore.podspec b/sdk/runanywhere-react-native/packages/core/RunAnywhereCore.podspec index e560c0afd..ed10c3427 100644 --- a/sdk/runanywhere-react-native/packages/core/RunAnywhereCore.podspec +++ b/sdk/runanywhere-react-native/packages/core/RunAnywhereCore.podspec @@ -35,6 +35,7 @@ Pod::Spec.new do |s| "HEADER_SEARCH_PATHS" => [ "$(PODS_TARGET_SRCROOT)/cpp", "$(PODS_TARGET_SRCROOT)/cpp/bridges", + "$(PODS_TARGET_SRCROOT)/cpp/third_party", "$(PODS_TARGET_SRCROOT)/ios/Binaries/RACommons.xcframework/ios-arm64/RACommons.framework/Headers", "$(PODS_TARGET_SRCROOT)/ios/Binaries/RACommons.xcframework/ios-arm64_x86_64-simulator/RACommons.framework/Headers", "$(PODS_ROOT)/Headers/Public", diff --git a/sdk/runanywhere-react-native/packages/core/android/CMakeLists.txt b/sdk/runanywhere-react-native/packages/core/android/CMakeLists.txt index 6a08dc20c..3456e142c 100644 --- a/sdk/runanywhere-react-native/packages/core/android/CMakeLists.txt +++ b/sdk/runanywhere-react-native/packages/core/android/CMakeLists.txt @@ -5,6 +5,19 @@ set(PACKAGE_NAME runanywherecore) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_CXX_STANDARD 20) +# ============================================================================= +# nlohmann/json - Header-only JSON library for robust JSON parsing +# Used by ToolCallingBridge for parsing tool call JSON from LLM output +# ============================================================================= +include(FetchContent) +FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.11.3 + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(nlohmann_json) + # ============================================================================= # 16KB Page Alignment for Android 15+ (API 35) Compliance # Required starting November 1, 2025 for Google Play submissions @@ -73,6 +86,7 @@ include_directories( "src/main/cpp" "../cpp" "../cpp/bridges" + "../cpp/third_party" "${CMAKE_SOURCE_DIR}/include" # RAC API headers from runanywhere-commons (flat access) "${RAC_INCLUDE_DIR}" @@ -111,6 +125,9 @@ target_link_libraries( target_link_libraries(${PACKAGE_NAME} rac_commons) target_compile_definitions(${PACKAGE_NAME} PRIVATE HAS_RACOMMONS=1) +# Link nlohmann_json for ToolCallingBridge +target_link_libraries(${PACKAGE_NAME} nlohmann_json::nlohmann_json) + # 16KB page alignment - MUST be on target for Android 15+ compliance target_link_options(${PACKAGE_NAME} PRIVATE -Wl,-z,max-page-size=16384) diff --git a/sdk/runanywhere-react-native/packages/core/android/src/main/cpp/cpp-adapter.cpp b/sdk/runanywhere-react-native/packages/core/android/src/main/cpp/cpp-adapter.cpp index 04fcb76a5..b0f894a27 100644 --- a/sdk/runanywhere-react-native/packages/core/android/src/main/cpp/cpp-adapter.cpp +++ b/sdk/runanywhere-react-native/packages/core/android/src/main/cpp/cpp-adapter.cpp @@ -2,6 +2,7 @@ #include #include #include "runanywherecoreOnLoad.hpp" +#include "PlatformDownloadBridge.h" #define LOG_TAG "ArchiveJNI" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) @@ -35,6 +36,8 @@ jmethodID g_getCoreCountMethod = nullptr; jmethodID g_getArchitectureMethod = nullptr; jmethodID g_getGPUFamilyMethod = nullptr; jmethodID g_isTabletMethod = nullptr; +jmethodID g_httpDownloadMethod = nullptr; +jmethodID g_httpDownloadCancelMethod = nullptr; // HttpResponse field IDs jfieldID g_httpResponse_successField = nullptr; jfieldID g_httpResponse_statusCodeField = nullptr; @@ -101,11 +104,14 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { g_getArchitectureMethod = env->GetStaticMethodID(g_platformAdapterBridgeClass, "getArchitecture", "()Ljava/lang/String;"); g_getGPUFamilyMethod = env->GetStaticMethodID(g_platformAdapterBridgeClass, "getGPUFamily", "()Ljava/lang/String;"); g_isTabletMethod = env->GetStaticMethodID(g_platformAdapterBridgeClass, "isTablet", "()Z"); + g_httpDownloadMethod = env->GetStaticMethodID(g_platformAdapterBridgeClass, "httpDownload", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I"); + g_httpDownloadCancelMethod = env->GetStaticMethodID(g_platformAdapterBridgeClass, "httpDownloadCancel", "(Ljava/lang/String;)Z"); - if (g_secureSetMethod && g_secureGetMethod && g_getPersistentDeviceUUIDMethod && + if (g_secureSetMethod && g_secureGetMethod && g_getPersistentDeviceUUIDMethod && g_getDeviceModelMethod && g_getOSVersionMethod && g_getChipNameMethod && - g_getTotalMemoryMethod && g_getAvailableMemoryMethod && g_getCoreCountMethod && - g_getArchitectureMethod && g_getGPUFamilyMethod && g_isTabletMethod) { + g_getTotalMemoryMethod && g_getAvailableMemoryMethod && g_getCoreCountMethod && + g_getArchitectureMethod && g_getGPUFamilyMethod && g_isTabletMethod && + g_httpDownloadMethod && g_httpDownloadCancelMethod) { LOGI("PlatformAdapterBridge class and methods cached successfully"); } else { LOGE("Failed to cache some PlatformAdapterBridge methods"); @@ -269,3 +275,45 @@ extern "C" bool ArchiveUtility_extractAndroid(const char* archivePath, const cha return result == JNI_TRUE; } + +// ============================================================================= +// HTTP Download Callback Reporting (from Kotlin to C++) +// ============================================================================= + +static std::string jstringToStdString(JNIEnv* env, jstring value) { + if (value == nullptr) { + return ""; + } + const char* chars = env->GetStringUTFChars(value, nullptr); + std::string result = chars ? chars : ""; + if (chars) { + env->ReleaseStringUTFChars(value, chars); + } + return result; +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_margelo_nitro_runanywhere_PlatformAdapterBridge_nativeHttpDownloadReportProgress( + JNIEnv* env, jclass clazz, jstring taskId, jlong downloadedBytes, jlong totalBytes) { + (void)clazz; + std::string task = jstringToStdString(env, taskId); + return RunAnywhereHttpDownloadReportProgress(task.c_str(), + static_cast(downloadedBytes), + static_cast(totalBytes)); +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_margelo_nitro_runanywhere_PlatformAdapterBridge_nativeHttpDownloadReportComplete( + JNIEnv* env, jclass clazz, jstring taskId, jint result, jstring downloadedPath) { + (void)clazz; + std::string task = jstringToStdString(env, taskId); + if (downloadedPath == nullptr) { + return RunAnywhereHttpDownloadReportComplete(task.c_str(), + static_cast(result), + nullptr); + } + std::string path = jstringToStdString(env, downloadedPath); + return RunAnywhereHttpDownloadReportComplete(task.c_str(), + static_cast(result), + path.c_str()); +} diff --git a/sdk/runanywhere-react-native/packages/core/android/src/main/java/com/margelo/nitro/runanywhere/PlatformAdapterBridge.kt b/sdk/runanywhere-react-native/packages/core/android/src/main/java/com/margelo/nitro/runanywhere/PlatformAdapterBridge.kt index 19326b3f0..60e645e6f 100644 --- a/sdk/runanywhere-react-native/packages/core/android/src/main/java/com/margelo/nitro/runanywhere/PlatformAdapterBridge.kt +++ b/sdk/runanywhere-react-native/packages/core/android/src/main/java/com/margelo/nitro/runanywhere/PlatformAdapterBridge.kt @@ -10,6 +10,14 @@ package com.margelo.nitro.runanywhere import android.util.Log +import java.io.File +import java.io.FileOutputStream +import java.net.HttpURLConnection +import java.net.URL +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executors +import java.util.concurrent.Future +import java.util.concurrent.atomic.AtomicBoolean /** * JNI bridge that C++ code calls for platform operations. @@ -18,6 +26,33 @@ import android.util.Log object PlatformAdapterBridge { private const val TAG = "PlatformAdapterBridge" + private const val RAC_SUCCESS = 0 + private const val RAC_ERROR_INVALID_PARAMETER = -106 + private const val RAC_ERROR_DOWNLOAD_FAILED = -153 + private const val RAC_ERROR_CANCELLED = -380 + + private data class HttpDownloadTask( + val taskId: String, + val url: String, + val destinationPath: String, + val cancelFlag: AtomicBoolean = AtomicBoolean(false), + ) { + @Volatile + var connection: HttpURLConnection? = null + + @Volatile + var future: Future<*>? = null + } + + private val httpDownloadTasks = ConcurrentHashMap() + + private val httpDownloadExecutor = + Executors.newCachedThreadPool { runnable -> + Thread(runnable, "runanywhere-http-download").apply { + isDaemon = true + } + } + /** * Called from C++ to set a secure value */ @@ -156,6 +191,172 @@ object PlatformAdapterBridge { } } + // ======================================================================== + // HTTP Download (Async, Platform Adapter) + // ======================================================================== + + /** + * Start an HTTP download (async). + * Called from C++ platform adapter with a provided taskId. + */ + @JvmStatic + fun httpDownload(url: String, destinationPath: String, taskId: String): Int { + if (url.isBlank() || destinationPath.isBlank() || taskId.isBlank()) { + Log.e(TAG, "httpDownload invalid args (taskId=$taskId)") + return RAC_ERROR_INVALID_PARAMETER + } + + val task = HttpDownloadTask(taskId = taskId, url = url, destinationPath = destinationPath) + if (httpDownloadTasks.putIfAbsent(taskId, task) != null) { + Log.w(TAG, "httpDownload duplicate taskId=$taskId") + return RAC_ERROR_INVALID_PARAMETER + } + + return try { + val future = httpDownloadExecutor.submit { + performHttpDownload(task) + } + task.future = future + RAC_SUCCESS + } catch (e: Exception) { + httpDownloadTasks.remove(taskId) + Log.e(TAG, "httpDownload schedule failed: ${e.message}") + RAC_ERROR_DOWNLOAD_FAILED + } + } + + /** + * Cancel an HTTP download. + */ + @JvmStatic + fun httpDownloadCancel(taskId: String): Boolean { + val task = httpDownloadTasks[taskId] ?: return false + task.cancelFlag.set(true) + task.connection?.disconnect() + return true + } + + private fun performHttpDownload(task: HttpDownloadTask) { + var result = RAC_ERROR_DOWNLOAD_FAILED + var finalPath: String? = null + var tempFile: File? = null + + try { + if (task.cancelFlag.get()) { + result = RAC_ERROR_CANCELLED + return + } + + val connection = URL(task.url).openConnection() as HttpURLConnection + task.connection = connection + connection.instanceFollowRedirects = true + connection.connectTimeout = 30_000 + connection.readTimeout = 60_000 + connection.requestMethod = "GET" + connection.connect() + + val status = connection.responseCode + if (status !in 200..299) { + Log.e(TAG, "httpDownload failed status=$status url=${task.url}") + result = RAC_ERROR_DOWNLOAD_FAILED + return + } + + val totalBytes = connection.contentLengthLong.let { if (it > 0) it else 0L } + val destFile = File(task.destinationPath) + val parentDir = destFile.parentFile + parentDir?.mkdirs() + val temp = if (parentDir != null) { + File(parentDir, destFile.name + ".part") + } else { + File(destFile.path + ".part") + } + tempFile = temp + if (temp.exists()) { + temp.delete() + } + + var downloaded = 0L + var lastReported = 0L + val reportThreshold = 256 * 1024L + + connection.inputStream.use { input -> + FileOutputStream(temp).use { output -> + val buffer = ByteArray(8192) + while (true) { + if (task.cancelFlag.get()) { + result = RAC_ERROR_CANCELLED + return + } + val read = input.read(buffer) + if (read <= 0) break + output.write(buffer, 0, read) + downloaded += read + if (downloaded - lastReported >= reportThreshold) { + nativeHttpDownloadReportProgress(task.taskId, downloaded, totalBytes) + lastReported = downloaded + } + } + } + } + + if (task.cancelFlag.get()) { + result = RAC_ERROR_CANCELLED + return + } + + if (temp.exists()) { + if (destFile.exists()) { + destFile.delete() + } + val moved = temp.renameTo(destFile) + if (!moved) { + temp.copyTo(destFile, overwrite = true) + temp.delete() + } + } + + nativeHttpDownloadReportProgress(task.taskId, downloaded, totalBytes) + finalPath = destFile.absolutePath + result = RAC_SUCCESS + } catch (e: Exception) { + result = if (task.cancelFlag.get()) { + RAC_ERROR_CANCELLED + } else { + Log.e(TAG, "httpDownload failed for ${task.url}: ${e.message}") + RAC_ERROR_DOWNLOAD_FAILED + } + } finally { + task.connection?.disconnect() + task.connection = null + httpDownloadTasks.remove(task.taskId) + + if (result != RAC_SUCCESS) { + tempFile?.let { + if (it.exists()) { + it.delete() + } + } + } + + nativeHttpDownloadReportComplete(task.taskId, result, finalPath) + } + } + + @JvmStatic + private external fun nativeHttpDownloadReportProgress( + taskId: String, + downloadedBytes: Long, + totalBytes: Long, + ): Int + + @JvmStatic + private external fun nativeHttpDownloadReportComplete( + taskId: String, + result: Int, + downloadedPath: String?, + ): Int + // ======================================================================== // Device Info (Synchronous) // For device registration callback which must be synchronous @@ -389,4 +590,3 @@ object PlatformAdapterBridge { return false } } - diff --git a/sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp b/sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp index dbb75ae09..11638f87e 100644 --- a/sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp +++ b/sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp @@ -35,6 +35,7 @@ #include "bridges/HTTPBridge.hpp" #include "bridges/DownloadBridge.hpp" #include "bridges/TelemetryBridge.hpp" +#include "bridges/ToolCallingBridge.hpp" // RACommons C API headers for capability methods // These are backend-agnostic - they work with any registered backend @@ -198,6 +199,7 @@ bool extractBoolValue(const std::string& json, const std::string& key, bool defa rac_inference_framework_t frameworkFromString(const std::string& framework) { if (framework == "LlamaCpp" || framework == "llamacpp") return RAC_FRAMEWORK_LLAMACPP; if (framework == "ONNX" || framework == "onnx") return RAC_FRAMEWORK_ONNX; + if (framework == "CoreML" || framework == "coreml") return RAC_FRAMEWORK_COREML; if (framework == "FoundationModels") return RAC_FRAMEWORK_FOUNDATION_MODELS; if (framework == "SystemTTS") return RAC_FRAMEWORK_SYSTEM_TTS; return RAC_FRAMEWORK_UNKNOWN; @@ -810,6 +812,7 @@ std::shared_ptr> HybridRunAnywhereCore::getAvailableModels( case RAC_MODEL_CATEGORY_SPEECH_RECOGNITION: categoryStr = "speech-recognition"; break; case RAC_MODEL_CATEGORY_SPEECH_SYNTHESIS: categoryStr = "speech-synthesis"; break; case RAC_MODEL_CATEGORY_VISION: categoryStr = "vision"; break; + case RAC_MODEL_CATEGORY_IMAGE_GENERATION: categoryStr = "image-generation"; break; case RAC_MODEL_CATEGORY_AUDIO: categoryStr = "audio"; break; case RAC_MODEL_CATEGORY_MULTIMODAL: categoryStr = "multimodal"; break; default: categoryStr = "unknown"; break; @@ -826,6 +829,7 @@ std::shared_ptr> HybridRunAnywhereCore::getAvailableModels( switch (m.framework) { case RAC_FRAMEWORK_LLAMACPP: frameworkStr = "LlamaCpp"; break; case RAC_FRAMEWORK_ONNX: frameworkStr = "ONNX"; break; + case RAC_FRAMEWORK_COREML: frameworkStr = "CoreML"; break; case RAC_FRAMEWORK_FOUNDATION_MODELS: frameworkStr = "FoundationModels"; break; case RAC_FRAMEWORK_SYSTEM_TTS: frameworkStr = "SystemTTS"; break; default: frameworkStr = "unknown"; break; @@ -888,6 +892,7 @@ std::shared_ptr> HybridRunAnywhereCore::getModelInfo( switch (m.framework) { case RAC_FRAMEWORK_LLAMACPP: frameworkStr = "LlamaCpp"; break; case RAC_FRAMEWORK_ONNX: frameworkStr = "ONNX"; break; + case RAC_FRAMEWORK_COREML: frameworkStr = "CoreML"; break; case RAC_FRAMEWORK_FOUNDATION_MODELS: frameworkStr = "FoundationModels"; break; case RAC_FRAMEWORK_SYSTEM_TTS: frameworkStr = "SystemTTS"; break; default: frameworkStr = "unknown"; break; @@ -1653,21 +1658,38 @@ std::shared_ptr> HybridRunAnywhereCore::loadSTTModel( const std::string& modelType, const std::optional& configJson) { return Promise::async([this, modelPath, modelType]() -> bool { - LOGI("Loading STT model: %s", modelPath.c_str()); + try { + LOGI("Loading STT model: %s", modelPath.c_str()); - rac_handle_t handle = getGlobalSTTHandle(); - if (!handle) { - setLastError("Failed to create STT component. Is an STT backend registered?"); - throw std::runtime_error("STT backend not registered. Install @runanywhere/onnx."); - } + if (modelPath.empty()) { + setLastError("STT model path is empty. Download the model first."); + return false; + } - rac_result_t result = rac_stt_component_load_model(handle, modelPath.c_str(), modelPath.c_str(), modelType.c_str()); - if (result != RAC_SUCCESS) { - throw std::runtime_error("Failed to load STT model: " + std::to_string(result)); - } + rac_handle_t handle = getGlobalSTTHandle(); + if (!handle) { + setLastError("Failed to create STT component. Is an STT backend registered?"); + return false; + } - LOGI("STT model loaded successfully"); - return true; + rac_result_t result = rac_stt_component_load_model( + handle, modelPath.c_str(), modelPath.c_str(), modelType.c_str()); + if (result != RAC_SUCCESS) { + setLastError("Failed to load STT model: " + std::to_string(result)); + return false; + } + + LOGI("STT model loaded successfully"); + return true; + } catch (const std::exception& e) { + std::string msg = e.what(); + LOGI("loadSTTModel exception: %s", msg.c_str()); + setLastError(msg); + return false; + } catch (...) { + setLastError("STT model load failed (unknown error)"); + return false; + } }); } @@ -1704,57 +1726,71 @@ std::shared_ptr> HybridRunAnywhereCore::transcribe( double sampleRate, const std::optional& language) { return Promise::async([this, audioBase64, sampleRate, language]() -> std::string { - LOGI("Transcribing audio (base64)..."); + try { + LOGI("Transcribing audio (base64)..."); - rac_handle_t handle = getGlobalSTTHandle(); - if (!handle) { - throw std::runtime_error("STT component not available. Is an STT backend registered?"); - } + rac_handle_t handle = getGlobalSTTHandle(); + if (!handle) { + return "{\"error\":\"STT component not available. Is an STT backend registered?\"}"; + } - if (rac_stt_component_is_loaded(handle) != RAC_TRUE) { - throw std::runtime_error("No STT model loaded. Call loadSTTModel first."); - } + if (rac_stt_component_is_loaded(handle) != RAC_TRUE) { + return "{\"error\":\"No STT model loaded. Call loadSTTModel first.\"}"; + } - // Decode base64 audio data - std::vector audioData = base64Decode(audioBase64); - if (audioData.empty()) { - throw std::runtime_error("Failed to decode base64 audio data"); - } + // Decode base64 audio data + std::vector audioData = base64Decode(audioBase64); + if (audioData.empty()) { + return "{\"error\":\"Failed to decode base64 audio data\"}"; + } - LOGI("Decoded %zu bytes of audio data", audioData.size()); + // Minimum ~0.05s at 16kHz 16-bit to avoid backend crash on tiny input + if (audioData.size() < 1600) { + return "{\"text\":\"\",\"confidence\":0.0}"; + } - // Set up transcription options - rac_stt_options_t options = RAC_STT_OPTIONS_DEFAULT; - options.sample_rate = static_cast(sampleRate > 0 ? sampleRate : 16000); - options.audio_format = RAC_AUDIO_FORMAT_PCM; - if (language.has_value() && !language->empty()) { - options.language = language->c_str(); - } + LOGI("Decoded %zu bytes of audio data", audioData.size()); - // Transcribe - rac_stt_result_t result = {}; - rac_result_t status = rac_stt_component_transcribe( - handle, - audioData.data(), - audioData.size(), - &options, - &result - ); + // Set up transcription options + rac_stt_options_t options = RAC_STT_OPTIONS_DEFAULT; + options.sample_rate = static_cast(sampleRate > 0 ? sampleRate : 16000); + options.audio_format = RAC_AUDIO_FORMAT_PCM; + if (language.has_value() && !language->empty()) { + options.language = language->c_str(); + } - if (status != RAC_SUCCESS) { - throw std::runtime_error("Transcription failed with error code: " + std::to_string(status)); - } + // Transcribe + rac_stt_result_t result = {}; + rac_result_t status = rac_stt_component_transcribe( + handle, + audioData.data(), + audioData.size(), + &options, + &result + ); + + if (status != RAC_SUCCESS) { + rac_stt_result_free(&result); + return "{\"error\":\"Transcription failed with error code: " + std::to_string(status) + "\"}"; + } - std::string transcribedText; - if (result.text) { - transcribedText = std::string(result.text); - } + std::string transcribedText; + if (result.text) { + transcribedText = std::string(result.text); + } + float confidence = result.confidence; - // Free the result - rac_stt_result_free(&result); + rac_stt_result_free(&result); - LOGI("Transcription result: %s", transcribedText.c_str()); - return transcribedText; + LOGI("Transcription result: %s", transcribedText.c_str()); + return "{\"text\":" + jsonString(transcribedText) + ",\"confidence\":" + std::to_string(confidence) + "}"; + } catch (const std::exception& e) { + std::string msg = e.what(); + LOGI("Transcribe exception: %s", msg.c_str()); + return "{\"error\":" + jsonString(msg) + "}"; + } catch (...) { + return "{\"error\":\"Transcription failed (unknown error)\"}"; + } }); } @@ -1762,137 +1798,133 @@ std::shared_ptr> HybridRunAnywhereCore::transcribeFile( const std::string& filePath, const std::optional& language) { return Promise::async([this, filePath, language]() -> std::string { - LOGI("Transcribing file: %s", filePath.c_str()); - - rac_handle_t handle = getGlobalSTTHandle(); - if (!handle) { - throw std::runtime_error("STT component not available. Is an STT backend registered?"); - } + try { + LOGI("Transcribing file: %s", filePath.c_str()); - if (rac_stt_component_is_loaded(handle) != RAC_TRUE) { - throw std::runtime_error("No STT model loaded. Call loadSTTModel first."); - } + rac_handle_t handle = getGlobalSTTHandle(); + if (!handle) { + return "{\"error\":\"STT component not available. Is an STT backend registered?\"}"; + } - // Open the file - FILE* file = fopen(filePath.c_str(), "rb"); - if (!file) { - throw std::runtime_error("Failed to open audio file: " + filePath); - } + if (rac_stt_component_is_loaded(handle) != RAC_TRUE) { + return "{\"error\":\"No STT model loaded. Call loadSTTModel first.\"}"; + } - // Get file size - fseek(file, 0, SEEK_END); - long fileSize = ftell(file); - fseek(file, 0, SEEK_SET); + // Open the file + FILE* file = fopen(filePath.c_str(), "rb"); + if (!file) { + return "{\"error\":\"Failed to open audio file. Check that the path is valid.\"}"; + } - if (fileSize <= 0) { - fclose(file); - throw std::runtime_error("Audio file is empty: " + filePath); - } + // Get file size + fseek(file, 0, SEEK_END); + long fileSize = ftell(file); + fseek(file, 0, SEEK_SET); - LOGI("File size: %ld bytes", fileSize); + if (fileSize <= 0) { + fclose(file); + return "{\"error\":\"Audio file is empty\"}"; + } - // Read the entire file into memory - std::vector fileData(fileSize); - size_t bytesRead = fread(fileData.data(), 1, fileSize, file); - fclose(file); + LOGI("File size: %ld bytes", fileSize); - if (bytesRead != static_cast(fileSize)) { - throw std::runtime_error("Failed to read audio file completely"); - } + // Read the entire file into memory + std::vector fileData(static_cast(fileSize)); + size_t bytesRead = fread(fileData.data(), 1, static_cast(fileSize), file); + fclose(file); - // Parse WAV header to extract audio data - // WAV header: RIFF chunk (12 bytes) + fmt chunk + data chunk - // We need to find the "data" chunk and extract PCM audio + if (bytesRead != static_cast(fileSize)) { + return "{\"error\":\"Failed to read audio file completely\"}"; + } - const uint8_t* data = fileData.data(); - size_t dataSize = fileData.size(); - int32_t sampleRate = 16000; + // Parse WAV header to extract audio data + const uint8_t* data = fileData.data(); + size_t dataSize = fileData.size(); + int32_t sampleRate = 16000; - // Check RIFF header - if (dataSize < 44) { - throw std::runtime_error("File too small to be a valid WAV file"); - } + if (dataSize < 44) { + return "{\"error\":\"File too small to be a valid WAV file\"}"; + } + if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F') { + return "{\"error\":\"Invalid WAV file: missing RIFF header\"}"; + } + if (data[8] != 'W' || data[9] != 'A' || data[10] != 'V' || data[11] != 'E') { + return "{\"error\":\"Invalid WAV file: missing WAVE format\"}"; + } - // Check "RIFF" signature - if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F') { - throw std::runtime_error("Invalid WAV file: missing RIFF header"); - } + size_t pos = 12; + size_t audioDataOffset = 0; + size_t audioDataSize = 0; - // Check "WAVE" format - if (data[8] != 'W' || data[9] != 'A' || data[10] != 'V' || data[11] != 'E') { - throw std::runtime_error("Invalid WAV file: missing WAVE format"); - } + while (pos + 8 < dataSize) { + char chunkId[5] = {0}; + memcpy(chunkId, &data[pos], 4); + uint32_t chunkSize = *reinterpret_cast(&data[pos + 4]); - // Find "fmt " and "data" chunks - size_t pos = 12; - size_t audioDataOffset = 0; - size_t audioDataSize = 0; + if (strcmp(chunkId, "fmt ") == 0) { + if (pos + 8 + chunkSize <= dataSize && chunkSize >= 16) { + sampleRate = *reinterpret_cast(&data[pos + 12]); + if (sampleRate <= 0 || sampleRate > 48000) sampleRate = 16000; + LOGI("WAV sample rate: %d Hz", sampleRate); + } + } else if (strcmp(chunkId, "data") == 0) { + audioDataOffset = pos + 8; + audioDataSize = chunkSize; + LOGI("Found audio data: offset=%zu, size=%zu", audioDataOffset, audioDataSize); + break; + } - while (pos + 8 < dataSize) { - char chunkId[5] = {0}; - memcpy(chunkId, &data[pos], 4); - uint32_t chunkSize = *reinterpret_cast(&data[pos + 4]); + pos += 8 + chunkSize; + if (chunkSize % 2 != 0) pos++; + } - if (strcmp(chunkId, "fmt ") == 0) { - // Parse fmt chunk - if (pos + 8 + chunkSize <= dataSize && chunkSize >= 16) { - // Bytes 12-13: Audio format (1 = PCM) - // Bytes 14-15: Number of channels - // Bytes 16-19: Sample rate - sampleRate = *reinterpret_cast(&data[pos + 12]); - LOGI("WAV sample rate: %d Hz", sampleRate); - } - } else if (strcmp(chunkId, "data") == 0) { - // Found data chunk - audioDataOffset = pos + 8; - audioDataSize = chunkSize; - LOGI("Found audio data: offset=%zu, size=%zu", audioDataOffset, audioDataSize); - break; + if (audioDataSize == 0 || audioDataOffset + audioDataSize > dataSize) { + return "{\"error\":\"Could not find valid audio data in WAV file\"}"; } - pos += 8 + chunkSize; - // Align to 2-byte boundary - if (chunkSize % 2 != 0) pos++; - } + // Minimum ~0.1s at 16kHz 16-bit; avoid empty or tiny buffers + if (audioDataSize < 3200) { + return "{\"error\":\"Recording too short to transcribe\"}"; + } - if (audioDataSize == 0 || audioDataOffset + audioDataSize > dataSize) { - throw std::runtime_error("Could not find valid audio data in WAV file"); - } + rac_stt_options_t options = RAC_STT_OPTIONS_DEFAULT; + options.sample_rate = sampleRate; + options.audio_format = RAC_AUDIO_FORMAT_PCM; + if (language.has_value() && !language->empty()) { + options.language = language->c_str(); + } - // Set up transcription options - rac_stt_options_t options = RAC_STT_OPTIONS_DEFAULT; - options.sample_rate = sampleRate; - options.audio_format = RAC_AUDIO_FORMAT_WAV; // Tell the backend it's WAV format - if (language.has_value() && !language->empty()) { - options.language = language->c_str(); - } + LOGI("Transcribing %zu bytes of audio at %d Hz", audioDataSize, sampleRate); - LOGI("Transcribing %zu bytes of audio at %d Hz", audioDataSize, sampleRate); + rac_stt_result_t result = {}; + rac_result_t status = rac_stt_component_transcribe( + handle, + &data[audioDataOffset], + audioDataSize, + &options, + &result + ); - // Transcribe - pass the raw PCM data (after WAV header) - rac_stt_result_t result = {}; - rac_result_t status = rac_stt_component_transcribe( - handle, - &data[audioDataOffset], - audioDataSize, - &options, - &result - ); + if (status != RAC_SUCCESS) { + rac_stt_result_free(&result); + return "{\"error\":\"Transcription failed with error code: " + std::to_string(status) + "\"}"; + } - if (status != RAC_SUCCESS) { - throw std::runtime_error("Transcription failed with error code: " + std::to_string(status)); - } + std::string transcribedText; + if (result.text) { + transcribedText = std::string(result.text); + } - std::string transcribedText; - if (result.text) { - transcribedText = std::string(result.text); + rac_stt_result_free(&result); + LOGI("Transcription result: %s", transcribedText.c_str()); + return transcribedText; + } catch (const std::exception& e) { + std::string msg = e.what(); + LOGI("TranscribeFile exception: %s", msg.c_str()); + return "{\"error\":\"" + msg + "\"}"; + } catch (...) { + return "{\"error\":\"Transcription failed (unknown error)\"}"; } - - // Free the result - rac_stt_result_free(&result); - - LOGI("Transcription result: %s", transcribedText.c_str()); - return transcribedText; }); } @@ -2569,4 +2601,71 @@ std::shared_ptr> HybridRunAnywhereCore::isTelemetryInitialized() { }); } +// ============================================================================ +// Tool Calling +// +// ARCHITECTURE: +// - C++ (ToolCallingBridge): Parses tags from LLM output. +// This is the SINGLE SOURCE OF TRUTH for parsing, ensuring consistency. +// +// - TypeScript (RunAnywhere+ToolCalling.ts): Handles tool registry, executor +// storage, prompt formatting, and orchestration. Executors MUST stay in +// TypeScript because they need JavaScript APIs (fetch, device APIs, etc.). +// +// Only parseToolCallFromOutput is implemented in C++. All other tool calling +// functionality (registration, execution, prompt formatting) is in TypeScript. +// ============================================================================ + +std::shared_ptr> HybridRunAnywhereCore::parseToolCallFromOutput(const std::string& llmOutput) { + return Promise::async([llmOutput]() -> std::string { + LOGD("parseToolCallFromOutput: input length=%zu", llmOutput.length()); + + // Use ToolCallingBridge for parsing - single source of truth + // This ensures consistent tag parsing across all platforms + return ::runanywhere::bridges::ToolCallingBridge::shared().parseToolCall(llmOutput); + }); +} + +std::shared_ptr> HybridRunAnywhereCore::formatToolsForPrompt( + const std::string& toolsJson, + const std::string& format +) { + return Promise::async([toolsJson, format]() -> std::string { + LOGD("formatToolsForPrompt: tools length=%zu, format=%s", toolsJson.length(), format.c_str()); + + // Use C++ single source of truth for prompt formatting + // This eliminates duplicate TypeScript implementation + return ::runanywhere::bridges::ToolCallingBridge::shared().formatToolsPrompt(toolsJson, format); + }); +} + +std::shared_ptr> HybridRunAnywhereCore::buildInitialPrompt( + const std::string& userPrompt, + const std::string& toolsJson, + const std::string& optionsJson +) { + return Promise::async([userPrompt, toolsJson, optionsJson]() -> std::string { + LOGD("buildInitialPrompt: prompt length=%zu, tools length=%zu", userPrompt.length(), toolsJson.length()); + + // Use C++ single source of truth for initial prompt building + return ::runanywhere::bridges::ToolCallingBridge::shared().buildInitialPrompt(userPrompt, toolsJson, optionsJson); + }); +} + +std::shared_ptr> HybridRunAnywhereCore::buildFollowupPrompt( + const std::string& originalPrompt, + const std::string& toolsPrompt, + const std::string& toolName, + const std::string& resultJson, + bool keepToolsAvailable +) { + return Promise::async([originalPrompt, toolsPrompt, toolName, resultJson, keepToolsAvailable]() -> std::string { + LOGD("buildFollowupPrompt: tool=%s, keepTools=%d", toolName.c_str(), keepToolsAvailable); + + // Use C++ single source of truth for follow-up prompt building + return ::runanywhere::bridges::ToolCallingBridge::shared().buildFollowupPrompt( + originalPrompt, toolsPrompt, toolName, resultJson, keepToolsAvailable); + }); +} + } // namespace margelo::nitro::runanywhere diff --git a/sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.hpp b/sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.hpp index 5291493e1..8d17aa6b9 100644 --- a/sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.hpp +++ b/sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.hpp @@ -257,6 +257,17 @@ class HybridRunAnywhereCore : public HybridRunAnywhereCoreSpec { std::shared_ptr> voiceAgentSynthesizeSpeech(const std::string& text) override; std::shared_ptr> cleanupVoiceAgent() override; + // ============================================================================ + // Tool Calling - Delegates to ToolCallingBridge + // Single source of truth for parsing, formatting, and prompt building + // Tool registry and execution are in TypeScript (RunAnywhere+ToolCalling.ts) + // ============================================================================ + + std::shared_ptr> parseToolCallFromOutput(const std::string& llmOutput) override; + std::shared_ptr> formatToolsForPrompt(const std::string& toolsJson, const std::string& format) override; + std::shared_ptr> buildInitialPrompt(const std::string& userPrompt, const std::string& toolsJson, const std::string& optionsJson) override; + std::shared_ptr> buildFollowupPrompt(const std::string& originalPrompt, const std::string& toolsPrompt, const std::string& toolName, const std::string& resultJson, bool keepToolsAvailable) override; + private: // Thread safety std::mutex initMutex_; diff --git a/sdk/runanywhere-react-native/packages/core/cpp/bridges/InitBridge.cpp b/sdk/runanywhere-react-native/packages/core/cpp/bridges/InitBridge.cpp index 8b9c46222..131c0e25f 100644 --- a/sdk/runanywhere-react-native/packages/core/cpp/bridges/InitBridge.cpp +++ b/sdk/runanywhere-react-native/packages/core/cpp/bridges/InitBridge.cpp @@ -7,13 +7,16 @@ */ #include "InitBridge.hpp" +#include "PlatformDownloadBridge.h" #include "rac_model_paths.h" #include "rac_environment.h" // For rac_sdk_init, rac_sdk_config_t #include #include #include +#include #include #include +#include // Platform-specific logging and bridges #if defined(ANDROID) || defined(__ANDROID__) @@ -48,6 +51,8 @@ extern jmethodID g_getCoreCountMethod; extern jmethodID g_getArchitectureMethod; extern jmethodID g_getGPUFamilyMethod; extern jmethodID g_isTabletMethod; +extern jmethodID g_httpDownloadMethod; +extern jmethodID g_httpDownloadCancelMethod; // HttpResponse field IDs extern jfieldID g_httpResponse_successField; extern jfieldID g_httpResponse_statusCodeField; @@ -427,6 +432,62 @@ namespace AndroidBridge { jboolean result = env->CallStaticBooleanMethod(g_platformAdapterBridgeClass, g_isTabletMethod); return result == JNI_TRUE; } + + rac_result_t httpDownload(const char* url, const char* destinationPath, const char* taskId) { + JNIEnv* env = getJNIEnv(); + if (!env) return RAC_ERROR_NOT_SUPPORTED; + + if (!g_platformAdapterBridgeClass || !g_httpDownloadMethod) { + LOGE("PlatformAdapterBridge class or httpDownload method not cached"); + return RAC_ERROR_NOT_SUPPORTED; + } + + jstring jUrl = env->NewStringUTF(url ? url : ""); + jstring jDest = env->NewStringUTF(destinationPath ? destinationPath : ""); + jstring jTaskId = env->NewStringUTF(taskId ? taskId : ""); + + jint result = env->CallStaticIntMethod(g_platformAdapterBridgeClass, + g_httpDownloadMethod, + jUrl, + jDest, + jTaskId); + + env->DeleteLocalRef(jUrl); + env->DeleteLocalRef(jDest); + env->DeleteLocalRef(jTaskId); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + LOGE("Exception in httpDownload"); + return RAC_ERROR_DOWNLOAD_FAILED; + } + + return static_cast(result); + } + + bool httpDownloadCancel(const char* taskId) { + JNIEnv* env = getJNIEnv(); + if (!env) return false; + + if (!g_platformAdapterBridgeClass || !g_httpDownloadCancelMethod) { + LOGE("PlatformAdapterBridge class or httpDownloadCancel method not cached"); + return false; + } + + jstring jTaskId = env->NewStringUTF(taskId ? taskId : ""); + jboolean result = env->CallStaticBooleanMethod(g_platformAdapterBridgeClass, + g_httpDownloadCancelMethod, + jTaskId); + env->DeleteLocalRef(jTaskId); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + LOGE("Exception in httpDownloadCancel"); + return false; + } + + return result == JNI_TRUE; + } } // namespace AndroidBridge #elif defined(__APPLE__) #include @@ -461,6 +522,14 @@ extern "C" { char** outResponseBody, char** outErrorMessage ); + + int PlatformAdapter_httpDownload( + const char* url, + const char* destinationPath, + const char* taskId + ); + + bool PlatformAdapter_httpDownloadCancel(const char* taskId); } #define LOGI(...) printf("[InitBridge] "); printf(__VA_ARGS__); printf("\n") #define LOGD(...) printf("[InitBridge DEBUG] "); printf(__VA_ARGS__); printf("\n") @@ -483,6 +552,20 @@ namespace bridges { static PlatformCallbacks* g_platformCallbacks = nullptr; +// ============================================================================= +// HTTP download callback state (platform adapter) +// ============================================================================= + +struct http_download_context { + rac_http_progress_callback_fn progress_callback; + rac_http_complete_callback_fn complete_callback; + void* user_data; +}; + +static std::mutex g_http_download_mutex; +static std::unordered_map g_http_downloads; +static std::atomic g_http_download_counter{0}; + // ============================================================================= // C Callback Implementations (called by RACommons) // ============================================================================= @@ -702,6 +785,134 @@ static void platformTrackErrorCallback(const char* errorJson, void* userData) { } } +// ============================================================================= +// HTTP Download Callbacks (Platform Adapter) +// ============================================================================= + +static int reportHttpDownloadProgressInternal(const char* task_id, + int64_t downloaded_bytes, + int64_t total_bytes) { + if (!task_id) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + std::lock_guard lock(g_http_download_mutex); + auto it = g_http_downloads.find(task_id); + if (it == g_http_downloads.end()) { + return RAC_ERROR_NOT_FOUND; + } + + if (it->second.progress_callback) { + it->second.progress_callback(downloaded_bytes, total_bytes, it->second.user_data); + } + + return RAC_SUCCESS; +} + +static int reportHttpDownloadCompleteInternal(const char* task_id, + int result, + const char* downloaded_path) { + if (!task_id) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + http_download_context ctx{}; + { + std::lock_guard lock(g_http_download_mutex); + auto it = g_http_downloads.find(task_id); + if (it == g_http_downloads.end()) { + return RAC_ERROR_NOT_FOUND; + } + ctx = it->second; + g_http_downloads.erase(it); + } + + if (ctx.complete_callback) { + ctx.complete_callback(static_cast(result), downloaded_path, ctx.user_data); + } + + return RAC_SUCCESS; +} + +static rac_result_t platformHttpDownloadCallback(const char* url, + const char* destination_path, + rac_http_progress_callback_fn progress_callback, + rac_http_complete_callback_fn complete_callback, + void* callback_user_data, + char** out_task_id, + void* user_data) { + (void)user_data; + + if (!url || !destination_path || !out_task_id) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + std::string task_id = + "http_" + std::to_string(g_http_download_counter.fetch_add(1, std::memory_order_relaxed)); + + *out_task_id = strdup(task_id.c_str()); + if (!*out_task_id) { + return RAC_ERROR_OUT_OF_MEMORY; + } + + { + std::lock_guard lock(g_http_download_mutex); + g_http_downloads[task_id] = {progress_callback, complete_callback, callback_user_data}; + } + + rac_result_t start_result = RAC_ERROR_NOT_SUPPORTED; + +#if defined(ANDROID) || defined(__ANDROID__) + start_result = AndroidBridge::httpDownload(url, destination_path, task_id.c_str()); +#elif defined(__APPLE__) + start_result = static_cast( + PlatformAdapter_httpDownload(url, destination_path, task_id.c_str())); +#endif + + if (start_result != RAC_SUCCESS) { + http_download_context ctx{}; + { + std::lock_guard lock(g_http_download_mutex); + auto it = g_http_downloads.find(task_id); + if (it != g_http_downloads.end()) { + ctx = it->second; + g_http_downloads.erase(it); + } + } + + if (ctx.complete_callback) { + ctx.complete_callback(start_result, nullptr, ctx.user_data); + } + } + + return start_result; +} + +static rac_result_t platformHttpDownloadCancelCallback(const char* task_id, void* user_data) { + (void)user_data; + + if (!task_id) { + return RAC_ERROR_INVALID_ARGUMENT; + } + + { + std::lock_guard lock(g_http_download_mutex); + if (g_http_downloads.find(task_id) == g_http_downloads.end()) { + return RAC_ERROR_NOT_FOUND; + } + } + + bool cancelled = false; + +#if defined(ANDROID) || defined(__ANDROID__) + cancelled = AndroidBridge::httpDownloadCancel(task_id); +#elif defined(__APPLE__) + cancelled = PlatformAdapter_httpDownloadCancel(task_id); +#endif + + return cancelled ? RAC_SUCCESS : RAC_ERROR_CANCELLED; +} + // ============================================================================= // InitBridge Implementation // ============================================================================= @@ -757,9 +968,9 @@ void InitBridge::registerPlatformAdapter() { // Error tracking adapter_.track_error = platformTrackErrorCallback; - // HTTP download (handled by JS layer) - adapter_.http_download = nullptr; - adapter_.http_download_cancel = nullptr; + // HTTP download (platform adapter) + adapter_.http_download = platformHttpDownloadCallback; + adapter_.http_download_cancel = platformHttpDownloadCancelCallback; // Archive extraction (handled by JS layer) adapter_.extract_archive = nullptr; @@ -1271,3 +1482,23 @@ std::tuple InitBridge::httpPostSync( } // namespace bridges } // namespace runanywhere + +// ============================================================================= +// Global C API for platform download reporting +// ============================================================================= + +extern "C" int RunAnywhereHttpDownloadReportProgress(const char* task_id, + int64_t downloaded_bytes, + int64_t total_bytes) { + return runanywhere::bridges::reportHttpDownloadProgressInternal(task_id, + downloaded_bytes, + total_bytes); +} + +extern "C" int RunAnywhereHttpDownloadReportComplete(const char* task_id, + int result, + const char* downloaded_path) { + return runanywhere::bridges::reportHttpDownloadCompleteInternal(task_id, + result, + downloaded_path); +} diff --git a/sdk/runanywhere-react-native/packages/core/cpp/bridges/PlatformDownloadBridge.h b/sdk/runanywhere-react-native/packages/core/cpp/bridges/PlatformDownloadBridge.h new file mode 100644 index 000000000..742afe477 --- /dev/null +++ b/sdk/runanywhere-react-native/packages/core/cpp/bridges/PlatformDownloadBridge.h @@ -0,0 +1,44 @@ +/** + * PlatformDownloadBridge.h + * + * C callbacks for platform HTTP download progress/completion reporting. + * Used by iOS/Android platform adapters to report async download state + * back into the C++ bridge. + */ + +#ifndef RUNANYWHERE_PLATFORM_DOWNLOAD_BRIDGE_H +#define RUNANYWHERE_PLATFORM_DOWNLOAD_BRIDGE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Report HTTP download progress for a task. + * @param task_id Task identifier + * @param downloaded_bytes Bytes downloaded so far + * @param total_bytes Total bytes (0 if unknown) + * @return RAC_SUCCESS on success, error code otherwise + */ +int RunAnywhereHttpDownloadReportProgress(const char* task_id, + int64_t downloaded_bytes, + int64_t total_bytes); + +/** + * Report HTTP download completion for a task. + * @param task_id Task identifier + * @param result RAC_SUCCESS or error code + * @param downloaded_path Path to downloaded file (NULL on failure) + * @return RAC_SUCCESS on success, error code otherwise + */ +int RunAnywhereHttpDownloadReportComplete(const char* task_id, + int result, + const char* downloaded_path); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // RUNANYWHERE_PLATFORM_DOWNLOAD_BRIDGE_H diff --git a/sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp b/sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp new file mode 100644 index 000000000..873b28b2c --- /dev/null +++ b/sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp @@ -0,0 +1,188 @@ +/** + * @file ToolCallingBridge.cpp + * @brief Tool Calling bridge implementation - THIN WRAPPER + * + * *** ALL LOGIC IS IN runanywhere-commons (rac_tool_calling.h) *** + * + * This bridge just wraps the C API functions from commons. + * NO LOCAL PARSING LOGIC - everything calls through to commons. + */ + +#include "ToolCallingBridge.hpp" +#include +#include +#include + +using json = nlohmann::json; + +namespace runanywhere { +namespace bridges { + +ToolCallingBridge& ToolCallingBridge::shared() { + static ToolCallingBridge instance; + return instance; +} + +std::string ToolCallingBridge::parseToolCall(const std::string& llmOutput) { + rac_tool_call_t result = {}; // Zero-initialize for safety + rac_result_t rc = rac_tool_call_parse(llmOutput.c_str(), &result); + + // Handle parse failure defensively - return safe default + if (rc != RAC_SUCCESS) { + json fallback; + fallback["hasToolCall"] = false; + fallback["cleanText"] = llmOutput; + return fallback.dump(); + } + + // Build JSON response using nlohmann/json + json response; + response["hasToolCall"] = result.has_tool_call == RAC_TRUE; + response["cleanText"] = result.clean_text ? result.clean_text : llmOutput; + + if (result.has_tool_call == RAC_TRUE) { + response["toolName"] = result.tool_name ? result.tool_name : ""; + + if (result.arguments_json) { + try { + response["argumentsJson"] = json::parse(result.arguments_json); + } catch (...) { + response["argumentsJson"] = json::object(); + } + } else { + response["argumentsJson"] = json::object(); + } + response["callId"] = result.call_id; + } + + rac_tool_call_free(&result); + return response.dump(); +} + +std::string ToolCallingBridge::formatToolsPrompt(const std::string& toolsJson, const std::string& format) { + if (toolsJson.empty() || toolsJson == "[]") { + return ""; + } + + char* prompt = nullptr; + rac_result_t rc = rac_tool_call_format_prompt_json_with_format_name( + toolsJson.c_str(), + format.c_str(), + &prompt + ); + + if (rc != RAC_SUCCESS || !prompt) { + return ""; + } + + std::string result(prompt); + rac_free(prompt); + return result; +} + +std::string ToolCallingBridge::buildInitialPrompt( + const std::string& userPrompt, + const std::string& toolsJson, + const std::string& optionsJson +) { + // Start with default options + rac_tool_calling_options_t options = { + 5, // max_tool_calls + RAC_TRUE, // auto_execute + 0.7f, // temperature + 1024, // max_tokens + nullptr, // system_prompt + RAC_FALSE, // replace_system_prompt + RAC_FALSE, // keep_tools_available + RAC_TOOL_FORMAT_DEFAULT // format + }; + + // Parse optionsJson if provided + if (!optionsJson.empty()) { + try { + json opts = json::parse(optionsJson); + + if (opts.contains("maxToolCalls") && opts["maxToolCalls"].is_number_integer()) { + options.max_tool_calls = opts["maxToolCalls"].get(); + } + if (opts.contains("autoExecute") && opts["autoExecute"].is_boolean()) { + options.auto_execute = opts["autoExecute"].get() ? RAC_TRUE : RAC_FALSE; + } + if (opts.contains("temperature") && opts["temperature"].is_number()) { + options.temperature = opts["temperature"].get(); + } + if (opts.contains("maxTokens") && opts["maxTokens"].is_number_integer()) { + options.max_tokens = opts["maxTokens"].get(); + } + if (opts.contains("format") && opts["format"].is_string()) { + options.format = rac_tool_call_format_from_name(opts["format"].get().c_str()); + } + if (opts.contains("replaceSystemPrompt") && opts["replaceSystemPrompt"].is_boolean()) { + options.replace_system_prompt = opts["replaceSystemPrompt"].get() ? RAC_TRUE : RAC_FALSE; + } + if (opts.contains("keepToolsAvailable") && opts["keepToolsAvailable"].is_boolean()) { + options.keep_tools_available = opts["keepToolsAvailable"].get() ? RAC_TRUE : RAC_FALSE; + } + } catch (...) { + // JSON parse failed, keep defaults + } + } + + char* prompt = nullptr; + rac_result_t rc = rac_tool_call_build_initial_prompt( + userPrompt.c_str(), + toolsJson.c_str(), + &options, + &prompt + ); + + if (rc != RAC_SUCCESS || !prompt) { + return userPrompt; + } + + std::string result(prompt); + rac_free(prompt); + return result; +} + +std::string ToolCallingBridge::buildFollowupPrompt( + const std::string& originalPrompt, + const std::string& toolsPrompt, + const std::string& toolName, + const std::string& resultJson, + bool keepToolsAvailable +) { + char* prompt = nullptr; + rac_result_t rc = rac_tool_call_build_followup_prompt( + originalPrompt.c_str(), + toolsPrompt.empty() ? nullptr : toolsPrompt.c_str(), + toolName.c_str(), + resultJson.c_str(), + keepToolsAvailable ? RAC_TRUE : RAC_FALSE, + &prompt + ); + + if (rc != RAC_SUCCESS || !prompt) { + return ""; + } + + std::string result(prompt); + rac_free(prompt); + return result; +} + +std::string ToolCallingBridge::normalizeJson(const std::string& jsonStr) { + char* normalized = nullptr; + rac_result_t rc = rac_tool_call_normalize_json(jsonStr.c_str(), &normalized); + + if (rc != RAC_SUCCESS || !normalized) { + return jsonStr; + } + + std::string result(normalized); + rac_free(normalized); + return result; +} + +} // namespace bridges +} // namespace runanywhere diff --git a/sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.hpp b/sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.hpp new file mode 100644 index 000000000..8a4ff4579 --- /dev/null +++ b/sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.hpp @@ -0,0 +1,98 @@ +/** + * @file ToolCallingBridge.hpp + * @brief Tool Calling bridge for React Native - THIN WRAPPER + * + * *** SINGLE SOURCE OF TRUTH FOR TOOL CALLING LOGIC IS IN COMMONS C++ *** + * + * All parsing logic is in runanywhere-commons (rac_tool_calling.h). + * This bridge just wraps the C API for JSI access. + * + * ARCHITECTURE: + * - Commons C++: ALL parsing, prompt formatting, JSON normalization + * - This bridge: Thin wrapper that calls rac_tool_call_* functions + * - TypeScript: Tool registry, execution (needs JS APIs) + */ + +#pragma once + +#include + +namespace runanywhere { +namespace bridges { + +/** + * Tool Calling bridge - thin wrapper around commons API + * + * *** NO LOCAL PARSING LOGIC - ALL CALLS GO TO COMMONS *** + */ +class ToolCallingBridge { +public: + static ToolCallingBridge& shared(); + + /** + * Format tool definitions into a system prompt. + * Calls rac_tool_call_format_prompt_json_with_format_name() from commons. + * + * @param toolsJson JSON array of tool definitions + * @param format Format name ("default" or "lfm2") + * @return Formatted system prompt string + */ + std::string formatToolsPrompt(const std::string& toolsJson, const std::string& format = "default"); + + /** + * Parse LLM output for tool calls. + * Calls rac_tool_call_parse() from commons. + * + * @param llmOutput Raw LLM output text + * @return JSON string with hasToolCall, toolName, argumentsJson, cleanText + */ + std::string parseToolCall(const std::string& llmOutput); + + /** + * Build initial prompt with tools and user query. + * Calls rac_tool_call_build_initial_prompt() from commons. + * + * @param userPrompt User's question/request + * @param toolsJson JSON array of tool definitions + * @param optionsJson Options as JSON (nullable) + * @return Complete formatted prompt + */ + std::string buildInitialPrompt(const std::string& userPrompt, + const std::string& toolsJson, + const std::string& optionsJson); + + /** + * Build follow-up prompt after tool execution. + * Calls rac_tool_call_build_followup_prompt() from commons. + * + * @param originalPrompt Original user prompt + * @param toolsPrompt Formatted tools prompt (can be empty) + * @param toolName Name of executed tool + * @param resultJson Tool result as JSON + * @param keepToolsAvailable Whether to keep tools in follow-up + * @return Follow-up prompt string + */ + std::string buildFollowupPrompt(const std::string& originalPrompt, + const std::string& toolsPrompt, + const std::string& toolName, + const std::string& resultJson, + bool keepToolsAvailable); + + /** + * Normalize JSON by adding quotes around unquoted keys. + * Calls rac_tool_call_normalize_json() from commons. + * + * @param jsonStr Raw JSON possibly with unquoted keys + * @return Normalized JSON string + */ + std::string normalizeJson(const std::string& jsonStr); + +private: + ToolCallingBridge() = default; + ~ToolCallingBridge() = default; + ToolCallingBridge(const ToolCallingBridge&) = delete; + ToolCallingBridge& operator=(const ToolCallingBridge&) = delete; +}; + +} // namespace bridges +} // namespace runanywhere diff --git a/sdk/runanywhere-react-native/packages/core/cpp/third_party/nlohmann/json.hpp b/sdk/runanywhere-react-native/packages/core/cpp/third_party/nlohmann/json.hpp new file mode 100644 index 000000000..8b72ea653 --- /dev/null +++ b/sdk/runanywhere-react-native/packages/core/cpp/third_party/nlohmann/json.hpp @@ -0,0 +1,24765 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#ifndef JSON_NO_IO + #include // istream, ostream +#endif // JSON_NO_IO +#include // random_access_iterator_tag +#include // unique_ptr +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // nullptr_t +#include // exception +#if JSON_DIAGNOSTICS + #include // accumulate +#endif +#include // runtime_error +#include // to_string +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // declval, pair +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions (except those affecting ABI) +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// #include + + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used c++ version this is skipped +#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifndef JSON_HAS_STATIC_RTTI + #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 + #define JSON_HAS_STATIC_RTTI 1 + #else + #define JSON_HAS_STATIC_RTTI 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" + #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#endif + +// allow disabling exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow overriding assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow to access some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType, \ + class CustomBaseClass> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +// inspired from https://stackoverflow.com/a/26745591 +// allows to call any std function as if (e.g. with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template \ + std_name##_tag std_name(T&&...); \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + \ + template \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name \ + { \ + } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != StringType::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +template +inline StringType escape(StringType s) +{ + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +template +static void unescape(StringType& s) +{ + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template +using index_sequence_for = make_index_sequence; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static JSON_INLINE_VARIABLE constexpr T value{}; +}; + +#ifndef JSON_HAS_CPP_17 + template + constexpr T static_const::value; +#endif + +template +inline constexpr std::array make_array(Args&& ... args) +{ + return std::array {{static_cast(std::forward(args))...}}; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval +#include // tuple +#include // char_traits + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // random_access_iterator_tag + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); + +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); + +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + #include // int64_t, uint64_t + #include // map + #include // allocator + #include // string + #include // vector + + // #include + + + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN + + /*! + @brief default JSONSerializer template argument + + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template + struct adl_serializer; + + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> + class basic_json; + + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template + class json_pointer; + + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; + + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template + struct ordered_map; + + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json; + + NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +NLOHMANN_JSON_NAMESPACE_BEGIN +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ + +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// used by exceptions create() member functions +// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json::type>::type>::value + || std::is_same::value > +{}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; + +///////////////// +// char_traits // +///////////////// + +// Primary template of char_traits calls std char_traits +template +struct char_traits : std::char_traits +{}; + +// Explicitly define char traits for unsigned char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = unsigned char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(EOF); + } +}; + +// Explicitly define char traits for signed char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = signed char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(EOF); + } +}; + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B { }; +template +struct conjunction +: std::conditional(B::value), conjunction, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > { }; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g. clang 3.5 or gcc 4.9. +template +struct is_default_constructible : std::is_default_constructible {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_constructible : std::is_constructible {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +template +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference::type; + + using iterator = detected_t; + using sentinel = detected_t; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits>::value; + + public: + static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; +}; + +template +using iterator_t = enable_if_t::value, result_of_begin())>>; + +template +using range_value_t = value_type_t>>; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to using it in this library. + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible::value && + is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_constructible_string_type +{ + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + + static constexpr auto value = + conjunction < + is_constructible, + is_detected_exact>::value; +}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < + is_detected::value&& + is_iterator_traits>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same>::value >> +{ + static constexpr bool value = + is_constructible>::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + !is_compatible_string_type::value&& + is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_iterator_traits>>::value&& +is_detected::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same>::value&& + is_complete_type < + detected_t>::value >> +{ + using value_type = range_value_t; + + static constexpr bool value = + std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; + +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template