@alia.onl/sdk 10.1.1 → 11.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 (64) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +24 -0
  3. package/package.json +4 -6
  4. package/src/components/AliaAvatar.tsx +66 -0
  5. package/src/components/AliaChatContent.tsx +235 -112
  6. package/src/components/AliaChatHeader.tsx +118 -0
  7. package/src/components/AliaChatMessageList.tsx +171 -260
  8. package/src/components/AliaChatScreen.tsx +23 -24
  9. package/src/components/AliaChatSheet.tsx +70 -316
  10. package/src/components/AliaWelcomeMessage.tsx +47 -50
  11. package/src/components/AmbientField.tsx +577 -0
  12. package/src/components/IdentityMark.tsx +10 -2
  13. package/src/components/Markdown.tsx +116 -40
  14. package/src/components/PlanPreviewCard.tsx +71 -55
  15. package/src/components/ThinkingBlock.tsx +47 -0
  16. package/src/components/VoiceModeIcon.tsx +10 -0
  17. package/src/components/voice/VoiceControls.tsx +22 -73
  18. package/src/components/voice/VoiceSession.tsx +2 -1
  19. package/src/hooks/useAliaChat.ts +22 -15
  20. package/src/hooks/useAmbientWave.ts +35 -5
  21. package/src/hooks/useSoundEffects.ts +14 -11
  22. package/src/hooks/useSpeechToText.ts +9 -2
  23. package/src/hooks/useTTS.ts +163 -156
  24. package/src/hooks/useVoiceRoom.ts +228 -170
  25. package/src/index.ts +18 -6
  26. package/src/lib/catalogue.ts +17 -4
  27. package/src/lib/chat-stream.ts +25 -7
  28. package/src/lib/chat-transport.ts +2 -1
  29. package/src/lib/citations.ts +63 -0
  30. package/src/lib/content-utils.ts +3 -2
  31. package/src/lib/image-data-url.ts +15 -0
  32. package/src/lib/keyboard.tsx +1 -1
  33. package/src/lib/labels.tsx +81 -0
  34. package/src/lib/speech-recognition.native.ts +15 -4
  35. package/src/lib/speech-recognition.ts +27 -7
  36. package/src/lib/task-labels.ts +67 -0
  37. package/src/lib/tool-call-status.ts +28 -0
  38. package/src/lib/tool-registry.ts +14 -14
  39. package/src/lib/voice-text.ts +12 -6
  40. package/src/lib/voice-turn.ts +4 -1
  41. package/src/lib/work-log-copy.ts +61 -0
  42. package/src/lib/work-log.ts +378 -0
  43. package/src/tools.ts +11 -0
  44. package/src/types.ts +5 -2
  45. package/src/voice.ts +12 -4
  46. package/src/components/Reasoning.tsx +0 -152
  47. package/src/components/ResearchProgressCard.tsx +0 -159
  48. package/src/components/ThinkingIndicator.tsx +0 -98
  49. package/src/components/ui/button.tsx +0 -104
  50. package/src/components/ui/chat-text-input.tsx +0 -184
  51. package/src/components/ui/dropdown-menu.tsx +0 -2
  52. package/src/components/ui/dropdown-menu.web.tsx +0 -383
  53. package/src/components/ui/prompt-input/actions.tsx +0 -28
  54. package/src/components/ui/prompt-input/add-menu.tsx +0 -83
  55. package/src/components/ui/prompt-input/attachments.tsx +0 -181
  56. package/src/components/ui/prompt-input/autocomplete.tsx +0 -168
  57. package/src/components/ui/prompt-input/context.tsx +0 -71
  58. package/src/components/ui/prompt-input/mic-button.tsx +0 -53
  59. package/src/components/ui/prompt-input/prompt-input.tsx +0 -279
  60. package/src/components/ui/prompt-input/submit-button.tsx +0 -50
  61. package/src/components/ui/prompt-input/textarea.tsx +0 -80
  62. package/src/components/ui/text.tsx +0 -29
  63. package/src/components/voice/AudioWaveVisualizer.tsx +0 -444
  64. package/src/components/voice/VoiceOverlay.tsx +0 -75
@@ -37,8 +37,15 @@ import {
37
37
  requestSpeechRecognitionPermission,
38
38
  startSpeechRecognition,
39
39
  } from '../lib/speech-recognition';
40
- import type { SpeechRecognitionFailure, SpeechRecognitionSession } from '../lib/speech-recognition-types';
41
- import { speechFailureCode, VOICE_ERROR_MESSAGES, type VoiceErrorCode } from '../lib/speech-messages';
40
+ import type {
41
+ SpeechRecognitionFailure,
42
+ SpeechRecognitionSession,
43
+ } from '../lib/speech-recognition-types';
44
+ import {
45
+ speechFailureCode,
46
+ VOICE_ERROR_MESSAGES,
47
+ type VoiceErrorCode,
48
+ } from '../lib/speech-messages';
42
49
  import { defaultSpeechLanguage } from '../lib/speech-language';
43
50
  import { playSpeechClip, requestSpeechClip, type ProductVoice } from '../lib/speech-synthesis';
44
51
  import { VoiceLevelChannel, type VoiceLevelSource } from '../lib/voice-levels';
@@ -146,7 +153,9 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
146
153
  * the code's English copy, except for `turn-failed`, which carries what the
147
154
  * turn sender threw.
148
155
  */
149
- const [callFailure, setCallFailure] = useState<{ code: VoiceErrorCode; message: string } | null>(null);
156
+ const [callFailure, setCallFailure] = useState<{ code: VoiceErrorCode; message: string } | null>(
157
+ null,
158
+ );
150
159
  /** A turn that failed without ending the call: no answer, or no audio for it. */
151
160
  const [turnErrorCode, setTurnErrorCode] = useState<VoiceErrorCode | null>(null);
152
161
  const [messages, setMessagesState] = useState<VoiceMessage[]>([]);
@@ -218,9 +227,14 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
218
227
  return `${sessionPrefixRef.current}-${msgIdRef.current}`;
219
228
  };
220
229
 
221
- const patchMessage = useCallback((id: string, patch: Partial<VoiceMessage>) => {
222
- updateMessages((previous) => previous.map((message) => (message.id === id ? { ...message, ...patch } : message)));
223
- }, [updateMessages]);
230
+ const patchMessage = useCallback(
231
+ (id: string, patch: Partial<VoiceMessage>) => {
232
+ updateMessages((previous) =>
233
+ previous.map((message) => (message.id === id ? { ...message, ...patch } : message)),
234
+ );
235
+ },
236
+ [updateMessages],
237
+ );
224
238
 
225
239
  const getToken = useCallback((): string | null => {
226
240
  const { accessToken } = configRef.current;
@@ -237,26 +251,32 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
237
251
 
238
252
  // ============== TEARDOWN ==============
239
253
 
240
- const endTurn = useCallback((turn: ActiveTurn) => {
241
- turn.controller.abort();
242
- turn.closed = true;
243
- turn.wake?.();
244
- if (turnRef.current === turn) turnRef.current = null;
245
- const message = messagesRef.current.find((candidate) => candidate.id === turn.assistantId);
246
- if (message?.isStreaming) {
247
- if (message.content.trim() === '') {
248
- updateMessages((previous) => previous.filter((candidate) => candidate.id !== turn.assistantId));
249
- } else {
250
- patchMessage(turn.assistantId, { isStreaming: false });
254
+ const endTurn = useCallback(
255
+ (turn: ActiveTurn) => {
256
+ turn.controller.abort();
257
+ turn.closed = true;
258
+ turn.wake?.();
259
+ if (turnRef.current === turn) turnRef.current = null;
260
+ const message = messagesRef.current.find((candidate) => candidate.id === turn.assistantId);
261
+ if (message?.isStreaming) {
262
+ if (message.content.trim() === '') {
263
+ updateMessages((previous) =>
264
+ previous.filter((candidate) => candidate.id !== turn.assistantId),
265
+ );
266
+ } else {
267
+ patchMessage(turn.assistantId, { isStreaming: false });
268
+ }
251
269
  }
252
- }
253
- levels.setPlayback(0);
254
- }, [levels, patchMessage, updateMessages]);
270
+ levels.setPlayback(0);
271
+ },
272
+ [levels, patchMessage, updateMessages],
273
+ );
255
274
 
256
275
  const discardDraft = useCallback(() => {
257
276
  const draftId = draftIdRef.current;
258
277
  draftIdRef.current = null;
259
- if (draftId !== null) updateMessages((previous) => previous.filter((message) => message.id !== draftId));
278
+ if (draftId !== null)
279
+ updateMessages((previous) => previous.filter((message) => message.id !== draftId));
260
280
  }, [updateMessages]);
261
281
 
262
282
  /** Stop everything; the caller decides what state the room is left in. */
@@ -272,64 +292,79 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
272
292
  levels.reset();
273
293
  }, [endTurn, levels]);
274
294
 
275
- const fail = useCallback((code: VoiceErrorCode, message: string = VOICE_ERROR_MESSAGES[code]) => {
276
- teardown();
277
- discardDraft();
278
- if (!mountedRef.current) return;
279
- setCallFailure({ code, message });
280
- setRoomState('error');
281
- setAgentStateValue('idle');
282
- setCurrentSpeaker(null);
283
- }, [discardDraft, teardown]);
295
+ const fail = useCallback(
296
+ (code: VoiceErrorCode, message: string = VOICE_ERROR_MESSAGES[code]) => {
297
+ teardown();
298
+ discardDraft();
299
+ if (!mountedRef.current) return;
300
+ setCallFailure({ code, message });
301
+ setRoomState('error');
302
+ setAgentStateValue('idle');
303
+ setCurrentSpeaker(null);
304
+ },
305
+ [discardDraft, teardown],
306
+ );
284
307
 
285
308
  // ============== SPEAKING ==============
286
309
 
287
- const synthesize = useCallback((turn: ActiveTurn, text: string): Promise<string | null> => {
288
- const token = getToken();
289
- if (token === null) {
290
- turn.synthesisFailed = true;
291
- return Promise.resolve(null);
292
- }
293
- const { apiUrl: url, speechModel: model, voicePref: voice } = configRef.current;
294
- return requestSpeechClip({ apiUrl: url, token, model, input: text, voice, signal: turn.controller.signal })
295
- .catch((caught: unknown) => {
310
+ const synthesize = useCallback(
311
+ (turn: ActiveTurn, text: string): Promise<string | null> => {
312
+ const token = getToken();
313
+ if (token === null) {
314
+ turn.synthesisFailed = true;
315
+ return Promise.resolve(null);
316
+ }
317
+ const { apiUrl: url, speechModel: model, voicePref: voice } = configRef.current;
318
+ return requestSpeechClip({
319
+ apiUrl: url,
320
+ token,
321
+ model,
322
+ input: text,
323
+ voice,
324
+ signal: turn.controller.signal,
325
+ }).catch((caught: unknown) => {
296
326
  if (!turn.controller.signal.aborted) {
297
327
  console.error('[useVoiceRoom] Speech synthesis failed:', caught);
298
328
  turn.synthesisFailed = true;
299
329
  }
300
330
  return null;
301
331
  });
302
- }, [getToken]);
332
+ },
333
+ [getToken],
334
+ );
303
335
 
304
336
  /** Play the turn's clips in order as they arrive, fetching one ahead. */
305
- const playTurn = useCallback(async (turn: ActiveTurn): Promise<void> => {
306
- const signal = turn.controller.signal;
307
- for (let index = 0; ; index += 1) {
308
- while (index >= turn.clips.length && !turn.closed) {
309
- await new Promise<void>((resolve) => {
310
- turn.wake = resolve;
311
- });
312
- turn.wake = null;
313
- }
314
- if (signal.aborted || index >= turn.clips.length) return;
315
- const item = turn.clips[index];
316
- item.clip ??= synthesize(turn, item.text);
317
- const next = turn.clips[index + 1];
318
- if (next !== undefined) next.clip ??= synthesize(turn, next.text);
319
-
320
- const uri = await item.clip;
321
- if (signal.aborted) return;
322
- if (uri === null) continue;
323
- turn.speaking = `${turn.speaking} ${item.text}`;
324
- setAgent('speaking');
325
- try {
326
- await playSpeechClip(uri, { signal, onLevel: (level) => levels.setPlayback(level) });
327
- } catch (caught: unknown) {
328
- console.error('[useVoiceRoom] Playback failed:', caught);
329
- turn.synthesisFailed = true;
337
+ const playTurn = useCallback(
338
+ async (turn: ActiveTurn): Promise<void> => {
339
+ const signal = turn.controller.signal;
340
+ for (let index = 0; ; index += 1) {
341
+ while (index >= turn.clips.length && !turn.closed) {
342
+ await new Promise<void>((resolve) => {
343
+ turn.wake = resolve;
344
+ });
345
+ turn.wake = null;
346
+ }
347
+ if (signal.aborted || index >= turn.clips.length) return;
348
+ const item = turn.clips[index];
349
+ item.clip ??= synthesize(turn, item.text);
350
+ const next = turn.clips[index + 1];
351
+ if (next !== undefined) next.clip ??= synthesize(turn, next.text);
352
+
353
+ const uri = await item.clip;
354
+ if (signal.aborted) return;
355
+ if (uri === null) continue;
356
+ turn.speaking = `${turn.speaking} ${item.text}`;
357
+ setAgent('speaking');
358
+ try {
359
+ await playSpeechClip(uri, { signal, onLevel: (level) => levels.setPlayback(level) });
360
+ } catch (caught: unknown) {
361
+ console.error('[useVoiceRoom] Playback failed:', caught);
362
+ turn.synthesisFailed = true;
363
+ }
330
364
  }
331
- }
332
- }, [levels, setAgent, synthesize]);
365
+ },
366
+ [levels, setAgent, synthesize],
367
+ );
333
368
 
334
369
  const queueChunks = useCallback((turn: ActiveTurn, final: boolean) => {
335
370
  const pending = turn.text.slice(turn.consumed);
@@ -362,20 +397,23 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
362
397
  }, COMMIT_TIMEOUT_MS);
363
398
  }, []);
364
399
 
365
- const showDraft = useCallback((transcript: string) => {
366
- const draftId = draftIdRef.current;
367
- if (draftId !== null) {
368
- patchMessage(draftId, { content: transcript });
369
- return;
370
- }
371
- const id = nextId();
372
- draftIdRef.current = id;
373
- updateMessages((previous) => [
374
- ...previous,
375
- { id, role: 'user', content: transcript, timestamp: Date.now(), isStreaming: true },
376
- ]);
377
- if (mountedRef.current) setCurrentSpeaker('user');
378
- }, [patchMessage, updateMessages]);
400
+ const showDraft = useCallback(
401
+ (transcript: string) => {
402
+ const draftId = draftIdRef.current;
403
+ if (draftId !== null) {
404
+ patchMessage(draftId, { content: transcript });
405
+ return;
406
+ }
407
+ const id = nextId();
408
+ draftIdRef.current = id;
409
+ updateMessages((previous) => [
410
+ ...previous,
411
+ { id, role: 'user', content: transcript, timestamp: Date.now(), isStreaming: true },
412
+ ]);
413
+ if (mountedRef.current) setCurrentSpeaker('user');
414
+ },
415
+ [patchMessage, updateMessages],
416
+ );
379
417
 
380
418
  const startListening = useCallback(() => {
381
419
  if (phaseRef.current === 'off' || mutedRef.current || sessionRef.current !== null) return;
@@ -436,7 +474,11 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
436
474
  }
437
475
 
438
476
  // An utterance in progress when the engine ended is still an utterance.
439
- if ((committing || draftIdRef.current !== null) && heard !== '' && turnRef.current === null) {
477
+ if (
478
+ (committing || draftIdRef.current !== null) &&
479
+ heard !== '' &&
480
+ turnRef.current === null
481
+ ) {
440
482
  rapidEndsRef.current = 0;
441
483
  runTurnRef.current(heard);
442
484
  return;
@@ -460,99 +502,109 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
460
502
 
461
503
  // ============== THINKING ==============
462
504
 
463
- const runTurn = useCallback((text: string) => {
464
- // Settle the person's words as a message of their own.
465
- const draftId = draftIdRef.current;
466
- draftIdRef.current = null;
467
- if (draftId !== null) {
468
- patchMessage(draftId, { content: text, isStreaming: false });
469
- } else {
505
+ const runTurn = useCallback(
506
+ (text: string) => {
507
+ // Settle the person's words as a message of their own.
508
+ const draftId = draftIdRef.current;
509
+ draftIdRef.current = null;
510
+ if (draftId !== null) {
511
+ patchMessage(draftId, { content: text, isStreaming: false });
512
+ } else {
513
+ updateMessages((previous) => [
514
+ ...previous,
515
+ { id: nextId(), role: 'user', content: text, timestamp: Date.now(), isStreaming: false },
516
+ ]);
517
+ }
518
+
519
+ const history: VoiceTurnMessage[] = messagesRef.current
520
+ .filter((message) => message.content.trim() !== '' && message.id !== draftId)
521
+ .map((message) => ({ role: message.role, content: message.content }))
522
+ // The turn itself is `text`, not history.
523
+ .slice(0, draftId === null ? -1 : undefined);
524
+
525
+ const assistantId = nextId();
470
526
  updateMessages((previous) => [
471
527
  ...previous,
472
- { id: nextId(), role: 'user', content: text, timestamp: Date.now(), isStreaming: false },
528
+ {
529
+ id: assistantId,
530
+ role: 'assistant',
531
+ speaker: 'primary',
532
+ content: '',
533
+ timestamp: Date.now(),
534
+ isStreaming: true,
535
+ },
473
536
  ]);
474
- }
475
-
476
- const history: VoiceTurnMessage[] = messagesRef.current
477
- .filter((message) => message.content.trim() !== '' && message.id !== draftId)
478
- .map((message) => ({ role: message.role, content: message.content }))
479
- // The turn itself is `text`, not history.
480
- .slice(0, draftId === null ? -1 : undefined);
481
-
482
- const assistantId = nextId();
483
- updateMessages((previous) => [
484
- ...previous,
485
- { id: assistantId, role: 'assistant', speaker: 'primary', content: '', timestamp: Date.now(), isStreaming: true },
486
- ]);
487
-
488
- const turn: ActiveTurn = {
489
- controller: new AbortController(),
490
- assistantId,
491
- consumed: 0,
492
- chunkCount: 0,
493
- text: '',
494
- speaking: '',
495
- clips: [],
496
- closed: false,
497
- wake: null,
498
- synthesisFailed: false,
499
- };
500
- turnRef.current = turn;
501
- setAgent('thinking');
502
- if (mountedRef.current) setTurnErrorCode(null);
503
537
 
504
- // The microphone stays open through thinking and speaking, for barge-in.
505
- startListeningRef.current();
538
+ const turn: ActiveTurn = {
539
+ controller: new AbortController(),
540
+ assistantId,
541
+ consumed: 0,
542
+ chunkCount: 0,
543
+ text: '',
544
+ speaking: '',
545
+ clips: [],
546
+ closed: false,
547
+ wake: null,
548
+ synthesisFailed: false,
549
+ };
550
+ turnRef.current = turn;
551
+ setAgent('thinking');
552
+ if (mountedRef.current) setTurnErrorCode(null);
553
+
554
+ // The microphone stays open through thinking and speaking, for barge-in.
555
+ startListeningRef.current();
506
556
 
507
- const playing = playTurn(turn);
508
- const signal = turn.controller.signal;
509
-
510
- void (async () => {
511
- let lastText = '';
512
- try {
513
- await configRef.current.sendTurn({
514
- text,
515
- history,
516
- signal,
517
- onText: (answerSoFar) => {
518
- if (signal.aborted) return;
519
- lastText = answerSoFar;
520
- turn.text = stripTitleTagsPartial(answerSoFar, { trim: false });
521
- patchMessage(assistantId, { content: turn.text.trim() });
522
- queueChunks(turn, false);
523
- },
524
- });
525
- } catch (caught: unknown) {
557
+ const playing = playTurn(turn);
558
+ const signal = turn.controller.signal;
559
+
560
+ void (async () => {
561
+ let lastText = '';
562
+ try {
563
+ await configRef.current.sendTurn({
564
+ text,
565
+ history,
566
+ signal,
567
+ onText: (answerSoFar) => {
568
+ if (signal.aborted) return;
569
+ lastText = answerSoFar;
570
+ turn.text = stripTitleTagsPartial(answerSoFar, { trim: false });
571
+ patchMessage(assistantId, { content: turn.text.trim() });
572
+ queueChunks(turn, false);
573
+ },
574
+ });
575
+ } catch (caught: unknown) {
576
+ if (signal.aborted) return;
577
+ console.error('[useVoiceRoom] Turn failed:', caught);
578
+ fail('turn-failed', errorMessage(caught, VOICE_ERROR_MESSAGES['turn-failed']));
579
+ return;
580
+ }
526
581
  if (signal.aborted) return;
527
- console.error('[useVoiceRoom] Turn failed:', caught);
528
- fail('turn-failed', errorMessage(caught, VOICE_ERROR_MESSAGES['turn-failed']));
529
- return;
530
- }
531
- if (signal.aborted) return;
532
-
533
- turn.text = stripTitleTags(lastText, { trim: false });
534
- patchMessage(assistantId, { content: turn.text.trim(), isStreaming: false });
535
- queueChunks(turn, true);
536
- turn.closed = true;
537
- turn.wake?.();
538
- await playing;
539
- if (signal.aborted || turnRef.current !== turn) return;
540
582
 
541
- turnRef.current = null;
542
- levels.setPlayback(0);
543
- // The session that listened through the answer has heard the answer; a
544
- // fresh one starts the next utterance clean.
545
- sessionRef.current?.abort();
546
- if (turn.text.trim() === '') {
547
- updateMessages((previous) => previous.filter((message) => message.id !== assistantId));
548
- if (mountedRef.current) setTurnErrorCode('no-answer');
549
- } else if (turn.synthesisFailed) {
550
- if (mountedRef.current) setTurnErrorCode('not-played');
551
- }
552
- setAgent('listening');
553
- startListeningRef.current();
554
- })();
555
- }, [fail, levels, patchMessage, playTurn, queueChunks, setAgent, updateMessages]);
583
+ turn.text = stripTitleTags(lastText, { trim: false });
584
+ patchMessage(assistantId, { content: turn.text.trim(), isStreaming: false });
585
+ queueChunks(turn, true);
586
+ turn.closed = true;
587
+ turn.wake?.();
588
+ await playing;
589
+ if (signal.aborted || turnRef.current !== turn) return;
590
+
591
+ turnRef.current = null;
592
+ levels.setPlayback(0);
593
+ // The session that listened through the answer has heard the answer; a
594
+ // fresh one starts the next utterance clean.
595
+ sessionRef.current?.abort();
596
+ if (turn.text.trim() === '') {
597
+ updateMessages((previous) => previous.filter((message) => message.id !== assistantId));
598
+ if (mountedRef.current) setTurnErrorCode('no-answer');
599
+ } else if (turn.synthesisFailed) {
600
+ if (mountedRef.current) setTurnErrorCode('not-played');
601
+ }
602
+ setAgent('listening');
603
+ startListeningRef.current();
604
+ })();
605
+ },
606
+ [fail, levels, patchMessage, playTurn, queueChunks, setAgent, updateMessages],
607
+ );
556
608
  runTurnRef.current = runTurn;
557
609
 
558
610
  // ============== CONNECT ==============
@@ -564,12 +616,18 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
564
616
  setRoomState('connecting');
565
617
 
566
618
  if (!isSpeechRecognitionAvailable()) {
567
- setCallFailure({ code: 'voice-unavailable', message: VOICE_ERROR_MESSAGES['voice-unavailable'] });
619
+ setCallFailure({
620
+ code: 'voice-unavailable',
621
+ message: VOICE_ERROR_MESSAGES['voice-unavailable'],
622
+ });
568
623
  setRoomState('error');
569
624
  return;
570
625
  }
571
626
  if (getToken() === null) {
572
- setCallFailure({ code: 'not-authenticated', message: VOICE_ERROR_MESSAGES['not-authenticated'] });
627
+ setCallFailure({
628
+ code: 'not-authenticated',
629
+ message: VOICE_ERROR_MESSAGES['not-authenticated'],
630
+ });
573
631
  setRoomState('error');
574
632
  return;
575
633
  }
package/src/index.ts CHANGED
@@ -9,14 +9,18 @@ export { AliaChatScreen } from './components/AliaChatScreen';
9
9
  export type { AliaChatScreenProps } from './components/AliaChatScreen';
10
10
 
11
11
  // ── Chat UI components ──
12
+ export { DEFAULT_LABELS } from './lib/labels';
13
+ export type { AliaChatLabels } from './lib/labels';
14
+ export { VoiceModeIcon } from './components/VoiceModeIcon';
15
+ export { AmbientField, PARALLAX_DURATION, PARALLAX_EASE } from './components/AmbientField';
16
+ export type { AmbientFieldProps } from './components/AmbientField';
17
+ export { AliaAvatar } from './components/AliaAvatar';
18
+ export type { AliaAvatarProps, AliaAvatarState } from './components/AliaAvatar';
12
19
  export { IdentityMark } from './components/IdentityMark';
13
20
  export type { IdentityMarkProps, IdentityMarkState } from './components/IdentityMark';
14
- export { ThinkingIndicator } from './components/ThinkingIndicator';
15
21
  export { AliaMarkdown } from './components/Markdown';
16
22
  export type { AliaMarkdownProps, RenderCodeBlock } from './components/Markdown';
17
- export { Reasoning, ReasoningTrigger, ReasoningContent } from './components/Reasoning';
18
23
  export { AliaWelcomeMessage } from './components/AliaWelcomeMessage';
19
- export { ResearchProgressCard } from './components/ResearchProgressCard';
20
24
  export { PlanPreviewCard } from './components/PlanPreviewCard';
21
25
 
22
26
  // ── Chat hook ──
@@ -64,13 +68,21 @@ export type {
64
68
  } from './types';
65
69
 
66
70
  // ── UI Primitives (NativeWind) ──
67
- export { Button, buttonVariants, buttonTextVariants } from './components/ui/button';
68
- export type { ButtonProps } from './components/ui/button';
69
- export { Text, TextClassContext } from './components/ui/text';
70
71
  export { cn, formatFileSize } from './lib/utils';
71
72
  export { getToolLabel, getToolActiveLabel, getResearchActiveLabel } from './lib/tool-registry';
72
73
  export { getTextFromContent, getImagesFromContent } from './lib/content-utils';
73
74
 
75
+ // ── The turn's work log: tool calls as Bloom's `WebSearch` and `TaskList` ──
76
+ export { isWebInvocation, taskListLog, webSearchLog } from './lib/work-log';
77
+ export type { LiveResearch, TaskListLog, WebSearchLog, WorkLogOptions } from './lib/work-log';
78
+ export { englishWorkLog } from './lib/work-log-copy';
79
+ export type { Translate } from './lib/work-log-copy';
80
+ export { getToolDoneLabel, getToolPillLabel } from './lib/task-labels';
81
+ export { extractCitationSources } from './lib/citations';
82
+ export type { CitationSource } from './lib/citations';
83
+ export { toolCallStatus } from './lib/tool-call-status';
84
+ export type { ToolCallStatus } from './lib/tool-call-status';
85
+
74
86
  // ── Picker hooks ──
75
87
  export { useImagePicker } from './hooks/useImagePicker';
76
88
  export type { ImagePickerAsset } from './hooks/useImagePicker';
@@ -70,7 +70,9 @@ function asCount(value: unknown): number | null {
70
70
  }
71
71
 
72
72
  function asTexts(value: unknown): string[] {
73
- return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
73
+ return Array.isArray(value)
74
+ ? value.filter((item): item is string => typeof item === 'string')
75
+ : [];
74
76
  }
75
77
 
76
78
  function parseEntry(value: unknown): CatalogueEntry | null {
@@ -100,7 +102,8 @@ function parseEntry(value: unknown): CatalogueEntry | null {
100
102
  reasoningEfforts: REASONING_EFFORTS.filter((effort) =>
101
103
  asTexts(raw.reasoningEfforts).includes(effort),
102
104
  ),
103
- pricing: input !== null && output !== null ? { inputPerMTok: input, outputPerMTok: output } : null,
105
+ pricing:
106
+ input !== null && output !== null ? { inputPerMTok: input, outputPerMTok: output } : null,
104
107
  releasedAt: asText(raw.releasedAt),
105
108
  featured: raw.featured === true,
106
109
  };
@@ -159,7 +162,12 @@ export function resolveSelection(
159
162
  catalogue?.entries.find((entry) => entry.id === catalogue.defaultModelId) ?? null;
160
163
 
161
164
  if (requested === undefined) {
162
- return { requestedId: undefined, effectiveId: undefined, entry: defaultEntry(), source: 'default' };
165
+ return {
166
+ requestedId: undefined,
167
+ effectiveId: undefined,
168
+ entry: defaultEntry(),
169
+ source: 'default',
170
+ };
163
171
  }
164
172
  if (catalogue === undefined) {
165
173
  return { requestedId: requested, effectiveId: requested, entry: null, source: 'requested' };
@@ -168,7 +176,12 @@ export function resolveSelection(
168
176
  if (entry !== undefined) {
169
177
  return { requestedId: requested, effectiveId: requested, entry, source: 'requested' };
170
178
  }
171
- return { requestedId: requested, effectiveId: undefined, entry: defaultEntry(), source: 'replaced' };
179
+ return {
180
+ requestedId: requested,
181
+ effectiveId: undefined,
182
+ entry: defaultEntry(),
183
+ source: 'replaced',
184
+ };
172
185
  }
173
186
 
174
187
  /**
@@ -86,10 +86,14 @@ export function readAliaChatFailure(body: unknown): AliaChatFailure {
86
86
  return {
87
87
  ...(typeof detail.code === 'string' && detail.code !== '' ? { code: detail.code } : {}),
88
88
  ...(typeof detail.retryable === 'boolean' ? { retryable: detail.retryable } : {}),
89
- ...(typeof detail.retryAfter === 'number' && Number.isFinite(detail.retryAfter) && detail.retryAfter >= 0
89
+ ...(typeof detail.retryAfter === 'number' &&
90
+ Number.isFinite(detail.retryAfter) &&
91
+ detail.retryAfter >= 0
90
92
  ? { retryAfter: detail.retryAfter }
91
93
  : {}),
92
- ...(typeof detail.reference === 'string' && detail.reference !== '' ? { reference: detail.reference } : {}),
94
+ ...(typeof detail.reference === 'string' && detail.reference !== ''
95
+ ? { reference: detail.reference }
96
+ : {}),
93
97
  };
94
98
  }
95
99
 
@@ -159,7 +163,11 @@ function parsePlanSteps(value: unknown): PlanStep[] {
159
163
 
160
164
  return value.map((entry, index) => {
161
165
  const step = asObject(entry, `alia.plan_preview steps[${index}]`);
162
- assertOnlyKeys(step, ['action', 'description', 'toolName'], `alia.plan_preview steps[${index}]`);
166
+ assertOnlyKeys(
167
+ step,
168
+ ['action', 'description', 'toolName'],
169
+ `alia.plan_preview steps[${index}]`,
170
+ );
163
171
  return {
164
172
  action: asString(step.action, `alia.plan_preview steps[${index}].action`),
165
173
  description: asString(step.description, `alia.plan_preview steps[${index}].description`),
@@ -276,7 +284,8 @@ function validateIgnoredNamedEvent(eventName: string, payload: JsonObject): void
276
284
  const scopes = new Set(['same_model_revision', 'same_model', 'cross_model']);
277
285
  const reason = asString(payload.reason, `${eventName} reason`);
278
286
  const scope = asString(payload.scope, `${eventName} scope`);
279
- if (!reasons.has(reason) || !scopes.has(scope)) fail(`${eventName} has an unsupported route switch.`);
287
+ if (!reasons.has(reason) || !scopes.has(scope))
288
+ fail(`${eventName} has an unsupported route switch.`);
280
289
  asString(payload.occurredAt, `${eventName} occurredAt`);
281
290
  return;
282
291
  }
@@ -378,7 +387,10 @@ function parseOpenAIFrame(payload: unknown): ParsedOpenAIFrame {
378
387
  asString(error.message, 'OpenAI stream error message');
379
388
  // The server's prose is not carried: the code and retryability are what a
380
389
  // caller acts on, and the caller words it in its own language.
381
- throw new AliaChatStreamError('Alia ended the stream with an error.', readAliaChatFailure(body));
390
+ throw new AliaChatStreamError(
391
+ 'Alia ended the stream with an error.',
392
+ readAliaChatFailure(body),
393
+ );
382
394
  }
383
395
  assertOnlyKeys(
384
396
  body,
@@ -430,10 +442,16 @@ function parseOpenAIFrame(payload: unknown): ParsedOpenAIFrame {
430
442
  fail(`choices[${index}].delta.role must be assistant.`);
431
443
  }
432
444
  if (delta.content !== undefined) {
433
- events.push({ kind: 'content', content: asString(delta.content, `choices[${index}].delta.content`, true) });
445
+ events.push({
446
+ kind: 'content',
447
+ content: asString(delta.content, `choices[${index}].delta.content`, true),
448
+ });
434
449
  }
435
450
  if (delta.reasoning !== undefined) {
436
- events.push({ kind: 'reasoning', content: asString(delta.reasoning, `choices[${index}].delta.reasoning`) });
451
+ events.push({
452
+ kind: 'reasoning',
453
+ content: asString(delta.reasoning, `choices[${index}].delta.reasoning`),
454
+ });
437
455
  }
438
456
  if (delta.tool_calls !== undefined) events.push(...parseToolCalls(delta.tool_calls));
439
457