react-native-nobodywho 3.0.0 → 4.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.
package/package.json CHANGED
@@ -1,21 +1,25 @@
1
1
  {
2
2
  "name": "react-native-nobodywho",
3
- "version": "3.0.0",
4
- "description": "Run LLMs locally with offline inference — React Native bindings",
3
+ "version": "4.0.0",
4
+ "description": "Run LLMs locally and offline with React Native & Expo - Tool calling, RAG, Speech-to-Text, Text-to-Speech, and VAD",
5
5
  "main": "src/wrapper.ts",
6
6
  "types": "src/wrapper.ts",
7
7
  "exports": {
8
8
  ".": "./src/wrapper.ts"
9
9
  },
10
10
  "license": "EUPL-1.2",
11
- "homepage": "https://nobodywho.ooo",
11
+ "homepage": "https://www.nobodywho.ai",
12
12
  "bugs": "https://github.com/nobodywho-ooo/nobodywho/issues",
13
13
  "keywords": [
14
14
  "react-native",
15
+ "expo",
15
16
  "ai",
16
17
  "llm",
17
18
  "local-ai",
18
19
  "inference",
20
+ "stt",
21
+ "tts",
22
+ "vad",
19
23
  "gguf",
20
24
  "on-device",
21
25
  "offline"
package/src/chat.ts CHANGED
@@ -10,6 +10,18 @@ import { TokenStream } from "./streaming";
10
10
  import type { Prompt } from "./prompt";
11
11
  import type { Tool } from "./tool";
12
12
 
13
+ /**
14
+ * Settings to apply before a `complete` turn. An omitted field keeps what the
15
+ * chat has; a set one stays set, like a leading system message.
16
+ */
17
+ export type Options = {
18
+ sampler?: SamplerConfig;
19
+ /** Replaces the chat's template variables wholesale. */
20
+ templateVariables?: Record<string, boolean>;
21
+ /** Re-selects the chat template, so the turn re-prefills from near token zero. */
22
+ tools?: Tool[];
23
+ };
24
+
13
25
  /**
14
26
  * A chat session for local LLM inference.
15
27
  *
@@ -107,6 +119,29 @@ export class Chat {
107
119
  return new TokenStream(this._inner.askWithPrompt(message._parts!));
108
120
  }
109
121
 
122
+ /**
123
+ * Answer a full list of messages, replacing the chat history.
124
+ *
125
+ * The list is the whole conversation, used as given: it must be non-empty, end
126
+ * in a user or tool message, and carry a system message only first. That system
127
+ * message sets the chat's system prompt; leave it out and the prompt already on
128
+ * the chat is kept. The response is appended, and the next `ask` continues from
129
+ * there.
130
+ *
131
+ * `options` follows the same rule for the chat's other settings.
132
+ */
133
+ complete(messages: Message[], options: Options = {}): TokenStream {
134
+ return new TokenStream(
135
+ this._inner.complete(messages.map(toInternal), {
136
+ sampler: options.sampler,
137
+ templateVariables: options.templateVariables
138
+ ? new Map(Object.entries(options.templateVariables))
139
+ : undefined,
140
+ tools: options.tools?.map((t) => t._inner),
141
+ }),
142
+ );
143
+ }
144
+
110
145
  /** Stop the current generation. */
111
146
  stopGeneration(): void {
112
147
  this._inner.stopGeneration();
package/src/message.ts CHANGED
@@ -1,19 +1,45 @@
1
1
  import {
2
+ ContentPart as InternalContentPart,
3
+ ContentPart_Tags,
2
4
  Message as InternalMessage,
3
5
  Message_Tags,
4
- type Asset,
6
+ MessageContent as InternalMessageContent,
7
+ MessageContent_Tags,
5
8
  type ToolCall,
6
9
  } from "../generated/ts/nobodywho";
7
10
 
11
+ /** One piece of a message: a run of text, or a media file at this position. */
12
+ export type ContentPart =
13
+ | { type: "text"; text: string }
14
+ | { type: "image"; path: string }
15
+ | { type: "audio"; path: string };
16
+
17
+ /**
18
+ * Message content: plain text, interleaved text and media, or a raw JSON value
19
+ * handed straight to the chat template.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const content: Content = [
24
+ * { type: "text", text: "What is in this image?" },
25
+ * { type: "image", path: "./dog.png" },
26
+ * ];
27
+ * ```
28
+ */
29
+ export type Content = string | ContentPart[] | { type: "json"; json: string };
30
+
8
31
  /**
9
32
  * A chat message. The variant determines the message type:
10
33
  *
11
- * - **User message:** `{ role: "user", content, assets? }`
34
+ * - **User message:** `{ role: "user", content }`
12
35
  * - **Assistant message:** `{ role: "assistant", content }`
13
36
  * - **Assistant tool call:** `{ role: "assistant", content, toolCalls }`
14
37
  * - **System message:** `{ role: "system", content }`
15
38
  * - **Tool response:** `{ role: "tool", name, content }`
16
39
  *
40
+ * `content` is a string, a list of parts for multimodal input, or a
41
+ * `{ type: "json", json }` wrapper for raw template passthrough.
42
+ *
17
43
  * @example
18
44
  * ```typescript
19
45
  * const history = await chat.getChatHistory();
@@ -29,33 +55,75 @@ import {
29
55
  * ```
30
56
  */
31
57
  export type Message =
32
- | { role: "user"; content: string; assets?: Asset[] }
33
- | { role: "assistant"; content: string }
34
- | { role: "assistant"; content: string; toolCalls: ToolCall[] }
35
- | { role: "system"; content: string }
36
- | { role: "tool"; name: string; content: string };
58
+ | { role: "user"; content: Content }
59
+ | { role: "assistant"; content: Content }
60
+ | { role: "assistant"; content: Content; toolCalls: ToolCall[] }
61
+ | { role: "system"; content: Content }
62
+ | { role: "tool"; name: string; content: Content };
63
+
64
+ function partFromInternal(part: InternalContentPart): ContentPart {
65
+ if (part.tag === ContentPart_Tags.Text) {
66
+ return { type: "text", text: part.inner.text };
67
+ } else if (part.tag === ContentPart_Tags.Image) {
68
+ return { type: "image", path: part.inner.path };
69
+ } else {
70
+ return { type: "audio", path: part.inner.path };
71
+ }
72
+ }
73
+
74
+ function partToInternal(part: ContentPart): InternalContentPart {
75
+ if (part.type === "text") {
76
+ return new InternalContentPart.Text({ text: part.text });
77
+ } else if (part.type === "image") {
78
+ return new InternalContentPart.Image({ path: part.path });
79
+ } else {
80
+ return new InternalContentPart.Audio({ path: part.path });
81
+ }
82
+ }
83
+
84
+ /** @internal */
85
+ export function contentFromInternal(content: InternalMessageContent): Content {
86
+ if (content.tag === MessageContent_Tags.Text) {
87
+ return content.inner.text;
88
+ } else if (content.tag === MessageContent_Tags.Parts) {
89
+ return content.inner.parts.map(partFromInternal);
90
+ } else {
91
+ return { type: "json", json: content.inner.json };
92
+ }
93
+ }
94
+
95
+ /** @internal */
96
+ export function contentToInternal(content: Content): InternalMessageContent {
97
+ if (typeof content === "string") {
98
+ return new InternalMessageContent.Text({ text: content });
99
+ }
100
+ if (Array.isArray(content)) {
101
+ return new InternalMessageContent.Parts({
102
+ parts: content.map(partToInternal),
103
+ });
104
+ }
105
+ return new InternalMessageContent.Json({ json: content.json });
106
+ }
37
107
 
38
108
  /** @internal Convert internal Message to Message */
39
109
  export function fromInternal(msg: InternalMessage): Message {
40
110
  if (msg.tag === Message_Tags.User) {
41
- const { content, assets } = msg.inner;
42
- return {
43
- role: "user",
44
- content,
45
- ...(assets.length > 0 ? { assets } : {}),
46
- };
111
+ return { role: "user", content: contentFromInternal(msg.inner.content) };
47
112
  } else if (msg.tag === Message_Tags.Assistant) {
48
113
  const { content, toolCalls } = msg.inner;
49
114
  if (toolCalls != null && toolCalls.length > 0) {
50
- return { role: "assistant", content, toolCalls };
115
+ return {
116
+ role: "assistant",
117
+ content: contentFromInternal(content),
118
+ toolCalls,
119
+ };
51
120
  }
52
- return { role: "assistant", content };
121
+ return { role: "assistant", content: contentFromInternal(content) };
53
122
  } else if (msg.tag === Message_Tags.System) {
54
- const { content } = msg.inner;
55
- return { role: "system", content };
123
+ return { role: "system", content: contentFromInternal(msg.inner.content) };
56
124
  } else {
57
125
  const { name, content } = msg.inner;
58
- return { role: "tool", name, content };
126
+ return { role: "tool", name, content: contentFromInternal(content) };
59
127
  }
60
128
  }
61
129
 
@@ -63,27 +131,26 @@ export function fromInternal(msg: InternalMessage): Message {
63
131
  export function toInternal(msg: Message): InternalMessage {
64
132
  if (msg.role === "user") {
65
133
  return new InternalMessage.User({
66
- content: msg.content,
67
- assets: msg.assets ?? [],
134
+ content: contentToInternal(msg.content),
68
135
  });
69
136
  } else if (msg.role === "assistant" && "toolCalls" in msg) {
70
137
  return new InternalMessage.Assistant({
71
- content: msg.content,
138
+ content: contentToInternal(msg.content),
72
139
  toolCalls: msg.toolCalls,
73
140
  });
74
141
  } else if (msg.role === "assistant") {
75
142
  return new InternalMessage.Assistant({
76
- content: msg.content,
143
+ content: contentToInternal(msg.content),
77
144
  toolCalls: undefined,
78
145
  });
79
146
  } else if (msg.role === "system") {
80
147
  return new InternalMessage.System({
81
- content: msg.content,
148
+ content: contentToInternal(msg.content),
82
149
  });
83
150
  } else {
84
151
  return new InternalMessage.Tool({
85
152
  name: msg.name,
86
- content: msg.content,
153
+ content: contentToInternal(msg.content),
87
154
  });
88
155
  }
89
156
  }
package/src/prompt.ts CHANGED
@@ -1,32 +1,32 @@
1
- import { PromptPart } from "../generated/ts/nobodywho";
1
+ import { ContentPart } from "../generated/ts/nobodywho";
2
2
 
3
3
  /** A text part of a multimodal prompt. */
4
4
  class TextPart {
5
5
  /** @internal */
6
- readonly _inner: PromptPart;
6
+ readonly _inner: ContentPart;
7
7
 
8
8
  constructor(content: string) {
9
- this._inner = new PromptPart.Text({ content });
9
+ this._inner = new ContentPart.Text({ text: content });
10
10
  }
11
11
  }
12
12
 
13
13
  /** An image part of a multimodal prompt. */
14
14
  class ImagePart {
15
15
  /** @internal */
16
- readonly _inner: PromptPart;
16
+ readonly _inner: ContentPart;
17
17
 
18
18
  constructor(path: string) {
19
- this._inner = new PromptPart.Image({ path });
19
+ this._inner = new ContentPart.Image({ path });
20
20
  }
21
21
  }
22
22
 
23
23
  /** An audio part of a multimodal prompt. */
24
24
  class AudioPart {
25
25
  /** @internal */
26
- readonly _inner: PromptPart;
26
+ readonly _inner: ContentPart;
27
27
 
28
28
  constructor(path: string) {
29
- this._inner = new PromptPart.Audio({ path });
29
+ this._inner = new ContentPart.Audio({ path });
30
30
  }
31
31
  }
32
32
 
@@ -53,7 +53,7 @@ type Part = TextPart | ImagePart | AudioPart;
53
53
  */
54
54
  export class Prompt {
55
55
  /** @internal */
56
- readonly _parts: PromptPart[] | null;
56
+ readonly _parts: ContentPart[] | null;
57
57
  /** @internal */
58
58
  readonly _jsonString: string | null;
59
59
 
package/src/wrapper.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * This is the public entry point. It re-exports the public API:
5
5
  * - Wrapper classes: Model, Chat, Encoder, CrossEncoder, TextToSpeech, Tool, TokenStream, Prompt
6
6
  * - Direct re-exports: SamplerBuilder, SamplerConfig
7
- * - Types: Message, Asset, ToolCall
7
+ * - Types: Message, Content, ContentPart, ToolCall
8
8
  * - Utilities: SamplerPresets, cosineSimilarity, downloadModel
9
9
  *
10
10
  * This file is NOT generated — it is safe to edit.
@@ -57,6 +57,7 @@ export async function downloadModel(opts: {
57
57
  // Wrapper classes
58
58
  export { Model } from "./model";
59
59
  export { Chat } from "./chat";
60
+ export type { Options } from "./chat";
60
61
  export { SpeechToText } from "./speech_to_text";
61
62
  export { VoiceActivityDetection } from "./vad";
62
63
  export { Encoder } from "./encoder";
@@ -67,7 +68,7 @@ export { Tool } from "./tool";
67
68
  export { TokenStream } from "./streaming";
68
69
 
69
70
  // Message types
70
- export type { Message } from "./message";
71
+ export type { Message, Content, ContentPart } from "./message";
71
72
 
72
73
  // Re-export types that don't need wrapping.
73
74
  //
@@ -81,7 +82,6 @@ export {
81
82
  } from "../generated/ts/nobodywho";
82
83
 
83
84
  export type {
84
- Asset,
85
85
  ToolCall,
86
86
  CachedModel,
87
87
  ChatStats,