expo-ai-kit 0.3.6 → 0.4.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/build/index.js CHANGED
@@ -3,8 +3,6 @@ import { Platform } from 'react-native';
3
3
  import { ModelError, } from './types';
4
4
  import { MODEL_REGISTRY, getRegistryEntry } from './models';
5
5
  export * from './types';
6
- export * from './memory';
7
- export * from './hooks';
8
6
  export * from './models';
9
7
  const DEFAULT_SYSTEM_PROMPT = 'You are a helpful, friendly assistant. Answer the user directly and concisely.';
10
8
  let streamIdCounter = 0;
@@ -12,87 +10,8 @@ function generateSessionId() {
12
10
  return `stream_${Date.now()}_${++streamIdCounter}`;
13
11
  }
14
12
  // ============================================================================
15
- // Prompt Helper Constants
13
+ // Inference API
16
14
  // ============================================================================
17
- const SUMMARIZE_LENGTH_INSTRUCTIONS = {
18
- short: 'Keep it very brief, around 1-2 sentences.',
19
- medium: 'Provide a moderate summary, around 3-5 sentences.',
20
- long: 'Provide a comprehensive summary covering all main points.',
21
- };
22
- const SUMMARIZE_STYLE_INSTRUCTIONS = {
23
- paragraph: 'Write the summary as a flowing paragraph.',
24
- bullets: 'Format the summary as bullet points.',
25
- tldr: 'Start with "TL;DR:" and give an extremely concise summary in 1 sentence.',
26
- };
27
- const TRANSLATE_TONE_INSTRUCTIONS = {
28
- formal: 'Use formal language and honorifics where appropriate.',
29
- informal: 'Use casual, everyday language.',
30
- neutral: 'Use standard, neutral language.',
31
- };
32
- const REWRITE_STYLE_INSTRUCTIONS = {
33
- formal: 'Rewrite in a formal, professional tone suitable for business communication.',
34
- casual: 'Rewrite in a casual, conversational tone.',
35
- professional: 'Rewrite in a clear, professional tone suitable for work contexts.',
36
- friendly: 'Rewrite in a warm, friendly tone.',
37
- concise: 'Rewrite to be as brief as possible while keeping the meaning intact.',
38
- detailed: 'Expand and add more detail and explanation.',
39
- simple: 'Rewrite using simple words and short sentences, easy for anyone to understand.',
40
- academic: 'Rewrite in an academic style suitable for scholarly writing.',
41
- };
42
- const SUGGEST_TONE_INSTRUCTIONS = {
43
- formal: 'Use formal, professional language.',
44
- casual: 'Use casual, everyday language.',
45
- professional: 'Use clear, professional language suitable for work.',
46
- friendly: 'Use warm, friendly language.',
47
- neutral: 'Use standard, neutral language.',
48
- };
49
- const ANSWER_DETAIL_INSTRUCTIONS = {
50
- brief: 'Give a brief, direct answer in 1-2 sentences.',
51
- medium: 'Provide a clear answer with some explanation.',
52
- detailed: 'Provide a comprehensive answer with full explanation and relevant details from the context.',
53
- };
54
- // ============================================================================
55
- // Prompt Builder Helpers
56
- // ============================================================================
57
- function buildSummarizePrompt(length, style) {
58
- return `You are a summarization assistant. Summarize the provided text accurately and concisely. ${SUMMARIZE_LENGTH_INSTRUCTIONS[length]} ${SUMMARIZE_STYLE_INSTRUCTIONS[style]} Only output the summary, nothing else.`;
59
- }
60
- function buildTranslatePrompt(to, from, tone) {
61
- const fromClause = from ? `from ${from} ` : '';
62
- return `You are a translation assistant. Translate the provided text ${fromClause}to ${to}. ${TRANSLATE_TONE_INSTRUCTIONS[tone]} Only output the translation, nothing else. Do not include any explanations or notes.`;
63
- }
64
- function buildRewritePrompt(style) {
65
- return `You are a writing assistant. ${REWRITE_STYLE_INSTRUCTIONS[style]} Preserve the original meaning. Only output the rewritten text, nothing else.`;
66
- }
67
- function buildExtractKeyPointsPrompt(maxPoints) {
68
- return `You are an analysis assistant. Extract the ${maxPoints} most important key points from the provided text. Format each point as a bullet point starting with "•". Be concise and focus on the most significant information. Only output the bullet points, nothing else.`;
69
- }
70
- function buildSuggestPrompt(count, tone, context) {
71
- const contextClause = context
72
- ? ` The user is writing in this context: "${context}".`
73
- : '';
74
- return `You are a text suggestion assistant. Given the user's partial text, generate exactly ${count} possible continuations or completions.${contextClause} ${SUGGEST_TONE_INSTRUCTIONS[tone]} Output ONLY the suggestions, one per line, numbered like "1. suggestion here". Do not include any other text or explanation.`;
75
- }
76
- function buildSmartReplyPrompt(count, tone, persona) {
77
- const personaClause = persona ? ` You are replying as: ${persona}.` : '';
78
- return `You are a smart reply assistant. Given a conversation, generate exactly ${count} short, contextually appropriate reply suggestions that the user could send as their next message.${personaClause} ${SUGGEST_TONE_INSTRUCTIONS[tone]} Each reply should be a complete, ready-to-send message. Output ONLY the replies, one per line, numbered like "1. reply here". Do not include any other text or explanation.`;
79
- }
80
- function buildAutocompletePrompt(count, maxWords, context) {
81
- const contextClause = context
82
- ? ` The user is writing about: "${context}".`
83
- : '';
84
- return `You are an autocomplete assistant. Given the user's partial text, generate exactly ${count} natural completions of the current sentence or phrase. Each completion should be at most ${maxWords} words and should seamlessly continue from the user's text.${contextClause} Output ONLY the completions, one per line, numbered like "1. completion here". Do not repeat the user's text. Do not include any other text or explanation.`;
85
- }
86
- function parseSuggestions(raw) {
87
- return raw
88
- .split('\n')
89
- .map((line) => line.replace(/^\d+[\.\)]\s*/, '').trim())
90
- .filter((line) => line.length > 0)
91
- .map((text) => ({ text }));
92
- }
93
- function buildAnswerQuestionPrompt(detail) {
94
- return `You are a question-answering assistant. Answer questions based ONLY on the provided context. ${ANSWER_DETAIL_INSTRUCTIONS[detail]} If the answer cannot be found in the context, say so. Do not make up information.`;
95
- }
96
15
  /**
97
16
  * Check if on-device AI is available on the current device.
98
17
  * Returns false on unsupported platforms (web, etc.).
@@ -184,17 +103,6 @@ export async function sendMessage(messages, options) {
184
103
  * // Cancel after 5 seconds
185
104
  * setTimeout(() => stop(), 5000);
186
105
  * ```
187
- *
188
- * @example
189
- * ```ts
190
- * // React state update pattern
191
- * const [text, setText] = useState('');
192
- *
193
- * streamMessage(
194
- * [{ role: 'user', content: 'Hello!' }],
195
- * (event) => setText(event.accumulatedText)
196
- * );
197
- * ```
198
106
  */
199
107
  export function streamMessage(messages, onToken, options) {
200
108
  // Handle unsupported platforms
@@ -250,650 +158,6 @@ export function streamMessage(messages, onToken, options) {
250
158
  return { promise, stop };
251
159
  }
252
160
  // ============================================================================
253
- // Prompt Helpers
254
- // ============================================================================
255
- /**
256
- * Summarize text content using on-device AI.
257
- *
258
- * @param text - The text to summarize
259
- * @param options - Optional settings for summary style and length
260
- * @returns Promise with the generated summary
261
- *
262
- * @example
263
- * ```ts
264
- * // Basic summarization
265
- * const result = await summarize(longArticle);
266
- * console.log(result.text);
267
- * ```
268
- *
269
- * @example
270
- * ```ts
271
- * // Short bullet-point summary
272
- * const result = await summarize(longArticle, {
273
- * length: 'short',
274
- * style: 'bullets'
275
- * });
276
- * ```
277
- *
278
- * @example
279
- * ```ts
280
- * // TL;DR style
281
- * const result = await summarize(longArticle, {
282
- * style: 'tldr'
283
- * });
284
- * ```
285
- */
286
- export async function summarize(text, options) {
287
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
288
- return { text: '' };
289
- }
290
- if (!text || text.trim().length === 0) {
291
- throw new Error('text cannot be empty');
292
- }
293
- const length = options?.length ?? 'medium';
294
- const style = options?.style ?? 'paragraph';
295
- const systemPrompt = buildSummarizePrompt(length, style);
296
- return sendMessage([{ role: 'user', content: text }], { systemPrompt });
297
- }
298
- /**
299
- * Summarize text with streaming output.
300
- *
301
- * @param text - The text to summarize
302
- * @param onToken - Callback for each token received
303
- * @param options - Optional settings for summary style and length
304
- * @returns Object with stop() function and promise
305
- *
306
- * @example
307
- * ```ts
308
- * const { promise } = streamSummarize(
309
- * longArticle,
310
- * (event) => setSummary(event.accumulatedText),
311
- * { style: 'bullets' }
312
- * );
313
- * await promise;
314
- * ```
315
- */
316
- export function streamSummarize(text, onToken, options) {
317
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
318
- return { promise: Promise.resolve({ text: '' }), stop: () => { } };
319
- }
320
- if (!text || text.trim().length === 0) {
321
- return {
322
- promise: Promise.reject(new Error('text cannot be empty')),
323
- stop: () => { },
324
- };
325
- }
326
- const length = options?.length ?? 'medium';
327
- const style = options?.style ?? 'paragraph';
328
- const systemPrompt = buildSummarizePrompt(length, style);
329
- return streamMessage([{ role: 'user', content: text }], onToken, {
330
- systemPrompt,
331
- });
332
- }
333
- /**
334
- * Translate text to another language using on-device AI.
335
- *
336
- * @param text - The text to translate
337
- * @param options - Translation options including target language
338
- * @returns Promise with the translated text
339
- *
340
- * @example
341
- * ```ts
342
- * // Basic translation
343
- * const result = await translate('Hello, world!', { to: 'Spanish' });
344
- * console.log(result.text); // "¡Hola, mundo!"
345
- * ```
346
- *
347
- * @example
348
- * ```ts
349
- * // Formal translation with source language
350
- * const result = await translate('Hey, what\'s up?', {
351
- * to: 'French',
352
- * from: 'English',
353
- * tone: 'formal'
354
- * });
355
- * ```
356
- */
357
- export async function translate(text, options) {
358
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
359
- return { text: '' };
360
- }
361
- if (!text || text.trim().length === 0) {
362
- throw new Error('text cannot be empty');
363
- }
364
- const { to, from, tone = 'neutral' } = options;
365
- const systemPrompt = buildTranslatePrompt(to, from, tone);
366
- return sendMessage([{ role: 'user', content: text }], { systemPrompt });
367
- }
368
- /**
369
- * Translate text with streaming output.
370
- *
371
- * @param text - The text to translate
372
- * @param onToken - Callback for each token received
373
- * @param options - Translation options including target language
374
- * @returns Object with stop() function and promise
375
- *
376
- * @example
377
- * ```ts
378
- * const { promise } = streamTranslate(
379
- * 'Hello, world!',
380
- * (event) => setTranslation(event.accumulatedText),
381
- * { to: 'Japanese' }
382
- * );
383
- * await promise;
384
- * ```
385
- */
386
- export function streamTranslate(text, onToken, options) {
387
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
388
- return { promise: Promise.resolve({ text: '' }), stop: () => { } };
389
- }
390
- if (!text || text.trim().length === 0) {
391
- return {
392
- promise: Promise.reject(new Error('text cannot be empty')),
393
- stop: () => { },
394
- };
395
- }
396
- const { to, from, tone = 'neutral' } = options;
397
- const systemPrompt = buildTranslatePrompt(to, from, tone);
398
- return streamMessage([{ role: 'user', content: text }], onToken, {
399
- systemPrompt,
400
- });
401
- }
402
- /**
403
- * Rewrite text in a different style using on-device AI.
404
- *
405
- * @param text - The text to rewrite
406
- * @param options - Rewrite options specifying the target style
407
- * @returns Promise with the rewritten text
408
- *
409
- * @example
410
- * ```ts
411
- * // Make text more formal
412
- * const result = await rewrite('hey can u help me out?', {
413
- * style: 'formal'
414
- * });
415
- * console.log(result.text); // "Would you be able to assist me?"
416
- * ```
417
- *
418
- * @example
419
- * ```ts
420
- * // Simplify complex text
421
- * const result = await rewrite(technicalText, { style: 'simple' });
422
- * ```
423
- */
424
- export async function rewrite(text, options) {
425
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
426
- return { text: '' };
427
- }
428
- if (!text || text.trim().length === 0) {
429
- throw new Error('text cannot be empty');
430
- }
431
- const { style } = options;
432
- const systemPrompt = buildRewritePrompt(style);
433
- return sendMessage([{ role: 'user', content: text }], { systemPrompt });
434
- }
435
- /**
436
- * Rewrite text with streaming output.
437
- *
438
- * @param text - The text to rewrite
439
- * @param onToken - Callback for each token received
440
- * @param options - Rewrite options specifying the target style
441
- * @returns Object with stop() function and promise
442
- *
443
- * @example
444
- * ```ts
445
- * const { promise } = streamRewrite(
446
- * 'hey whats up',
447
- * (event) => setRewritten(event.accumulatedText),
448
- * { style: 'professional' }
449
- * );
450
- * await promise;
451
- * ```
452
- */
453
- export function streamRewrite(text, onToken, options) {
454
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
455
- return { promise: Promise.resolve({ text: '' }), stop: () => { } };
456
- }
457
- if (!text || text.trim().length === 0) {
458
- return {
459
- promise: Promise.reject(new Error('text cannot be empty')),
460
- stop: () => { },
461
- };
462
- }
463
- const { style } = options;
464
- const systemPrompt = buildRewritePrompt(style);
465
- return streamMessage([{ role: 'user', content: text }], onToken, {
466
- systemPrompt,
467
- });
468
- }
469
- /**
470
- * Extract key points from text using on-device AI.
471
- *
472
- * @param text - The text to extract key points from
473
- * @param options - Optional settings for extraction
474
- * @returns Promise with the key points as text
475
- *
476
- * @example
477
- * ```ts
478
- * // Extract key points from an article
479
- * const result = await extractKeyPoints(article);
480
- * console.log(result.text);
481
- * // "• Point 1\n• Point 2\n• Point 3"
482
- * ```
483
- *
484
- * @example
485
- * ```ts
486
- * // Limit to 3 key points
487
- * const result = await extractKeyPoints(article, { maxPoints: 3 });
488
- * ```
489
- */
490
- export async function extractKeyPoints(text, options) {
491
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
492
- return { text: '' };
493
- }
494
- if (!text || text.trim().length === 0) {
495
- throw new Error('text cannot be empty');
496
- }
497
- const maxPoints = options?.maxPoints ?? 5;
498
- const systemPrompt = buildExtractKeyPointsPrompt(maxPoints);
499
- return sendMessage([{ role: 'user', content: text }], { systemPrompt });
500
- }
501
- /**
502
- * Extract key points with streaming output.
503
- *
504
- * @param text - The text to extract key points from
505
- * @param onToken - Callback for each token received
506
- * @param options - Optional settings for extraction
507
- * @returns Object with stop() function and promise
508
- *
509
- * @example
510
- * ```ts
511
- * const { promise } = streamExtractKeyPoints(
512
- * article,
513
- * (event) => setKeyPoints(event.accumulatedText),
514
- * { maxPoints: 5 }
515
- * );
516
- * await promise;
517
- * ```
518
- */
519
- export function streamExtractKeyPoints(text, onToken, options) {
520
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
521
- return { promise: Promise.resolve({ text: '' }), stop: () => { } };
522
- }
523
- if (!text || text.trim().length === 0) {
524
- return {
525
- promise: Promise.reject(new Error('text cannot be empty')),
526
- stop: () => { },
527
- };
528
- }
529
- const maxPoints = options?.maxPoints ?? 5;
530
- const systemPrompt = buildExtractKeyPointsPrompt(maxPoints);
531
- return streamMessage([{ role: 'user', content: text }], onToken, {
532
- systemPrompt,
533
- });
534
- }
535
- /**
536
- * Answer a question based on provided context using on-device AI.
537
- *
538
- * @param question - The question to answer
539
- * @param context - The context/document to base the answer on
540
- * @param options - Optional settings for the answer
541
- * @returns Promise with the answer
542
- *
543
- * @example
544
- * ```ts
545
- * // Answer a question about a document
546
- * const result = await answerQuestion(
547
- * 'What is the main topic?',
548
- * documentText
549
- * );
550
- * console.log(result.text);
551
- * ```
552
- *
553
- * @example
554
- * ```ts
555
- * // Get a detailed answer
556
- * const result = await answerQuestion(
557
- * 'Explain the methodology',
558
- * researchPaper,
559
- * { detail: 'detailed' }
560
- * );
561
- * ```
562
- */
563
- export async function answerQuestion(question, context, options) {
564
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
565
- return { text: '' };
566
- }
567
- if (!question || question.trim().length === 0) {
568
- throw new Error('question cannot be empty');
569
- }
570
- if (!context || context.trim().length === 0) {
571
- throw new Error('context cannot be empty');
572
- }
573
- const detail = options?.detail ?? 'medium';
574
- const systemPrompt = buildAnswerQuestionPrompt(detail);
575
- const userContent = `Context:\n${context}\n\nQuestion: ${question}`;
576
- return sendMessage([{ role: 'user', content: userContent }], { systemPrompt });
577
- }
578
- /**
579
- * Answer a question with streaming output.
580
- *
581
- * @param question - The question to answer
582
- * @param context - The context/document to base the answer on
583
- * @param onToken - Callback for each token received
584
- * @param options - Optional settings for the answer
585
- * @returns Object with stop() function and promise
586
- *
587
- * @example
588
- * ```ts
589
- * const { promise } = streamAnswerQuestion(
590
- * 'What are the key findings?',
591
- * documentText,
592
- * (event) => setAnswer(event.accumulatedText),
593
- * { detail: 'detailed' }
594
- * );
595
- * await promise;
596
- * ```
597
- */
598
- export function streamAnswerQuestion(question, context, onToken, options) {
599
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
600
- return { promise: Promise.resolve({ text: '' }), stop: () => { } };
601
- }
602
- if (!question || question.trim().length === 0) {
603
- return {
604
- promise: Promise.reject(new Error('question cannot be empty')),
605
- stop: () => { },
606
- };
607
- }
608
- if (!context || context.trim().length === 0) {
609
- return {
610
- promise: Promise.reject(new Error('context cannot be empty')),
611
- stop: () => { },
612
- };
613
- }
614
- const detail = options?.detail ?? 'medium';
615
- const systemPrompt = buildAnswerQuestionPrompt(detail);
616
- const userContent = `Context:\n${context}\n\nQuestion: ${question}`;
617
- return streamMessage([{ role: 'user', content: userContent }], onToken, {
618
- systemPrompt,
619
- });
620
- }
621
- // ============================================================================
622
- // Smart Suggestions
623
- // ============================================================================
624
- /**
625
- * Generate text suggestions based on partial input using on-device AI.
626
- *
627
- * Useful for text completion, writing assistance, and predictive text features.
628
- *
629
- * @param partialText - The text the user has typed so far
630
- * @param options - Optional settings for suggestions
631
- * @returns Promise with an array of suggestions
632
- *
633
- * @example
634
- * ```ts
635
- * // Basic suggestions
636
- * const result = await suggest('I think we should');
637
- * result.suggestions.forEach(s => console.log(s.text));
638
- * // "schedule a meeting to discuss this further"
639
- * // "consider an alternative approach"
640
- * // "move forward with the plan"
641
- * ```
642
- *
643
- * @example
644
- * ```ts
645
- * // With context and tone
646
- * const result = await suggest('Dear Mr. Johnson,', {
647
- * count: 5,
648
- * context: 'writing a business email about project delays',
649
- * tone: 'formal'
650
- * });
651
- * ```
652
- */
653
- export async function suggest(partialText, options) {
654
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
655
- return { suggestions: [], raw: '' };
656
- }
657
- if (!partialText || partialText.trim().length === 0) {
658
- throw new Error('partialText cannot be empty');
659
- }
660
- const count = options?.count ?? 3;
661
- const tone = options?.tone ?? 'neutral';
662
- const systemPrompt = buildSuggestPrompt(count, tone, options?.context);
663
- const response = await sendMessage([{ role: 'user', content: partialText }], { systemPrompt });
664
- return {
665
- suggestions: parseSuggestions(response.text),
666
- raw: response.text,
667
- };
668
- }
669
- /**
670
- * Generate text suggestions with streaming output.
671
- *
672
- * @param partialText - The text the user has typed so far
673
- * @param onToken - Callback for each token received
674
- * @param options - Optional settings for suggestions
675
- * @returns Object with stop() function and promise
676
- *
677
- * @example
678
- * ```ts
679
- * const { promise } = streamSuggest(
680
- * 'The best way to',
681
- * (event) => setRawSuggestions(event.accumulatedText)
682
- * );
683
- * const result = await promise;
684
- * // Parse suggestions from result.text
685
- * ```
686
- */
687
- export function streamSuggest(partialText, onToken, options) {
688
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
689
- return { promise: Promise.resolve({ text: '' }), stop: () => { } };
690
- }
691
- if (!partialText || partialText.trim().length === 0) {
692
- return {
693
- promise: Promise.reject(new Error('partialText cannot be empty')),
694
- stop: () => { },
695
- };
696
- }
697
- const count = options?.count ?? 3;
698
- const tone = options?.tone ?? 'neutral';
699
- const systemPrompt = buildSuggestPrompt(count, tone, options?.context);
700
- return streamMessage([{ role: 'user', content: partialText }], onToken, {
701
- systemPrompt,
702
- });
703
- }
704
- /**
705
- * Generate smart reply suggestions for a conversation using on-device AI.
706
- *
707
- * Analyzes the conversation history and generates contextually appropriate
708
- * reply options, similar to Gmail/Messages smart replies.
709
- *
710
- * @param messages - The conversation history to generate replies for
711
- * @param options - Optional settings for reply generation
712
- * @returns Promise with an array of reply suggestions
713
- *
714
- * @example
715
- * ```ts
716
- * // Basic smart replies
717
- * const result = await smartReply([
718
- * { role: 'user', content: 'Hey, are you free for lunch tomorrow?' }
719
- * ]);
720
- * result.suggestions.forEach(s => console.log(s.text));
721
- * // "Sure, what time works for you?"
722
- * // "Sorry, I already have plans."
723
- * // "Let me check my schedule and get back to you."
724
- * ```
725
- *
726
- * @example
727
- * ```ts
728
- * // With persona and tone
729
- * const result = await smartReply(
730
- * [
731
- * { role: 'user', content: 'We need the report by Friday.' },
732
- * { role: 'assistant', content: 'I will do my best.' },
733
- * { role: 'user', content: 'Can you confirm the deadline works?' }
734
- * ],
735
- * { tone: 'professional', persona: 'project manager', count: 4 }
736
- * );
737
- * ```
738
- */
739
- export async function smartReply(messages, options) {
740
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
741
- return { suggestions: [], raw: '' };
742
- }
743
- if (!messages || messages.length === 0) {
744
- throw new Error('messages array cannot be empty');
745
- }
746
- const count = options?.count ?? 3;
747
- const tone = options?.tone ?? 'neutral';
748
- const systemPrompt = buildSmartReplyPrompt(count, tone, options?.persona);
749
- const conversation = messages
750
- .map((m) => `${m.role === 'user' ? 'Them' : 'Me'}: ${m.content}`)
751
- .join('\n');
752
- const response = await sendMessage([{ role: 'user', content: `Conversation:\n${conversation}\n\nGenerate ${count} reply suggestions for my next message.` }], { systemPrompt });
753
- return {
754
- suggestions: parseSuggestions(response.text),
755
- raw: response.text,
756
- };
757
- }
758
- /**
759
- * Generate smart reply suggestions with streaming output.
760
- *
761
- * @param messages - The conversation history to generate replies for
762
- * @param onToken - Callback for each token received
763
- * @param options - Optional settings for reply generation
764
- * @returns Object with stop() function and promise
765
- *
766
- * @example
767
- * ```ts
768
- * const { promise } = streamSmartReply(
769
- * [{ role: 'user', content: 'Want to grab coffee?' }],
770
- * (event) => setRawReplies(event.accumulatedText)
771
- * );
772
- * const result = await promise;
773
- * ```
774
- */
775
- export function streamSmartReply(messages, onToken, options) {
776
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
777
- return { promise: Promise.resolve({ text: '' }), stop: () => { } };
778
- }
779
- if (!messages || messages.length === 0) {
780
- return {
781
- promise: Promise.reject(new Error('messages array cannot be empty')),
782
- stop: () => { },
783
- };
784
- }
785
- const count = options?.count ?? 3;
786
- const tone = options?.tone ?? 'neutral';
787
- const systemPrompt = buildSmartReplyPrompt(count, tone, options?.persona);
788
- const conversation = messages
789
- .map((m) => `${m.role === 'user' ? 'Them' : 'Me'}: ${m.content}`)
790
- .join('\n');
791
- return streamMessage([{ role: 'user', content: `Conversation:\n${conversation}\n\nGenerate ${count} reply suggestions for my next message.` }], onToken, { systemPrompt });
792
- }
793
- /**
794
- * Autocomplete the user's current text using on-device AI.
795
- *
796
- * Generates short, natural completions that seamlessly continue from
797
- * the user's partial input. Ideal for real-time typing suggestions.
798
- *
799
- * @param partialText - The text the user has typed so far
800
- * @param options - Optional settings for autocompletion
801
- * @returns Promise with an array of completion suggestions
802
- *
803
- * @example
804
- * ```ts
805
- * // Basic autocomplete
806
- * const result = await autocomplete('How do I');
807
- * result.suggestions.forEach(s => console.log(s.text));
808
- * // "reset my password"
809
- * // "contact support"
810
- * // "cancel my subscription"
811
- * ```
812
- *
813
- * @example
814
- * ```ts
815
- * // With context for better suggestions
816
- * const result = await autocomplete('The patient presents with', {
817
- * context: 'medical notes',
818
- * maxWords: 15,
819
- * count: 5
820
- * });
821
- * ```
822
- */
823
- export async function autocomplete(partialText, options) {
824
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
825
- return { suggestions: [], raw: '' };
826
- }
827
- if (!partialText || partialText.trim().length === 0) {
828
- throw new Error('partialText cannot be empty');
829
- }
830
- const count = options?.count ?? 3;
831
- const maxWords = options?.maxWords ?? 10;
832
- const systemPrompt = buildAutocompletePrompt(count, maxWords, options?.context);
833
- const response = await sendMessage([{ role: 'user', content: partialText }], { systemPrompt });
834
- return {
835
- suggestions: parseSuggestions(response.text),
836
- raw: response.text,
837
- };
838
- }
839
- /**
840
- * Autocomplete text with streaming output.
841
- *
842
- * @param partialText - The text the user has typed so far
843
- * @param onToken - Callback for each token received
844
- * @param options - Optional settings for autocompletion
845
- * @returns Object with stop() function and promise
846
- *
847
- * @example
848
- * ```ts
849
- * const { promise } = streamAutocomplete(
850
- * 'I would like to',
851
- * (event) => setRawCompletions(event.accumulatedText)
852
- * );
853
- * const result = await promise;
854
- * ```
855
- */
856
- export function streamAutocomplete(partialText, onToken, options) {
857
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
858
- return { promise: Promise.resolve({ text: '' }), stop: () => { } };
859
- }
860
- if (!partialText || partialText.trim().length === 0) {
861
- return {
862
- promise: Promise.reject(new Error('partialText cannot be empty')),
863
- stop: () => { },
864
- };
865
- }
866
- const count = options?.count ?? 3;
867
- const maxWords = options?.maxWords ?? 10;
868
- const systemPrompt = buildAutocompletePrompt(count, maxWords, options?.context);
869
- return streamMessage([{ role: 'user', content: partialText }], onToken, {
870
- systemPrompt,
871
- });
872
- }
873
- /**
874
- * Parse suggestion text from a suggest/smartReply/autocomplete response.
875
- *
876
- * Use this to parse the raw text from streaming responses into structured suggestions.
877
- *
878
- * @param raw - Raw text response from the model
879
- * @returns Array of parsed suggestions
880
- *
881
- * @example
882
- * ```ts
883
- * const { promise } = streamSuggest('Hello', (event) => {
884
- * setText(event.accumulatedText);
885
- * });
886
- * const result = await promise;
887
- * const suggestions = parseSuggestResponse(result.text);
888
- * ```
889
- */
890
- export function parseSuggestResponse(raw) {
891
- return {
892
- suggestions: parseSuggestions(raw),
893
- raw,
894
- };
895
- }
896
- // ============================================================================
897
161
  // Model Management API
898
162
  // ============================================================================
899
163
  /**