react-native-nobodywho 5.0.0 → 6.0.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 (36) hide show
  1. package/Nobodywho.podspec +25 -20
  2. package/README.md +8 -8
  3. package/android/CMakeLists.txt +4 -15
  4. package/android/build.gradle +4 -5
  5. package/android/proguard-rules.pro +6 -0
  6. package/android/src/main/AndroidManifest.xml +4 -1
  7. package/android/src/main/AndroidManifestNew.xml +3 -0
  8. package/{generated/cpp → cpp/generated}/nobodywho.cpp +424 -66
  9. package/{generated/cpp → cpp/generated}/nobodywho.hpp +26 -2
  10. package/cpp/react-native-nobodywho.cpp +1 -1
  11. package/package.json +28 -14
  12. package/src/generated/index.ts +2 -0
  13. package/src/generated/jsi/nobodywho-ffi.ts +937 -0
  14. package/src/generated/jsi/nobodywho.ts +7421 -0
  15. package/src/index.ts +23 -0
  16. package/src/{chat.ts → wrapper/chat.ts} +1 -1
  17. package/src/wrapper/convert_value.ts +47 -0
  18. package/src/{cross_encoder.ts → wrapper/cross_encoder.ts} +1 -1
  19. package/src/wrapper/decision.ts +112 -0
  20. package/src/{encoder.ts → wrapper/encoder.ts} +1 -1
  21. package/src/{wrapper.ts → wrapper/index.ts} +11 -24
  22. package/src/{message.ts → wrapper/message.ts} +1 -1
  23. package/src/{model.ts → wrapper/model.ts} +3 -2
  24. package/src/{prompt.ts → wrapper/prompt.ts} +1 -1
  25. package/src/{sampler_presets.ts → wrapper/sampler_presets.ts} +1 -1
  26. package/src/{speech_to_text.ts → wrapper/speech_to_text.ts} +2 -2
  27. package/src/{streaming.ts → wrapper/streaming.ts} +1 -1
  28. package/src/{text_to_speech.ts → wrapper/text_to_speech.ts} +1 -1
  29. package/src/{tool.ts → wrapper/tool.ts} +2 -51
  30. package/src/{vad.ts → wrapper/vad.ts} +3 -3
  31. package/ubrn.config.yaml +26 -5
  32. package/cpp/nobodywho-react-native.cpp +0 -16
  33. package/cpp/nobodywho-react-native.h +0 -15
  34. package/generated/ts/nobodywho-ffi.ts +0 -414
  35. package/generated/ts/nobodywho.ts +0 -5470
  36. package/src/index.tsx +0 -41
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * NobodyWho — React Native bindings for local LLM inference.
3
+ */
4
+
5
+ // Native crate install + bindings initialize. These run as side effects of
6
+ // evaluating this module, which the React Native runtime is guaranteed to do
7
+ // before any FFI call because this file is the package main.
8
+ //
9
+ // The install logic was previously reached only through lazy re-exports from
10
+ // ./index.tsx. In release mode Metro's inlineRequires defers lazy re-exports
11
+ // until first use, so consumers whose first interaction was Model.load /
12
+ // downloadModel / Chat never triggered ./index and FFI calls then threw
13
+ // "Cannot read property 'ubrn_…' of undefined". Inlining the install here
14
+ // removes that indirection so this can't recur.
15
+ import installer from "./NativeNobodywho";
16
+ import nobodywho from "./generated";
17
+ // Register the rust crate with Hermes
18
+ installer.installRustCrate();
19
+ // Initialize the generated bindings: mostly checksums, but also callbacks.
20
+ nobodywho.initialize();
21
+
22
+ // Re-export the wrappers.
23
+ export * from "./wrapper";
@@ -4,7 +4,7 @@ import {
4
4
  MtpConfig,
5
5
  ContextShiftOptions,
6
6
  type ChatStats,
7
- } from "../generated/ts/nobodywho";
7
+ } from "../generated";
8
8
  import { Model } from "./model";
9
9
  import { type Message, fromInternal, toInternal } from "./message";
10
10
  import { TokenStream } from "./streaming";
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Recursively convert a value from JSON based on a JSON Schema type.
3
+ *
4
+ * Supports primitives (`string`, `number`, `integer`, `boolean`),
5
+ * arrays (`{ type: "array", items: ... }`), and nested objects
6
+ * (`{ type: "object", properties: { ... } }`).
7
+ */
8
+ export function convertValue(
9
+ value: unknown,
10
+ schema: Record<string, unknown>,
11
+ ): unknown {
12
+ if (value === null || value === undefined) {
13
+ return value;
14
+ }
15
+
16
+ switch (schema.type) {
17
+ case "integer":
18
+ case "number":
19
+ return Number(value);
20
+ case "boolean":
21
+ return typeof value === "string" ? value === "true" : Boolean(value);
22
+ case "string":
23
+ return String(value);
24
+ case "array": {
25
+ if (!Array.isArray(value)) return value;
26
+ const itemSchema = schema.items as Record<string, unknown> | undefined;
27
+ if (!itemSchema) return value;
28
+ return value.map((item) => convertValue(item, itemSchema));
29
+ }
30
+ case "object": {
31
+ if (typeof value !== "object" || Array.isArray(value)) return value;
32
+ const properties = schema.properties as
33
+ | Record<string, Record<string, unknown>>
34
+ | undefined;
35
+ if (!properties) return value;
36
+ const obj = value as Record<string, unknown>;
37
+ const result: Record<string, unknown> = {};
38
+ for (const [key, propSchema] of Object.entries(properties)) {
39
+ result[key] = key in obj ? convertValue(obj[key], propSchema) : undefined;
40
+ }
41
+ return result;
42
+ }
43
+ default:
44
+ return value;
45
+ }
46
+ }
47
+
@@ -1,4 +1,4 @@
1
- import { RustCrossEncoder } from "../generated/ts/nobodywho";
1
+ import { RustCrossEncoder } from "../generated";
2
2
  import { Model } from "./model";
3
3
 
4
4
  /**
@@ -0,0 +1,112 @@
1
+ import { RustDecision } from "../generated";
2
+ import { Model } from "./model";
3
+
4
+ /**
5
+ * A question asked of a decision model. `choice`, `score`, and `noul` (boolean)
6
+ * map directly to the Laya question types.
7
+ */
8
+ export type DecisionQuestion =
9
+ | {
10
+ type: "choice";
11
+ instructions: unknown;
12
+ criteria: Record<string, unknown>;
13
+ }
14
+ | {
15
+ type: "score";
16
+ instructions: unknown;
17
+ criteria: unknown[];
18
+ }
19
+ | {
20
+ type: "noul";
21
+ instructions: unknown;
22
+ criteria?: Record<string, unknown>;
23
+ };
24
+
25
+ export type DecisionRequest = {
26
+ state: unknown;
27
+ questions: Record<string, DecisionQuestion>;
28
+ };
29
+
30
+ export type DecisionAnswer =
31
+ | {
32
+ type: "choice";
33
+ choice: string;
34
+ probabilities: Record<string, number>;
35
+ confidence: number;
36
+ }
37
+ | {
38
+ type: "score";
39
+ score: number;
40
+ legend: Record<string, unknown>;
41
+ probabilities: Record<string, number>;
42
+ confidence: number;
43
+ }
44
+ | { type: "noul"; noul: number };
45
+
46
+ export type DecisionResponse = {
47
+ answers: Record<string, DecisionAnswer>;
48
+ usage: { input_tokens: number; output_tokens: number };
49
+ };
50
+
51
+ /**
52
+ * Typed, non-generative decisions over text or JSON using a Laya GGUF model.
53
+ * Questions run independently; every answer comes with probabilities instead
54
+ * of generated text.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const decision = await Decision.fromPath({
59
+ * modelPath: "hf://ggml-org/Laya-GGUF/Laya-Q8_0.gguf",
60
+ * });
61
+ * const response = await decision.decide({
62
+ * state: "I was charged twice. Please refund the duplicate payment.",
63
+ * questions: {
64
+ * refund: { type: "noul", instructions: "Is a refund requested?" },
65
+ * },
66
+ * });
67
+ * console.log(response.answers.refund);
68
+ * ```
69
+ */
70
+ export class Decision {
71
+ /** @internal */
72
+ private readonly _inner: RustDecision;
73
+
74
+ constructor(opts: { model: Model; contextSize?: number }) {
75
+ this._inner = new RustDecision(opts.model._inner, opts.contextSize);
76
+ }
77
+
78
+ /**
79
+ * Create a decision handle directly from a model path.
80
+ * Loads the model and creates the handle in one step.
81
+ */
82
+ static async fromPath(opts: {
83
+ modelPath: string;
84
+ useGpu?: boolean;
85
+ contextSize?: number;
86
+ onDownloadProgress?: (downloaded: number, total: number) => void;
87
+ }): Promise<Decision> {
88
+ const model = await Model.load({
89
+ modelPath: opts.modelPath,
90
+ useGpu: opts.useGpu,
91
+ onDownloadProgress: opts.onDownloadProgress,
92
+ });
93
+ return new Decision({ model, contextSize: opts.contextSize });
94
+ }
95
+
96
+ /** Run the request's questions over its state. */
97
+ async decide(request: DecisionRequest): Promise<DecisionResponse> {
98
+ const responseJson = await this._inner.decide(
99
+ JSON.stringify(request.state),
100
+ JSON.stringify(request.questions),
101
+ );
102
+ return JSON.parse(responseJson);
103
+ }
104
+
105
+ /**
106
+ * Immediately free the underlying Rust resources.
107
+ * After calling this, the Decision instance is no longer usable.
108
+ */
109
+ destroy(): void {
110
+ this._inner.uniffiDestroy();
111
+ }
112
+ }
@@ -1,4 +1,4 @@
1
- import { RustEncoder } from "../generated/ts/nobodywho";
1
+ import { RustEncoder } from "../generated";
2
2
  import { Model } from "./model";
3
3
 
4
4
  /**
@@ -1,32 +1,12 @@
1
1
  /**
2
- * NobodyWho — React Native bindings for local LLM inference.
3
- *
4
- * This is the public entry point. It re-exports the public API:
2
+ * Entry point that re-exports the public API:
5
3
  * - Wrapper classes: Model, Chat, Encoder, CrossEncoder, TextToSpeech, Tool, TokenStream, Prompt
6
4
  * - Direct re-exports: SamplerBuilder, SamplerConfig
7
5
  * - Types: Message, Content, ContentPart, ToolCall
8
6
  * - Utilities: SamplerPresets, cosineSimilarity, downloadModel
9
- *
10
- * This file is NOT generated — it is safe to edit.
11
7
  */
12
8
 
13
- // Native crate install + bindings initialize. These run as side effects of
14
- // evaluating this module, which the React Native runtime is guaranteed to do
15
- // before any FFI call because wrapper.ts is the package main.
16
- //
17
- // The install logic was previously reached only through lazy re-exports from
18
- // ./index. In release mode Metro's inlineRequires defers lazy re-exports
19
- // until first use, so consumers whose first interaction was Model.load /
20
- // downloadModel / Chat never triggered ./index and FFI calls then threw
21
- // "Cannot read property 'ubrn_…' of undefined". Inlining the install here
22
- // removes the ./index indirection so this can't recur.
23
- import installer from "./NativeNobodywho";
24
- import nobodywhoDefault, {
25
- downloadModel as _downloadModel,
26
- } from "../generated/ts/nobodywho";
27
-
28
- installer.installRustCrate();
29
- nobodywhoDefault.initialize();
9
+ import { downloadModel as _downloadModel } from "../generated";
30
10
 
31
11
  /**
32
12
  * Download a GGUF model from a remote URL or HuggingFace path and return the local file path.
@@ -61,6 +41,13 @@ export type { Options } from "./chat";
61
41
  export { SpeechToText } from "./speech_to_text";
62
42
  export { VoiceActivityDetection } from "./vad";
63
43
  export { Encoder } from "./encoder";
44
+ export { Decision } from "./decision";
45
+ export type {
46
+ DecisionAnswer,
47
+ DecisionQuestion,
48
+ DecisionRequest,
49
+ DecisionResponse,
50
+ } from "./decision";
64
51
  export { CrossEncoder } from "./cross_encoder";
65
52
  export { TextToSpeech } from "./text_to_speech";
66
53
  export { Prompt } from "./prompt";
@@ -80,14 +67,14 @@ export {
80
67
  ShiftTarget,
81
68
  cosineSimilarity,
82
69
  getCachedModels,
83
- } from "../generated/ts/nobodywho";
70
+ } from "../generated";
84
71
 
85
72
  export type {
86
73
  ToolCall,
87
74
  CachedModel,
88
75
  ChatStats,
89
76
  ContextShiftOptions,
90
- } from "../generated/ts/nobodywho";
77
+ } from "../generated";
91
78
  export type { TextToSpeechArchitecture, TextToSpeechDevice, TextToSpeechOptions } from "./text_to_speech";
92
79
  export type { SpeechToTextOptions } from "./speech_to_text";
93
80
  export { VoiceActivityDetectionEvent } from "./vad";
@@ -6,7 +6,7 @@ import {
6
6
  MessageContent as InternalMessageContent,
7
7
  MessageContent_Tags,
8
8
  type ToolCall,
9
- } from "../generated/ts/nobodywho";
9
+ } from "../generated";
10
10
 
11
11
  /** One piece of a message: a run of text, or a media file at this position. */
12
12
  export type ContentPart =
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  loadModel,
3
3
  type RustModelInterface,
4
- } from "../generated/ts/nobodywho";
4
+ } from "../generated";
5
5
 
6
6
  /**
7
7
  * A loaded LLM model that can be shared across Chat, Encoder, and CrossEncoder instances.
@@ -41,7 +41,8 @@ export class Model {
41
41
  * @param opts.projectionModelPath - Path to a multimodal projector file for vision models
42
42
  * @param opts.draftModelPath - Optional MTP draft-heads gguf. Loading it lets `Chat`
43
43
  * instances opt into MTP speculative decoding via `new Chat({ ..., mtp: true })`.
44
- * Adds around 5% to VRAM usage.
44
+ * Adds around 5% to VRAM usage. For models that bundle MTP layers (e.g. Qwen3.5),
45
+ * pass the model file itself; it is only loaded once.
45
46
  * @param opts.onDownloadProgress - Invoked with `(downloaded, total)` byte counts while a remote model is being downloaded. Throttled to ~10 Hz with a guaranteed final emit on completion. Not invoked for cached/local files.
46
47
  */
47
48
  static async load(opts: {
@@ -1,4 +1,4 @@
1
- import { ContentPart } from "../generated/ts/nobodywho";
1
+ import { ContentPart } from "../generated";
2
2
 
3
3
  /** A text part of a multimodal prompt. */
4
4
  class TextPart {
@@ -10,7 +10,7 @@ import {
10
10
  samplerPresetTemperature,
11
11
  samplerPresetTopK,
12
12
  samplerPresetTopP,
13
- } from "../generated/ts/nobodywho";
13
+ } from "../generated";
14
14
 
15
15
  /**
16
16
  * Static factory methods for common sampler configurations.
@@ -1,5 +1,5 @@
1
- import type { RustSpeechToTextInterface } from "../generated/ts/nobodywho";
2
- import * as nobodywho from "../generated/ts/nobodywho";
1
+ import type { RustSpeechToTextInterface } from "../generated";
2
+ import * as nobodywho from "../generated";
3
3
  import { TokenStream } from "./streaming";
4
4
 
5
5
  export type SpeechToTextOptions = {
@@ -1,4 +1,4 @@
1
- import type { RustTokenStreamInterface } from "../generated/ts/nobodywho";
1
+ import type { RustTokenStreamInterface } from "../generated";
2
2
 
3
3
  /**
4
4
  * A stream of response tokens from the model.
@@ -2,7 +2,7 @@ import {
2
2
  RustTextToSpeech,
3
3
  loadTextToSpeech,
4
4
  type RustTextToSpeechInterface,
5
- } from "../generated/ts/nobodywho";
5
+ } from "../generated";
6
6
 
7
7
  export type TextToSpeechArchitecture = "kokoro" | "pocket-tts" | "supertonic";
8
8
  export type TextToSpeechDevice = "auto" | "cpu" | "cuda";
@@ -1,54 +1,8 @@
1
1
  import {
2
2
  RustTool,
3
3
  type ToolParameter,
4
- } from "../generated/ts/nobodywho";
5
-
6
- /**
7
- * Recursively convert a value from JSON based on a JSON Schema type.
8
- *
9
- * Supports primitives (`string`, `number`, `integer`, `boolean`),
10
- * arrays (`{ type: "array", items: ... }`), and nested objects
11
- * (`{ type: "object", properties: { ... } }`).
12
- */
13
- function convertValue(
14
- value: unknown,
15
- schema: Record<string, unknown>,
16
- ): unknown {
17
- if (value === null || value === undefined) {
18
- return value;
19
- }
20
-
21
- switch (schema.type) {
22
- case "integer":
23
- case "number":
24
- return Number(value);
25
- case "boolean":
26
- return typeof value === "string" ? value === "true" : Boolean(value);
27
- case "string":
28
- return String(value);
29
- case "array": {
30
- if (!Array.isArray(value)) return value;
31
- const itemSchema = schema.items as Record<string, unknown> | undefined;
32
- if (!itemSchema) return value;
33
- return value.map((item) => convertValue(item, itemSchema));
34
- }
35
- case "object": {
36
- if (typeof value !== "object" || Array.isArray(value)) return value;
37
- const properties = schema.properties as
38
- | Record<string, Record<string, unknown>>
39
- | undefined;
40
- if (!properties) return value;
41
- const obj = value as Record<string, unknown>;
42
- const result: Record<string, unknown> = {};
43
- for (const [key, propSchema] of Object.entries(properties)) {
44
- result[key] = key in obj ? convertValue(obj[key], propSchema) : undefined;
45
- }
46
- return result;
47
- }
48
- default:
49
- return value;
50
- }
51
- }
4
+ } from "../generated";
5
+ import { convertValue } from "./convert_value";
52
6
 
53
7
  /**
54
8
  * A tool that the model can call during inference.
@@ -137,6 +91,3 @@ export class Tool {
137
91
  return this._inner.getSchemaJson();
138
92
  }
139
93
  }
140
-
141
- /** @internal Exported for testing only. */
142
- export { convertValue as _convertValue };
@@ -1,7 +1,7 @@
1
- import type { RustVoiceActivityDetectionInterface, VoiceActivityDetectionEvent } from "../generated/ts/nobodywho";
2
- import * as nobodywho from "../generated/ts/nobodywho";
1
+ import type { RustVoiceActivityDetectionInterface, VoiceActivityDetectionEvent } from "../generated";
2
+ import * as nobodywho from "../generated";
3
3
 
4
- export { VoiceActivityDetectionEvent } from "../generated/ts/nobodywho";
4
+ export { VoiceActivityDetectionEvent } from "../generated";
5
5
 
6
6
  export type VoiceActivityDetectionOptions = {
7
7
  source?: string;
package/ubrn.config.yaml CHANGED
@@ -4,9 +4,30 @@ rust:
4
4
  manifestPath: uniffi/Cargo.toml
5
5
 
6
6
  bindings:
7
- cpp: generated/cpp
8
- ts: generated/ts
7
+ cpp: cpp/generated
8
+ ts: src/generated/jsi
9
9
 
10
- turboModule:
11
- cpp: cpp
12
- ts: src
10
+ ios:
11
+ frameworkName: NobodyWho
12
+ targets:
13
+ - aarch64-apple-ios
14
+ - aarch64-apple-ios-sim
15
+
16
+ android:
17
+ targets:
18
+ - arm64-v8a
19
+ - x86_64
20
+ apiLevel: 28
21
+ useSharedLibrary: true
22
+
23
+ noOverwrite:
24
+ # Downloads prebuilt xcframework from GitHub Releases, custom authors/source fields
25
+ - Nobodywho.podspec
26
+ # Downloads prebuilt `.so` files from GitHub Releases at build time, optional NDK version
27
+ - android/build.gradle
28
+ # Loads `libnobodywho_uniffi.so` before the bridge lib
29
+ - android/src/main/java/ooo/nobodywho/NobodywhoModule.kt
30
+ # Enables `libOpenCL.so` linking.
31
+ - android/src/main/AndroidManifest.xml
32
+ # Uses TSX syntax unnecessarily, and besides, we want to customize it.
33
+ - src/index.tsx
@@ -1,16 +0,0 @@
1
- // Generated by uniffi-bindgen-react-native
2
- #include "nobodywho-react-native.h"
3
- #include "../generated/cpp/nobodywho.hpp"
4
-
5
- namespace nobodywhoreactnative {
6
- using namespace facebook;
7
-
8
- uint8_t installRustCrate(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker) {
9
- NativeNobodywho::registerModule(runtime, callInvoker);
10
- return true;
11
- }
12
-
13
- uint8_t cleanupRustCrate(jsi::Runtime &runtime) {
14
- return false;
15
- }
16
- }
@@ -1,15 +0,0 @@
1
- #ifndef NOBODYWHOREACTNATIVE_H
2
- #define NOBODYWHOREACTNATIVE_H
3
- // Generated by uniffi-bindgen-react-native
4
- #include <cstdint>
5
- #include <jsi/jsi.h>
6
- #include <ReactCommon/CallInvoker.h>
7
-
8
- namespace nobodywhoreactnative {
9
- using namespace facebook;
10
-
11
- uint8_t installRustCrate(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker);
12
- uint8_t cleanupRustCrate(jsi::Runtime &runtime);
13
- }
14
-
15
- #endif /* NOBODYWHOREACTNATIVE_H */