react-native-nitro-mlx 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/MLXReactNative.podspec +1 -1
  2. package/ios/Sources/HybridLLM.swift +250 -15
  3. package/lib/module/index.js +1 -0
  4. package/lib/module/index.js.map +1 -1
  5. package/lib/module/llm.js +33 -3
  6. package/lib/module/llm.js.map +1 -1
  7. package/lib/module/tool-utils.js +56 -0
  8. package/lib/module/tool-utils.js.map +1 -0
  9. package/lib/typescript/src/index.d.ts +3 -2
  10. package/lib/typescript/src/index.d.ts.map +1 -1
  11. package/lib/typescript/src/llm.d.ts +14 -2
  12. package/lib/typescript/src/llm.d.ts.map +1 -1
  13. package/lib/typescript/src/specs/LLM.nitro.d.ts +30 -3
  14. package/lib/typescript/src/specs/LLM.nitro.d.ts.map +1 -1
  15. package/lib/typescript/src/tool-utils.d.ts +13 -0
  16. package/lib/typescript/src/tool-utils.d.ts.map +1 -0
  17. package/nitrogen/generated/ios/MLXReactNative+autolinking.rb +1 -1
  18. package/nitrogen/generated/ios/MLXReactNative-Swift-Cxx-Bridge.cpp +34 -1
  19. package/nitrogen/generated/ios/MLXReactNative-Swift-Cxx-Bridge.hpp +173 -1
  20. package/nitrogen/generated/ios/MLXReactNative-Swift-Cxx-Umbrella.hpp +8 -1
  21. package/nitrogen/generated/ios/MLXReactNativeAutolinking.mm +1 -1
  22. package/nitrogen/generated/ios/MLXReactNativeAutolinking.swift +1 -1
  23. package/nitrogen/generated/ios/c++/HybridLLMSpecSwift.cpp +1 -1
  24. package/nitrogen/generated/ios/c++/HybridLLMSpecSwift.hpp +10 -3
  25. package/nitrogen/generated/ios/c++/HybridModelManagerSpecSwift.cpp +1 -1
  26. package/nitrogen/generated/ios/c++/HybridModelManagerSpecSwift.hpp +1 -1
  27. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__shared_ptr_AnyMap______std__shared_ptr_AnyMap_.swift +62 -0
  28. package/nitrogen/generated/ios/swift/Func_void.swift +1 -1
  29. package/nitrogen/generated/ios/swift/Func_void_bool.swift +1 -1
  30. package/nitrogen/generated/ios/swift/Func_void_double.swift +1 -1
  31. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +1 -1
  32. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_AnyMap_.swift +47 -0
  33. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_std__shared_ptr_AnyMap___.swift +67 -0
  34. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +1 -1
  35. package/nitrogen/generated/ios/swift/Func_void_std__string_std__string.swift +47 -0
  36. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +1 -1
  37. package/nitrogen/generated/ios/swift/GenerationStats.swift +1 -1
  38. package/nitrogen/generated/ios/swift/HybridLLMSpec.swift +2 -2
  39. package/nitrogen/generated/ios/swift/HybridLLMSpec_cxx.swift +14 -2
  40. package/nitrogen/generated/ios/swift/HybridModelManagerSpec.swift +1 -1
  41. package/nitrogen/generated/ios/swift/HybridModelManagerSpec_cxx.swift +1 -1
  42. package/nitrogen/generated/ios/swift/LLMLoadOptions.swift +44 -2
  43. package/nitrogen/generated/ios/swift/LLMMessage.swift +1 -1
  44. package/nitrogen/generated/ios/swift/ToolDefinition.swift +113 -0
  45. package/nitrogen/generated/ios/swift/ToolParameter.swift +69 -0
  46. package/nitrogen/generated/shared/c++/GenerationStats.hpp +1 -1
  47. package/nitrogen/generated/shared/c++/HybridLLMSpec.cpp +1 -1
  48. package/nitrogen/generated/shared/c++/HybridLLMSpec.hpp +2 -2
  49. package/nitrogen/generated/shared/c++/HybridModelManagerSpec.cpp +1 -1
  50. package/nitrogen/generated/shared/c++/HybridModelManagerSpec.hpp +1 -1
  51. package/nitrogen/generated/shared/c++/LLMLoadOptions.hpp +10 -3
  52. package/nitrogen/generated/shared/c++/LLMMessage.hpp +1 -1
  53. package/nitrogen/generated/shared/c++/ToolDefinition.hpp +93 -0
  54. package/nitrogen/generated/shared/c++/ToolParameter.hpp +87 -0
  55. package/package.json +8 -7
  56. package/src/index.ts +10 -3
  57. package/src/llm.ts +42 -3
  58. package/src/specs/LLM.nitro.ts +37 -3
  59. package/src/tool-utils.ts +74 -0
@@ -0,0 +1,87 @@
1
+ ///
2
+ /// ToolParameter.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © 2026 Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #if __has_include(<NitroModules/JSIConverter.hpp>)
11
+ #include <NitroModules/JSIConverter.hpp>
12
+ #else
13
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
+ #endif
15
+ #if __has_include(<NitroModules/NitroDefines.hpp>)
16
+ #include <NitroModules/NitroDefines.hpp>
17
+ #else
18
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
19
+ #endif
20
+ #if __has_include(<NitroModules/JSIHelpers.hpp>)
21
+ #include <NitroModules/JSIHelpers.hpp>
22
+ #else
23
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
24
+ #endif
25
+
26
+
27
+
28
+ #include <string>
29
+
30
+ namespace margelo::nitro::mlxreactnative {
31
+
32
+ /**
33
+ * A struct which can be represented as a JavaScript object (ToolParameter).
34
+ */
35
+ struct ToolParameter {
36
+ public:
37
+ std::string name SWIFT_PRIVATE;
38
+ std::string type SWIFT_PRIVATE;
39
+ std::string description SWIFT_PRIVATE;
40
+ bool required SWIFT_PRIVATE;
41
+
42
+ public:
43
+ ToolParameter() = default;
44
+ explicit ToolParameter(std::string name, std::string type, std::string description, bool required): name(name), type(type), description(description), required(required) {}
45
+ };
46
+
47
+ } // namespace margelo::nitro::mlxreactnative
48
+
49
+ namespace margelo::nitro {
50
+
51
+ // C++ ToolParameter <> JS ToolParameter (object)
52
+ template <>
53
+ struct JSIConverter<margelo::nitro::mlxreactnative::ToolParameter> final {
54
+ static inline margelo::nitro::mlxreactnative::ToolParameter fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
55
+ jsi::Object obj = arg.asObject(runtime);
56
+ return margelo::nitro::mlxreactnative::ToolParameter(
57
+ JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, "name")),
58
+ JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, "type")),
59
+ JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, "description")),
60
+ JSIConverter<bool>::fromJSI(runtime, obj.getProperty(runtime, "required"))
61
+ );
62
+ }
63
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::mlxreactnative::ToolParameter& arg) {
64
+ jsi::Object obj(runtime);
65
+ obj.setProperty(runtime, "name", JSIConverter<std::string>::toJSI(runtime, arg.name));
66
+ obj.setProperty(runtime, "type", JSIConverter<std::string>::toJSI(runtime, arg.type));
67
+ obj.setProperty(runtime, "description", JSIConverter<std::string>::toJSI(runtime, arg.description));
68
+ obj.setProperty(runtime, "required", JSIConverter<bool>::toJSI(runtime, arg.required));
69
+ return obj;
70
+ }
71
+ static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
72
+ if (!value.isObject()) {
73
+ return false;
74
+ }
75
+ jsi::Object obj = value.getObject(runtime);
76
+ if (!nitro::isPlainObject(runtime, obj)) {
77
+ return false;
78
+ }
79
+ if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "name"))) return false;
80
+ if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "type"))) return false;
81
+ if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "description"))) return false;
82
+ if (!JSIConverter<bool>::canConvert(runtime, obj.getProperty(runtime, "required"))) return false;
83
+ return true;
84
+ }
85
+ };
86
+
87
+ } // namespace margelo::nitro
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-mlx",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Nitro module package",
5
5
  "main": "./lib/module/index.js",
6
6
  "module": "./lib/module/index.js",
@@ -11,7 +11,7 @@
11
11
  "build": "rm -rf lib && bun typecheck && bob build",
12
12
  "typecheck": "tsc --noEmit",
13
13
  "clean": "rm -rf android/build node_modules/**/android/build lib android/.cxx node_modules/**/android/.cxx",
14
- "release": "release-it",
14
+ "release": "release-it && npm publish",
15
15
  "specs": "bun typecheck && nitrogen --logLevel=\\\"debug\\\" && bun run build",
16
16
  "specs:pod": "bun specs && bun --cwd ../example pod"
17
17
  },
@@ -68,13 +68,14 @@
68
68
  "peerDependencies": {
69
69
  "react": "*",
70
70
  "react-native": "*",
71
- "react-native-nitro-modules": "*"
71
+ "react-native-nitro-modules": "*",
72
+ "zod": ">=4.0.0"
73
+ },
74
+ "dependencies": {
75
+ "zod": "^4.3.5"
72
76
  },
73
77
  "release-it": {
74
- "npm": {
75
- "publish": true,
76
- "skipVersion": true
77
- },
78
+ "npm": false,
78
79
  "github": {
79
80
  "release": true,
80
81
  "releaseName": "v${version}"
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { LLM, type Message } from './llm'
1
+ export { LLM, type Message, type ToolCallInfo, type ToolCallUpdate } from './llm'
2
2
  export { ModelManager } from './modelManager'
3
3
  export {
4
4
  MLXModel,
@@ -8,6 +8,13 @@ export {
8
8
  ModelProvider,
9
9
  type ModelQuantization,
10
10
  } from './models'
11
-
12
- export type { GenerationStats, LLM as LLMSpec, LLMLoadOptions } from './specs/LLM.nitro'
11
+ export type {
12
+ GenerationStats,
13
+ LLM as LLMSpec,
14
+ LLMLoadOptions,
15
+ ToolDefinition,
16
+ ToolParameter,
17
+ ToolParameterType,
18
+ } from './specs/LLM.nitro'
13
19
  export type { ModelManager as ModelManagerSpec } from './specs/ModelManager.nitro'
20
+ export { createTool, type TypeSafeToolDefinition } from './tool-utils'
package/src/llm.ts CHANGED
@@ -8,6 +8,16 @@ export type Message = {
8
8
  content: string
9
9
  }
10
10
 
11
+ export type ToolCallInfo = {
12
+ name: string
13
+ arguments: Record<string, unknown>
14
+ }
15
+
16
+ export type ToolCallUpdate = {
17
+ toolCall: ToolCallInfo
18
+ allToolCalls: ToolCallInfo[]
19
+ }
20
+
11
21
  function getInstance(): LLMSpec {
12
22
  if (!instance) {
13
23
  instance = NitroModules.createHybridObject<LLMSpec>('LLM')
@@ -58,13 +68,42 @@ export const LLM = {
58
68
  },
59
69
 
60
70
  /**
61
- * Stream a response token by token.
71
+ * Stream a response token by token with optional tool calling support.
72
+ * Tools must be provided when loading the model via `load()` options.
73
+ * Tools are automatically executed when the model calls them.
62
74
  * @param prompt - The input text to generate a response for
63
75
  * @param onToken - Callback invoked for each generated token
76
+ * @param onToolCall - Optional callback invoked when a tool is called.
77
+ * Receives the current tool call and an accumulated array of all tool calls so far.
64
78
  * @returns The complete generated text
65
79
  */
66
- stream(prompt: string, onToken: (token: string) => void): Promise<string> {
67
- return getInstance().stream(prompt, onToken)
80
+ stream(
81
+ prompt: string,
82
+ onToken: (token: string) => void,
83
+ onToolCall?: (update: ToolCallUpdate) => void,
84
+ ): Promise<string> {
85
+ const accumulatedToolCalls: ToolCallInfo[] = []
86
+
87
+ return getInstance().stream(prompt, onToken, (name: string, argsJson: string) => {
88
+ if (onToolCall) {
89
+ try {
90
+ const args = JSON.parse(argsJson) as Record<string, unknown>
91
+ const toolCall = { name, arguments: args }
92
+ accumulatedToolCalls.push(toolCall)
93
+ onToolCall({
94
+ toolCall,
95
+ allToolCalls: [...accumulatedToolCalls],
96
+ })
97
+ } catch {
98
+ const toolCall = { name, arguments: {} }
99
+ accumulatedToolCalls.push(toolCall)
100
+ onToolCall({
101
+ toolCall,
102
+ allToolCalls: [...accumulatedToolCalls],
103
+ })
104
+ }
105
+ }
106
+ })
68
107
  },
69
108
 
70
109
  /**
@@ -1,4 +1,4 @@
1
- import type { HybridObject } from 'react-native-nitro-modules'
1
+ import type { AnyMap, HybridObject } from 'react-native-nitro-modules'
2
2
 
3
3
  /**
4
4
  * Statistics from the last text generation.
@@ -19,6 +19,26 @@ export interface LLMMessage {
19
19
  content: string
20
20
  }
21
21
 
22
+ /**
23
+ * Parameter definition for a tool.
24
+ */
25
+ export interface ToolParameter {
26
+ name: string
27
+ type: string
28
+ description: string
29
+ required: boolean
30
+ }
31
+
32
+ /**
33
+ * Tool definition that can be called by the model.
34
+ */
35
+ export interface ToolDefinition {
36
+ name: string
37
+ description: string
38
+ parameters: ToolParameter[]
39
+ handler: (args: AnyMap) => Promise<AnyMap>
40
+ }
41
+
22
42
  /** Options for loading a model.
23
43
  */
24
44
  export interface LLMLoadOptions {
@@ -28,6 +48,8 @@ export interface LLMLoadOptions {
28
48
  additionalContext?: LLMMessage[]
29
49
  /** Whether to automatically manage message history */
30
50
  manageHistory?: boolean
51
+ /** Tools available for the model to call */
52
+ tools?: ToolDefinition[]
31
53
  }
32
54
 
33
55
  /**
@@ -50,12 +72,18 @@ export interface LLM extends HybridObject<{ ios: 'swift' }> {
50
72
  generate(prompt: string): Promise<string>
51
73
 
52
74
  /**
53
- * Stream a response token by token.
75
+ * Stream a response token by token with optional tool calling support.
76
+ * Tools are automatically executed when the model calls them.
54
77
  * @param prompt - The input text to generate a response for
55
78
  * @param onToken - Callback invoked for each generated token
79
+ * @param onToolCall - Optional callback invoked when a tool is called (for UI feedback)
56
80
  * @returns The complete generated text
57
81
  */
58
- stream(prompt: string, onToken: (token: string) => void): Promise<string>
82
+ stream(
83
+ prompt: string,
84
+ onToken: (token: string) => void,
85
+ onToolCall?: (toolName: string, args: string) => void,
86
+ ): Promise<string>
59
87
 
60
88
  /**
61
89
  * Stop the current generation.
@@ -96,3 +124,9 @@ export interface LLM extends HybridObject<{ ios: 'swift' }> {
96
124
  /** System prompt used when loading the model */
97
125
  systemPrompt: string
98
126
  }
127
+
128
+ /**
129
+ * Supported parameter types for tool definitions.
130
+ * Used for type safety in createTool().
131
+ */
132
+ export type ToolParameterType = 'string' | 'number' | 'boolean' | 'array' | 'object'
@@ -0,0 +1,74 @@
1
+ import type { AnyMap } from 'react-native-nitro-modules'
2
+ import type { z } from 'zod'
3
+ import type { ToolDefinition, ToolParameter, ToolParameterType } from './specs/LLM.nitro'
4
+
5
+ type ZodObjectSchema = z.ZodObject<z.core.$ZodShape>
6
+ type InferArgs<T extends ZodObjectSchema> = z.infer<T>
7
+
8
+ export interface TypeSafeToolDefinition<T extends ZodObjectSchema> {
9
+ name: string
10
+ description: string
11
+ arguments: T
12
+ handler: (args: InferArgs<T>) => Promise<Record<string, unknown>>
13
+ }
14
+
15
+ function getZodTypeString(zodType: z.ZodType): ToolParameterType {
16
+ const typeName = zodType._zod.def.type
17
+ switch (typeName) {
18
+ case 'string':
19
+ return 'string'
20
+ case 'number':
21
+ case 'int':
22
+ return 'number'
23
+ case 'boolean':
24
+ return 'boolean'
25
+ case 'array':
26
+ return 'array'
27
+ case 'object':
28
+ return 'object'
29
+ case 'optional':
30
+ return getZodTypeString((zodType as z.ZodOptional<z.ZodType>)._zod.def.innerType)
31
+ case 'default':
32
+ return getZodTypeString((zodType as z.ZodDefault<z.ZodType>)._zod.def.innerType)
33
+ default:
34
+ return 'string'
35
+ }
36
+ }
37
+
38
+ function isZodOptional(zodType: z.ZodType): boolean {
39
+ const typeName = zodType._zod.def.type
40
+ return typeName === 'optional' || typeName === 'default'
41
+ }
42
+
43
+ function zodSchemaToParameters(schema: ZodObjectSchema): ToolParameter[] {
44
+ const shape = schema._zod.def.shape
45
+ const parameters: ToolParameter[] = []
46
+
47
+ for (const [key, zodType] of Object.entries(shape)) {
48
+ const zType = zodType as z.ZodType
49
+ parameters.push({
50
+ name: key,
51
+ type: getZodTypeString(zType),
52
+ description: zType.description ?? '',
53
+ required: !isZodOptional(zType),
54
+ })
55
+ }
56
+
57
+ return parameters
58
+ }
59
+
60
+ export function createTool<T extends ZodObjectSchema>(
61
+ definition: TypeSafeToolDefinition<T>,
62
+ ): ToolDefinition {
63
+ return {
64
+ name: definition.name,
65
+ description: definition.description,
66
+ parameters: zodSchemaToParameters(definition.arguments),
67
+ handler: async (args: AnyMap) => {
68
+ const argsObj = args as unknown as Record<string, unknown>
69
+ const parsedArgs = definition.arguments.parse(argsObj)
70
+ const result = await definition.handler(parsedArgs)
71
+ return result as unknown as AnyMap
72
+ },
73
+ }
74
+ }