@alia.onl/sdk 3.1.2 → 3.1.4

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.
@@ -12,11 +12,13 @@ import {
12
12
  RoomEvent,
13
13
  Track,
14
14
  DisconnectReason,
15
+ type RemoteTrack,
15
16
  type RemoteTrackPublication,
16
17
  type RemoteParticipant,
17
18
  type DataPublishOptions,
18
19
  } from 'livekit-client';
19
20
  import { useOxy } from '@oxyhq/services';
21
+ import { errorMessage } from '../lib/utils';
20
22
  import type { RoomState, AgentState, VoiceMessage, VoiceToolInvocation } from '../types';
21
23
 
22
24
  const API_URL = process.env.EXPO_PUBLIC_ALIA_API_URL ?? 'https://api.alia.onl';
@@ -63,7 +65,7 @@ interface CohostEnabledMsg { type: 'cohost.enabled' }
63
65
  interface CohostDisabledMsg { type: 'cohost.disabled' }
64
66
  interface CohostTurnMsg { type: 'cohost.turn_changed'; speaker: 'primary' | 'cohost' | 'user' }
65
67
  interface CohostRoundMsg { type: 'cohost.round_complete'; turns: number }
66
- interface ToolCallMsg { type: 'tool.call'; toolName: string; callId: string; args?: any; speaker: 'primary' | 'cohost' }
68
+ interface ToolCallMsg { type: 'tool.call'; toolName: string; callId: string; args?: Record<string, unknown>; speaker: 'primary' | 'cohost' }
67
69
  interface ToolResultMsg { type: 'tool.result'; callId: string; speaker: 'primary' | 'cohost' }
68
70
  interface SessionEndedMsg { type: 'session.ended'; reason: string }
69
71
  interface ErrorMsg { type: 'error'; code: string; message: string }
@@ -73,6 +75,31 @@ type AgentDataMessage =
73
75
  | CohostEnabledMsg | CohostDisabledMsg | CohostTurnMsg | CohostRoundMsg
74
76
  | ToolCallMsg | ToolResultMsg | SessionEndedMsg | ErrorMsg;
75
77
 
78
+ // ============== MIC DEVICE ERRORS ==============
79
+
80
+ /**
81
+ * Map a microphone-acquisition failure to actionable copy. When
82
+ * `setMicrophoneEnabled(true)` calls getUserMedia and the device is missing,
83
+ * blocked, or busy, it surfaces a DOMException whose `.name` identifies the
84
+ * cause (legacy aliases included). Returns null for anything that isn't a known
85
+ * device error so the caller can fall back to generic handling.
86
+ */
87
+ function micDeviceErrorMessage(name: string): string | null {
88
+ switch (name) {
89
+ case 'NotFoundError':
90
+ case 'DevicesNotFoundError':
91
+ return 'No microphone found — connect one and try again';
92
+ case 'NotAllowedError':
93
+ case 'PermissionDeniedError':
94
+ return 'Microphone access denied — allow it in your browser';
95
+ case 'NotReadableError':
96
+ case 'TrackStartError':
97
+ return 'Microphone is in use by another app';
98
+ default:
99
+ return null;
100
+ }
101
+ }
102
+
76
103
  // ============== HOOK ==============
77
104
 
78
105
  export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
@@ -324,13 +351,13 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
324
351
 
325
352
  // Event: remote audio tracks (agent speaking)
326
353
  // On web, must attach to a DOM element to play. On native, WebRTC plays audio automatically.
327
- room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
354
+ room.on(RoomEvent.TrackSubscribed, (track: RemoteTrack) => {
328
355
  if (track.kind === Track.Kind.Audio && hasDOM) {
329
356
  track.attach();
330
357
  }
331
358
  });
332
359
 
333
- room.on(RoomEvent.TrackUnsubscribed, (track) => {
360
+ room.on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {
334
361
  if (hasDOM && track.kind === Track.Kind.Audio) {
335
362
  track.detach();
336
363
  }
@@ -351,15 +378,33 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
351
378
  // Connect to the room
352
379
  await room.connect(url, livekitToken, { autoSubscribe: true });
353
380
 
354
- // Enable microphone — audio goes to LiveKit, agent picks it up server-side
355
- await room.localParticipant.setMicrophoneEnabled(true);
381
+ // Enable microphone — audio goes to LiveKit, agent picks it up server-side.
382
+ // getUserMedia device failures (no mic, permission blocked, hardware busy)
383
+ // surface here; map them to actionable copy instead of a generic failure.
384
+ try {
385
+ await room.localParticipant.setMicrophoneEnabled(true);
386
+ } catch (micError: unknown) {
387
+ // DOMException is not `instanceof Error`, so read `.name` off the object.
388
+ const name =
389
+ typeof micError === 'object' && micError !== null && 'name' in micError &&
390
+ typeof micError.name === 'string'
391
+ ? micError.name
392
+ : '';
393
+ const friendly = micDeviceErrorMessage(name);
394
+ if (!friendly) throw micError; // unknown failure — let the generic handler report it
395
+ console.error(`[useVoiceRoom] Microphone error (${name || 'unknown'}):`, micError);
396
+ setError(friendly);
397
+ setRoomState('error');
398
+ cleanup();
399
+ return;
400
+ }
356
401
 
357
402
  setRoomState('connected');
358
403
  setAgentState('listening');
359
404
 
360
- } catch (e: any) {
405
+ } catch (e: unknown) {
361
406
  console.error('[useVoiceRoom] Connection error:', e);
362
- setError(e.message || 'Failed to connect');
407
+ setError(errorMessage(e, 'Failed to connect'));
363
408
  setRoomState('error');
364
409
  cleanup();
365
410
  }
package/src/index.ts CHANGED
@@ -9,8 +9,8 @@ export { AliaChatScreen } from './components/AliaChatScreen';
9
9
  export type { AliaChatScreenProps } from './components/AliaChatScreen';
10
10
 
11
11
  // ── Chat UI components ──
12
- export { AliaFace } from './components/AliaFace';
13
- export type { AliaAccessory, AliaExpression, AliaFaceProps } from './components/AliaFace';
12
+ export { AliaMark } from './components/AliaMark';
13
+ export type { AliaMarkProps, AliaMarkState } from './components/AliaMark';
14
14
  export { ThinkingIndicator } from './components/ThinkingIndicator';
15
15
  export { AliaMarkdown } from './components/Markdown';
16
16
  export type { AliaMarkdownProps } from './components/Markdown';
@@ -36,6 +36,8 @@ export { useTTS } from './hooks/useTTS';
36
36
  export type { UseTTSOptions } from './hooks/useTTS';
37
37
  export { useSpeechToText, useSTTStore } from './hooks/useSpeechToText';
38
38
  export type { UseSTTOptions } from './hooks/useSpeechToText';
39
+ export { useAmbientWave } from './hooks/useAmbientWave';
40
+ export type { UseAmbientWaveOptions, UseAmbientWaveResult, AmbientWaveVoice, AmbientWaveMode } from './hooks/useAmbientWave';
39
41
  export { useSoundEffects, useVoiceSoundEffects } from './hooks/useSoundEffects';
40
42
  export type { SoundName, SoundSources } from './hooks/useSoundEffects';
41
43
 
@@ -1,12 +1,22 @@
1
+ /** A single part of a multi-part chat message content array. */
2
+ export interface ContentPart {
3
+ type: string;
4
+ text?: string;
5
+ image_url?: { url?: string };
6
+ [key: string]: unknown;
7
+ }
8
+
9
+ export type MessageContent = string | ContentPart[];
10
+
1
11
  /**
2
12
  * Extract text from message content, whether it's a string or multi-part array.
3
13
  */
4
- export function getTextFromContent(content: string | Array<{ type: string; [key: string]: any }> | any): string {
14
+ export function getTextFromContent(content: MessageContent | null | undefined): string {
5
15
  if (typeof content === 'string') return content;
6
16
  if (Array.isArray(content)) {
7
17
  return content
8
- .filter((p: any) => p.type === 'text')
9
- .map((p: any) => p.text || '')
18
+ .filter((p) => p.type === 'text')
19
+ .map((p) => p.text || '')
10
20
  .join('');
11
21
  }
12
22
  return String(content || '');
@@ -15,9 +25,11 @@ export function getTextFromContent(content: string | Array<{ type: string; [key:
15
25
  /**
16
26
  * Extract image URLs from multi-part message content.
17
27
  */
18
- export function getImagesFromContent(content: string | Array<{ type: string; [key: string]: any }> | any): string[] {
28
+ export function getImagesFromContent(content: MessageContent | null | undefined): string[] {
19
29
  if (!Array.isArray(content)) return [];
20
30
  return content
21
- .filter((p: any) => p.type === 'image_url' && p.image_url?.url)
22
- .map((p: any) => p.image_url.url);
31
+ .filter((p): p is ContentPart & { image_url: { url: string } } =>
32
+ p.type === 'image_url' && typeof p.image_url?.url === 'string',
33
+ )
34
+ .map((p) => p.image_url.url);
23
35
  }
package/src/lib/utils.ts CHANGED
@@ -5,6 +5,17 @@ export function cn(...inputs: ClassValue[]) {
5
5
  return twMerge(clsx(inputs));
6
6
  }
7
7
 
8
+ /** Extract a human-readable message from an unknown caught error. */
9
+ export function errorMessage(err: unknown, fallback = 'Something went wrong'): string {
10
+ if (err instanceof Error) return err.message;
11
+ if (typeof err === 'object' && err !== null) {
12
+ const maybe = err as { message?: string };
13
+ if (maybe.message) return maybe.message;
14
+ }
15
+ if (typeof err === 'string') return err;
16
+ return fallback;
17
+ }
18
+
8
19
  export function formatFileSize(bytes: number): string {
9
20
  if (bytes === 0) return '0 B';
10
21
  const k = 1024;
@@ -10,22 +10,28 @@
10
10
  // have their own real types — ambient declarations here would shadow them.
11
11
 
12
12
  declare module 'livekit-client' {
13
+ export interface RemoteTrack {
14
+ kind?: string;
15
+ mediaStreamTrack?: MediaStreamTrack;
16
+ attach(): HTMLMediaElement;
17
+ detach(): HTMLMediaElement[];
18
+ }
13
19
  export class Room {
14
- on(event: any, listener: (...args: any[]) => void): this;
15
- off(event: any, listener: (...args: any[]) => void): this;
20
+ on<Args extends unknown[]>(event: string, listener: (...args: Args) => void): this;
21
+ off<Args extends unknown[]>(event: string, listener: (...args: Args) => void): this;
16
22
  localParticipant: LocalParticipant;
17
23
  remoteParticipants: Map<string, RemoteParticipant>;
18
- connect(url: string, token: string, options?: any): Promise<void>;
24
+ connect(url: string, token: string, options?: Record<string, unknown>): Promise<void>;
19
25
  disconnect(): Promise<void>;
20
26
  state: string;
21
27
  }
22
28
  export class LocalParticipant {
23
29
  publishData(data: Uint8Array, options?: DataPublishOptions): Promise<void>;
24
- getTrackPublications(): Map<string, any>;
25
- getTrackPublication(source: string): any;
30
+ getTrackPublications(): Map<string, RemoteTrackPublication>;
31
+ getTrackPublication(source: string): RemoteTrackPublication | undefined;
26
32
  setMicrophoneEnabled(enabled: boolean): Promise<void>;
27
- audioTrackPublications: Map<string, any>;
28
- trackPublications: Map<string, any>;
33
+ audioTrackPublications: Map<string, RemoteTrackPublication>;
34
+ trackPublications: Map<string, RemoteTrackPublication>;
29
35
  }
30
36
  export class RemoteParticipant {
31
37
  getTrackPublications(): Map<string, RemoteTrackPublication>;
@@ -34,7 +40,7 @@ declare module 'livekit-client' {
34
40
  identity: string;
35
41
  }
36
42
  export class RemoteTrackPublication {
37
- track: any;
43
+ track: RemoteTrack | null;
38
44
  source: string;
39
45
  kind: string;
40
46
  isSubscribed: boolean;
@@ -59,15 +65,54 @@ declare module 'livekit-client' {
59
65
  }
60
66
  }
61
67
 
68
+ declare module 'expo-haptics' {
69
+ export enum ImpactFeedbackStyle {
70
+ Light = 'light',
71
+ Medium = 'medium',
72
+ Heavy = 'heavy',
73
+ Soft = 'soft',
74
+ Rigid = 'rigid',
75
+ }
76
+ export function impactAsync(style?: ImpactFeedbackStyle): Promise<void>;
77
+ }
78
+
62
79
  declare module 'expo-audio' {
63
- export function createAudioPlayer(source: any): any;
64
- export function useAudioRecorder(preset: any): any;
65
- export function useAudioRecorderState(recorder: any, interval: number): any;
80
+ export interface AudioPlaybackStatus {
81
+ didJustFinish?: boolean;
82
+ isLoaded?: boolean;
83
+ playing?: boolean;
84
+ }
85
+ export interface AudioPlayer {
86
+ addListener(event: 'playbackStatusUpdate', listener: (status: AudioPlaybackStatus) => void): { remove(): void };
87
+ play(): void;
88
+ pause(): void;
89
+ seekTo(seconds: number): void;
90
+ remove(): void;
91
+ release(): void;
92
+ }
93
+ /** A URI object, a bundled asset module id (number), or a required asset. */
94
+ export type AudioSource = { uri: string } | number;
95
+ export interface RecordingPreset {
96
+ isMeteringEnabled?: boolean;
97
+ [key: string]: unknown;
98
+ }
99
+ export interface AudioMode {
100
+ [key: string]: unknown;
101
+ }
102
+ export interface AudioRecorderState {
103
+ metering?: number;
104
+ isRecording?: boolean;
105
+ durationMillis?: number;
106
+ }
107
+ export function createAudioPlayer(source: AudioSource): AudioPlayer;
108
+ export function useAudioRecorder(preset: RecordingPreset): AudioRecorder;
109
+ export function useAudioRecorderState(recorder: AudioRecorder, interval: number): AudioRecorderState;
66
110
  export function requestRecordingPermissionsAsync(): Promise<{ granted: boolean }>;
67
- export function setAudioModeAsync(mode: any): Promise<void>;
68
- export const RecordingPresets: { HIGH_QUALITY: any };
111
+ export function getRecordingPermissionsAsync(): Promise<{ granted: boolean }>;
112
+ export function setAudioModeAsync(mode: AudioMode): Promise<void>;
113
+ export const RecordingPresets: { HIGH_QUALITY: RecordingPreset };
69
114
  export class AudioRecorder {
70
- constructor(preset: any);
115
+ constructor(preset: RecordingPreset);
71
116
  prepareToRecordAsync(): Promise<void>;
72
117
  record(): void;
73
118
  stop(): Promise<void>;
@@ -81,6 +126,22 @@ declare module 'expo-clipboard' {
81
126
  }
82
127
 
83
128
  declare module 'expo-image-picker' {
84
- export function launchImageLibraryAsync(options?: any): Promise<any>;
129
+ export interface ImagePickerAsset {
130
+ uri: string;
131
+ fileName?: string | null;
132
+ fileSize?: number;
133
+ mimeType?: string;
134
+ }
135
+ export interface ImagePickerOptions {
136
+ allowsMultipleSelection?: boolean;
137
+ mediaTypes?: string;
138
+ aspect?: [number, number];
139
+ quality?: number;
140
+ }
141
+ export interface ImagePickerResult {
142
+ canceled: boolean;
143
+ assets: ImagePickerAsset[];
144
+ }
145
+ export function launchImageLibraryAsync(options?: ImagePickerOptions): Promise<ImagePickerResult>;
85
146
  export const MediaTypeOptions: { Images: string; Videos: string; All: string };
86
147
  }
package/src/types.ts CHANGED
@@ -42,7 +42,7 @@ export interface PendingPlan {
42
42
  export interface ChatMessage {
43
43
  id: string;
44
44
  role: 'user' | 'assistant' | 'system';
45
- content: string | Array<{ type: string; [key: string]: any }>;
45
+ content: string | Array<{ type: string; [key: string]: unknown }>;
46
46
  toolInvocations?: ToolInvocation[];
47
47
  createdAt: number;
48
48
  /** Extended thinking / reasoning content */
@@ -66,8 +66,8 @@ export interface ChatMessage {
66
66
  export interface ToolInvocation {
67
67
  toolName: string;
68
68
  state: 'call' | 'partial-call' | 'result';
69
- args?: Record<string, any>;
70
- result?: any;
69
+ args?: Record<string, unknown>;
70
+ result?: unknown;
71
71
  /** OpenAI-format tool call ID for matching calls to results */
72
72
  toolCallId?: string;
73
73
  }
@@ -81,7 +81,7 @@ export interface VoiceToolInvocation {
81
81
  toolCallId: string;
82
82
  toolName: string;
83
83
  state: 'call' | 'result';
84
- args?: any;
84
+ args?: Record<string, unknown>;
85
85
  }
86
86
 
87
87
  export interface VoiceMessage {