@alia.onl/sdk 22.7.0 → 22.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @alia.onl/sdk
2
2
 
3
+ ## 22.9.0
4
+
5
+ - Native avatars can use the optional shared GPU companion `@oxy.so/bloom-avatar-native@^0.1.0`. Their default asset URL pins the installed companion manifest revision; Alia retains previous releases independently of web deployments. Hosts without the companion retain the WebView fallback, and web imports no native GPU graph.
6
+ - Avatar icons nested inside a chat preserve its explicit `characterRuntimeUrl` override.
7
+ - Regenerated Alia's bundled still with the smooth avatar runtime; idle icons retain the same character and dimensions without fetching the 3D runtime.
8
+
9
+ ## 22.8.0
10
+
11
+ - **Streaming dictation (ADR 0024).** `@alia.onl/sdk/dictation` gains
12
+ `openDictationStream({ apiUrl, getToken | token, language?, onEvent, signal?, openSocket?,
13
+ endTimeoutMs? })`: the caller's PCM16 LE mono 24 kHz frames go to
14
+ `GET /v1/audio/transcriptions/stream`, and Alia's events come back while the person speaks:
15
+ `dictation.ready`, `transcript.interim` / `transcript.delta` / `transcript.final` per numbered
16
+ `segment`, `error { code, retryable }` and one `dictation.closed { reason }`. A segment's
17
+ `transcript.final` is the only text to keep. It returns `{ append, commit, end, close, closed }`;
18
+ `end()` resolves once the finals have arrived. The protocol module (`DICTATION_PROTOCOL_VERSION`
19
+ 1, `parseDictationServerEvent`, `dictationStreamUrl`) is exported beside it. It reaches no React
20
+ or native module (`check:entries`). `useDictation` still records a clip for batch
21
+ transcription; adopting the stream there is a follow-up.
22
+ - The API that serves the stream ships with this release; against an older API the socket's
23
+ path answers 404 and the stream reports `TRANSCRIPTION_FAILED`, so batch
24
+ `requestTranscription` stays the fallback.
25
+
3
26
  ## 22.7.0
4
27
 
5
28
  - **Image results for awaited host tools.** An awaited tool's `execute` may answer
package/README.md CHANGED
@@ -33,12 +33,17 @@ content, safe areas and keyboard clearance. Hosts should not add window-height c
33
33
  scroll container to make an embedded chat visible.
34
34
 
35
35
  Alia's avatar is Bloom's 3D character (`AgentAvatar`). It draws through a runtime of
36
- static files (about 214 MB), which the chat loads from Alia's own origin
36
+ static files (loaded on demand), which the chat loads from Alia's own origin
37
37
  (`ALIA_CHARACTER_RUNTIME_URL`, served with CORS open), so the app hosts nothing and mounts
38
38
  no avatar provider. To serve your own copy instead, serve the Bloom
39
39
  package's `assets/character-runtime/` directory unchanged and pass the URL of `runtime.mjs`
40
- as `characterRuntimeUrl` to `AliaChatSheet` or `AliaChatScreen`; native avatars run in a
41
- WebView, so there the URL must be absolute and reachable from the device.
40
+ as `characterRuntimeUrl` to `AliaChatSheet` or `AliaChatScreen`; nested avatar icons
41
+ preserve that override. Native URLs must be absolute and reachable from the device.
42
+ For native shared GPU rendering, install the optional `@oxy.so/bloom-avatar-native@^0.1.0`
43
+ companion with its Expo GL peer and rebuild the native app. The SDK then pins its default
44
+ URL to the companion's full runtime manifest revision under `/bloom-character/releases/`.
45
+ Alia retains those assets across deployments. Hosts without the companion keep the
46
+ WebView fallback; web does not import the native companion graph.
42
47
 
43
48
  A web app with a Content-Security-Policy composes the SDK's into its own instead of listing
44
49
  Alia's origins by hand: `@alia.onl/sdk/csp` exports `ALIA_SDK_CSP` and `withAliaSdkCsp(extensions)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alia.onl/sdk",
3
- "version": "22.7.0",
3
+ "version": "22.9.0",
4
4
  "description": "Alia AI chat SDK — the conversation runtime, chat UI, voice calls and dictation for Oxy apps",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",
@@ -76,7 +76,8 @@
76
76
  "expo-file-system": "^57.0.0 || ^58.0.0",
77
77
  "expo-web-browser": "^57.0.0 || ^58.0.0",
78
78
  "react-native-css": "^3.0.7",
79
- "react-native-audio-api": "^0.13.6"
79
+ "react-native-audio-api": "^0.13.6",
80
+ "@oxy.so/bloom-avatar-native": "^0.1.0"
80
81
  },
81
82
  "peerDependenciesMeta": {
82
83
  "nativewind": {
@@ -84,6 +85,9 @@
84
85
  },
85
86
  "expo-clipboard": {
86
87
  "optional": true
88
+ },
89
+ "@oxy.so/bloom-avatar-native": {
90
+ "optional": true
87
91
  }
88
92
  },
89
93
  "devDependencies": {
@@ -1,10 +1,9 @@
1
- import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';
1
+ import { AliaAvatarProvider } from './AliaAvatarProvider';
2
2
  import { Avatar } from '@oxy.so/bloom/avatar';
3
3
  import type { BloomIconComponent } from '@oxy.so/bloom/icons';
4
4
  import { useAliaActivity } from '../hooks/useAliaActivity';
5
5
  import { ALIA_AVATAR } from '../lib/alia-character';
6
6
  import { ALIA_AVATAR_STILL } from '../lib/alia-avatar-still.generated';
7
- import { ALIA_CHARACTER_RUNTIME_URL } from '../lib/character-runtime';
8
7
  import { aliaIsBusy } from './AliaAvatar';
9
8
 
10
9
  const DEFAULT_SIZE = 20;
@@ -24,15 +23,15 @@ const STILL = { uri: ALIA_AVATAR_STILL };
24
23
  * (`characterIsLive`), and keeps her a still under reduced motion. The still stays under her
25
24
  * until the first live frame.
26
25
  *
27
- * It mounts its own `AgentAvatarProvider` on `ALIA_CHARACTER_RUNTIME_URL`: a live avatar outside
28
- * the chat's provider would ask the host for `/bloom-character/runtime.mjs` and get its HTML. The
26
+ * It inherits the chat's explicit runtime URL; outside a chat it uses the SDK default
27
+ * (the companion's immutable revision on native). The
29
28
  * colour (`fill`) is ignored — the likeness is painted — and it is never interactive, so the
30
29
  * button keeps the press.
31
30
  */
32
31
  export const AliaAvatarIcon: BloomIconComponent = ({ width, height }) => {
33
32
  const state = useAliaActivity();
34
33
  return (
35
- <AgentAvatarProvider runtimeUrl={ALIA_CHARACTER_RUNTIME_URL}>
34
+ <AliaAvatarProvider>
36
35
  <Avatar
37
36
  character={ALIA_AVATAR}
38
37
  characterStill={STILL}
@@ -40,6 +39,6 @@ export const AliaAvatarIcon: BloomIconComponent = ({ width, height }) => {
40
39
  activity={aliaIsBusy(state) ? 'working' : 'idle'}
41
40
  alt="Alia"
42
41
  />
43
- </AgentAvatarProvider>
42
+ </AliaAvatarProvider>
44
43
  );
45
44
  };
@@ -0,0 +1,21 @@
1
+ import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';
2
+ import { createContext, type ReactNode, useContext } from 'react';
3
+ import { ALIA_CHARACTER_RUNTIME_URL } from '../lib/character-runtime';
4
+
5
+ const RuntimeUrl = createContext<string | undefined>(undefined);
6
+ /** Icons inside a chat inherit its explicit host URL instead of replacing it. */
7
+ export function AliaAvatarProvider({
8
+ runtimeUrl,
9
+ children,
10
+ }: {
11
+ runtimeUrl?: string;
12
+ children: ReactNode;
13
+ }) {
14
+ const inherited = useContext(RuntimeUrl);
15
+ const resolved = runtimeUrl ?? inherited ?? ALIA_CHARACTER_RUNTIME_URL;
16
+ return (
17
+ <RuntimeUrl.Provider value={resolved}>
18
+ <AgentAvatarProvider runtimeUrl={resolved}>{children}</AgentAvatarProvider>
19
+ </RuntimeUrl.Provider>
20
+ );
21
+ }
@@ -21,8 +21,7 @@ import type React from 'react';
21
21
  import { useCallback } from 'react';
22
22
  import { View } from 'react-native';
23
23
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
24
- import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';
25
- import { ALIA_CHARACTER_RUNTIME_URL } from '../lib/character-runtime';
24
+ import { AliaAvatarProvider } from './AliaAvatarProvider';
26
25
  import { KeyboardAvoidingView } from '../lib/keyboard';
27
26
  import {
28
27
  type AliaChatAgentRef,
@@ -109,7 +108,7 @@ export function AliaChatScreen({
109
108
  isDarkMode,
110
109
  voice,
111
110
  labels,
112
- characterRuntimeUrl = ALIA_CHARACTER_RUNTIME_URL,
111
+ characterRuntimeUrl,
113
112
  }: AliaChatScreenProps) {
114
113
  const insets = useSafeAreaInsets();
115
114
  // The screen owns the whole window, so it keeps its composer out of both
@@ -142,7 +141,7 @@ export function AliaChatScreen({
142
141
  );
143
142
 
144
143
  return (
145
- <AgentAvatarProvider runtimeUrl={characterRuntimeUrl}>
144
+ <AliaAvatarProvider runtimeUrl={characterRuntimeUrl}>
146
145
  <View
147
146
  className="flex-1 bg-background-full"
148
147
  style={{ paddingTop: insets.top, paddingBottom: insets.bottom }}
@@ -169,6 +168,6 @@ export function AliaChatScreen({
169
168
  />
170
169
  </KeyboardAvoidingView>
171
170
  </View>
172
- </AgentAvatarProvider>
171
+ </AliaAvatarProvider>
173
172
  );
174
173
  }
@@ -1,4 +1,4 @@
1
- import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';
1
+ import { AliaAvatarProvider } from './AliaAvatarProvider';
2
2
  import { AvatarFlightLayer, useAvatarFlights } from '@oxy.so/bloom/avatar-flight';
3
3
  import { BottomSheet, type BottomSheetRef } from '@oxy.so/bloom/bottom-sheet';
4
4
  import {
@@ -15,7 +15,6 @@ import { useWindowDimensions, View } from 'react-native';
15
15
  import { useSharedValue } from 'react-native-reanimated';
16
16
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
17
17
  import { BREAKPOINTS } from '@oxy.so/bloom/styles';
18
- import { ALIA_CHARACTER_RUNTIME_URL } from '../lib/character-runtime';
19
18
  import { AliaChatLabelsProvider, type AliaChatLabels } from '../lib/labels';
20
19
  import type { AliaToolRenderers } from '../lib/tool-renderers';
21
20
  import { useShareHandler } from '../hooks/useShareHandler';
@@ -137,7 +136,7 @@ const AliaChatSheetBody = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
137
136
  isDarkMode,
138
137
  voice,
139
138
  labels,
140
- characterRuntimeUrl = ALIA_CHARACTER_RUNTIME_URL,
139
+ characterRuntimeUrl,
141
140
  onShare,
142
141
  headerMenu,
143
142
  recentConversations = true,
@@ -298,7 +297,7 @@ const AliaChatSheetBody = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
298
297
  // The provider wraps the sheets AND the flight layer: both draw avatars, and an avatar drawn
299
298
  // outside it would fall back to the host's own `/bloom-character/runtime.mjs`.
300
299
  return (
301
- <AgentAvatarProvider runtimeUrl={characterRuntimeUrl}>
300
+ <AliaAvatarProvider runtimeUrl={characterRuntimeUrl}>
302
301
  <BottomSheet
303
302
  ref={mainRef}
304
303
  detached={detached}
@@ -364,7 +363,7 @@ const AliaChatSheetBody = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
364
363
  </View>
365
364
  </BottomSheet>
366
365
  <AvatarFlightLayer flights={flights} />
367
- </AgentAvatarProvider>
366
+ </AliaAvatarProvider>
368
367
  );
369
368
  },
370
369
  );
@@ -6,13 +6,12 @@
6
6
  * which can hold the person's own data, and there is nothing to vote on, play, edit or regenerate.
7
7
  */
8
8
 
9
- import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';
9
+ import { AliaAvatarProvider } from './AliaAvatarProvider';
10
10
  import type { AiChatThreadHandle } from '@oxy.so/bloom/ai-chat';
11
11
  import { Loading } from '@oxy.so/bloom/loading';
12
12
  import { Text } from '@oxy.so/bloom/typography';
13
13
  import { useMemo, useRef } from 'react';
14
14
  import { View } from 'react-native';
15
- import { ALIA_CHARACTER_RUNTIME_URL } from '../lib/character-runtime';
16
15
  import { type AliaChatLabels, AliaChatLabelsProvider, useLabels } from '../lib/labels';
17
16
  import { AliaRuntimeProvider } from '../runtime/config';
18
17
  import {
@@ -73,15 +72,15 @@ export function AliaSharedConversation({
73
72
  token,
74
73
  apiUrl,
75
74
  labels,
76
- characterRuntimeUrl = ALIA_CHARACTER_RUNTIME_URL,
75
+ characterRuntimeUrl,
77
76
  }: AliaSharedConversationProps) {
78
77
  return (
79
- <AgentAvatarProvider runtimeUrl={characterRuntimeUrl}>
78
+ <AliaAvatarProvider runtimeUrl={characterRuntimeUrl}>
80
79
  <AliaChatLabelsProvider labels={labels}>
81
80
  <AliaRuntimeProvider apiUrl={apiUrl}>
82
81
  <SharedThread token={token} />
83
82
  </AliaRuntimeProvider>
84
83
  </AliaChatLabelsProvider>
85
- </AgentAvatarProvider>
84
+ </AliaAvatarProvider>
86
85
  );
87
86
  }
package/src/dictation.ts CHANGED
@@ -1,7 +1,8 @@
1
- // @alia.onl/sdk/dictation — dictation's request, without a component, a hook or a recorder
2
- // `POST /v1/audio/transcriptions` (ADR 0017 §7) as plain functions: an app that records audio its
3
- // own way (Astro's browser shell) sends its clips from here and reaches no React, no native module
4
- // and no Oxy client. `useDictation` and `AliaComposer` use the same request.
1
+ // @alia.onl/sdk/dictation — dictation's network half, without a component, a hook or a recorder
2
+ // `POST /v1/audio/transcriptions` (ADR 0017 §7) and the streaming dictation socket
3
+ // `GET /v1/audio/transcriptions/stream` (ADR 0024) as plain functions: an app that records audio
4
+ // its own way (Astro's browser shell) dictates from here and reaches no React, no native module
5
+ // and no Oxy client. `useDictation` and `AliaComposer` use the same batch request.
5
6
 
6
7
  export {
7
8
  DictationError,
@@ -18,3 +19,22 @@ export type {
18
19
  } from './lib/dictation';
19
20
  export type { AudioRequestAuthentication } from './lib/audio-request-auth';
20
21
  export type { VoiceErrorCode } from './lib/speech-messages';
22
+ export {
23
+ DICTATION_PROTOCOL_VERSION,
24
+ DICTATION_SAMPLE_RATE,
25
+ DICTATION_STREAM_PATH,
26
+ dictationStreamUrl,
27
+ parseDictationServerEvent,
28
+ } from './lib/dictation-protocol';
29
+ export type {
30
+ DictationCloseReason,
31
+ DictationServerEvent,
32
+ DictationStreamErrorCode,
33
+ } from './lib/dictation-protocol';
34
+ export { openDictationStream } from './lib/dictation-stream';
35
+ export type {
36
+ DictationSocket,
37
+ DictationStream,
38
+ DictationStreamAuthentication,
39
+ DictationStreamOptions,
40
+ } from './lib/dictation-stream';
@@ -1,10 +1,10 @@
1
1
  // Generated by `bun run render:avatar-still` (scripts/render-avatar-still.ts). Do not edit.
2
2
 
3
3
  /**
4
- * Alia's still: `ALIA_AVATAR` drawn by @oxy.so/bloom 10.6.0's character runtime as Bloom
5
- * draws any still (a paused portrait, reduced motion), 64 px at 2x, 16489 bytes of PNG.
4
+ * Alia's still: `ALIA_AVATAR` drawn by @oxy.so/bloom 15.78.0's character runtime as Bloom
5
+ * runtime manifest 7e44cfec2bd9e1490fb90270028fd84b12fcbc019f09709af9a1b9187b36b577. It draws a paused portrait with reduced motion, 64 px at 2x, 15415 bytes of PNG.
6
6
  */
7
- export const ALIA_AVATAR_STILL = 'data:image/png;base64,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';
7
+ export const ALIA_AVATAR_STILL = 'data:image/png;base64,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';
8
8
 
9
9
  /** The character it was drawn from; a test holds it equal to `ALIA_AVATAR`. */
10
10
  export const ALIA_AVATAR_STILL_OF = {"preset":"blue_beret","accessory":"none","color":"orchid"};
@@ -0,0 +1,16 @@
1
+ import { ALIA_WEB_ORIGIN } from './origins';
2
+
3
+ // Literal optional require stays directly in try for Metro's optional-peer resolver.
4
+ // This neutral subpath contains no React Native or GPU module imports.
5
+ let revision: string | undefined;
6
+ try {
7
+ const companion = require('@oxy.so/bloom-avatar-native/runtime-revision');
8
+ if (/^[a-f0-9]{64}$/.test(companion.AVATAR_RUNTIME_REVISION ?? '')) {
9
+ revision = companion.AVATAR_RUNTIME_REVISION;
10
+ }
11
+ } catch {
12
+ // Hosts without the optional native companion keep Bloom's WebView fallback.
13
+ }
14
+ export const ALIA_CHARACTER_RUNTIME_URL = revision
15
+ ? `${ALIA_WEB_ORIGIN}/bloom-character/releases/${revision}/runtime.mjs`
16
+ : `${ALIA_WEB_ORIGIN}/bloom-character/runtime.mjs`;
@@ -3,8 +3,8 @@ import { ALIA_WEB_ORIGIN } from './origins';
3
3
  /**
4
4
  * Where the 3D character runtime (Alia's avatar) is served from when the host does not say.
5
5
  *
6
- * The runtime is ~214 MB of engine and data, so the chat loads it from Alia's own web origin
7
- * instead of asking every app to host a copy. That origin serves it with CORS open
6
+ * The engine loads character assets and working motions on demand from Alia's own origin,
7
+ * so hosts need not bundle the entire catalog. That origin serves it with CORS open
8
8
  * (`packages/app/worker/index.js`), which a cross-origin module import needs; a host with a
9
9
  * CSP allows it with `@alia.onl/sdk/csp`. A host that serves its own copy passes
10
10
  * `characterRuntimeUrl`.
@@ -0,0 +1,142 @@
1
+ /**
2
+ * The client half of Alia's streaming dictation protocol,
3
+ * `GET /v1/audio/transcriptions/stream` (ADR 0024).
4
+ *
5
+ * The server's copy is `packages/api/src/lib/dictation/protocol.ts`; the SDK
6
+ * does not depend on the API package, so the shapes are restated here and
7
+ * `DICTATION_PROTOCOL_VERSION` is what turns a mismatch into a refusal instead
8
+ * of a misunderstanding. The API's tests hold the two copies equal.
9
+ *
10
+ * - Binary, client → Alia: microphone PCM16 LE, 24 kHz, mono.
11
+ * - Text: one JSON event per frame, both ways.
12
+ *
13
+ * Segments are Alia's numbers, from 0, in the order the person spoke them. A
14
+ * segment's `transcript.final` is the only text a client keeps:
15
+ * `transcript.interim` (the segment's provisional text, whole, replacing the
16
+ * last) and `transcript.delta` (locked text appended) are display state the
17
+ * final supersedes.
18
+ */
19
+
20
+ export const DICTATION_PROTOCOL_VERSION = 1;
21
+
22
+ export const DICTATION_STREAM_PATH = '/v1/audio/transcriptions/stream';
23
+
24
+ /** PCM16 mono at this rate: the one encoding the stream takes. */
25
+ export const DICTATION_SAMPLE_RATE = 24_000;
26
+
27
+ /** What a dictation ends with that is not a failure. */
28
+ export type DictationCloseReason =
29
+ | 'ended'
30
+ | 'max-duration'
31
+ | 'idle-timeout'
32
+ | 'error'
33
+ | 'server-shutdown';
34
+
35
+ /** Why a dictation failed, as Alia names it. */
36
+ export type DictationStreamErrorCode =
37
+ | 'TRANSCRIPTION_UNAVAILABLE'
38
+ | 'TRANSCRIPTION_LIMITED'
39
+ | 'TRANSCRIPTION_REJECTED'
40
+ | 'TRANSCRIPTION_FAILED'
41
+ | 'CLIP_TOO_LONG'
42
+ | 'INVALID_REQUEST'
43
+ | 'NOT_AUTHENTICATED'
44
+ | 'CREDENTIAL_NOT_SUPPORTED';
45
+
46
+ export type DictationServerEvent =
47
+ | { readonly type: 'dictation.ready'; readonly maxDurationMs: number }
48
+ | { readonly type: 'transcript.interim'; readonly segment: number; readonly text: string }
49
+ | { readonly type: 'transcript.delta'; readonly segment: number; readonly text: string }
50
+ | { readonly type: 'transcript.final'; readonly segment: number; readonly text: string }
51
+ | {
52
+ readonly type: 'error';
53
+ readonly code: DictationStreamErrorCode;
54
+ readonly message: string;
55
+ readonly retryable: boolean;
56
+ }
57
+ | { readonly type: 'dictation.closed'; readonly reason: DictationCloseReason };
58
+
59
+ export type DictationClientEvent =
60
+ | { readonly type: 'auth'; readonly token: string }
61
+ | {
62
+ readonly type: 'dictation.start';
63
+ readonly protocol: number;
64
+ readonly language?: string;
65
+ readonly sampleRate: number;
66
+ }
67
+ | { readonly type: 'dictation.commit' }
68
+ | { readonly type: 'dictation.end' };
69
+
70
+ const CLOSE_REASONS: ReadonlySet<string> = new Set<DictationCloseReason>([
71
+ 'ended',
72
+ 'max-duration',
73
+ 'idle-timeout',
74
+ 'error',
75
+ 'server-shutdown',
76
+ ]);
77
+
78
+ const ERROR_CODES: ReadonlySet<string> = new Set<DictationStreamErrorCode>([
79
+ 'TRANSCRIPTION_UNAVAILABLE',
80
+ 'TRANSCRIPTION_LIMITED',
81
+ 'TRANSCRIPTION_REJECTED',
82
+ 'TRANSCRIPTION_FAILED',
83
+ 'CLIP_TOO_LONG',
84
+ 'INVALID_REQUEST',
85
+ 'NOT_AUTHENTICATED',
86
+ 'CREDENTIAL_NOT_SUPPORTED',
87
+ ]);
88
+
89
+ const isSegment = (value: unknown): value is number =>
90
+ typeof value === 'number' && Number.isInteger(value) && value >= 0;
91
+
92
+ /** One server text frame, or `null` when it is not one of protocol 1's events. */
93
+ export function parseDictationServerEvent(text: string): DictationServerEvent | null {
94
+ let value: unknown;
95
+ try {
96
+ value = JSON.parse(text);
97
+ } catch {
98
+ return null;
99
+ }
100
+ if (value === null || typeof value !== 'object') return null;
101
+ const event = value as Record<string, unknown>;
102
+ switch (event.type) {
103
+ case 'dictation.ready':
104
+ return typeof event.maxDurationMs === 'number' && event.maxDurationMs > 0
105
+ ? { type: 'dictation.ready', maxDurationMs: event.maxDurationMs }
106
+ : null;
107
+ case 'transcript.interim':
108
+ case 'transcript.delta':
109
+ case 'transcript.final':
110
+ return isSegment(event.segment) && typeof event.text === 'string'
111
+ ? { type: event.type, segment: event.segment, text: event.text }
112
+ : null;
113
+ case 'error':
114
+ return typeof event.code === 'string' &&
115
+ ERROR_CODES.has(event.code) &&
116
+ typeof event.message === 'string'
117
+ ? {
118
+ type: 'error',
119
+ code: event.code as DictationStreamErrorCode,
120
+ message: event.message,
121
+ retryable: event.retryable === true,
122
+ }
123
+ : null;
124
+ case 'dictation.closed':
125
+ return typeof event.reason === 'string' && CLOSE_REASONS.has(event.reason)
126
+ ? { type: 'dictation.closed', reason: event.reason as DictationCloseReason }
127
+ : null;
128
+ default:
129
+ return null;
130
+ }
131
+ }
132
+
133
+ /** `wss://…/v1/audio/transcriptions/stream` for an Alia API origin. */
134
+ export function dictationStreamUrl(apiUrl: string): string {
135
+ const base = apiUrl.replace(/\/+$/, '');
136
+ const ws = base.startsWith('https://')
137
+ ? `wss://${base.slice('https://'.length)}`
138
+ : base.startsWith('http://')
139
+ ? `ws://${base.slice('http://'.length)}`
140
+ : base;
141
+ return `${ws}${DICTATION_STREAM_PATH}`;
142
+ }
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Streaming dictation's network half (ADR 0024): the caller's microphone PCM to
3
+ * `GET /v1/audio/transcriptions/stream`, and Alia's segments back while the
4
+ * person speaks.
5
+ *
6
+ * It records nothing and draws nothing: the caller brings PCM16 LE mono at
7
+ * 24 kHz from its own recorder (an AudioWorklet, a native module) and shows
8
+ * the segments its own way. A segment's `transcript.final` is the only text to
9
+ * keep; `transcript.interim` and `transcript.delta` are display state the
10
+ * final supersedes. When the stream cannot open (`TRANSCRIPTION_UNAVAILABLE`),
11
+ * batch `requestTranscription` is the fallback.
12
+ */
13
+
14
+ import {
15
+ type DictationClientEvent,
16
+ type DictationCloseReason,
17
+ type DictationServerEvent,
18
+ DICTATION_PROTOCOL_VERSION,
19
+ DICTATION_SAMPLE_RATE,
20
+ dictationStreamUrl,
21
+ parseDictationServerEvent,
22
+ } from './dictation-protocol';
23
+
24
+ /** The WebSocket the stream drives: a browser's, React Native's or a test's. */
25
+ export interface DictationSocket {
26
+ binaryType: string;
27
+ readonly readyState: number;
28
+ onopen: ((event: unknown) => void) | null;
29
+ onmessage: ((event: { data: unknown }) => void) | null;
30
+ onclose: ((event: unknown) => void) | null;
31
+ onerror: ((event: unknown) => void) | null;
32
+ send(data: string | ArrayBufferLike | ArrayBufferView): void;
33
+ close(code?: number, reason?: string): void;
34
+ }
35
+
36
+ /** How the stream proves the person: their Oxy session token, read once at open. */
37
+ export type DictationStreamAuthentication =
38
+ | {
39
+ readonly getToken: (
40
+ target: string,
41
+ options?: { signal?: AbortSignal },
42
+ ) => string | null | Promise<string | null>;
43
+ readonly token?: never;
44
+ }
45
+ | { readonly token: string; readonly getToken?: never };
46
+
47
+ export type DictationStreamOptions = DictationStreamAuthentication & {
48
+ /** Alia's API origin, `https://api.alia.onl`. */
49
+ readonly apiUrl: string;
50
+ /** BCP 47; omitted lets the model detect the language. */
51
+ readonly language?: string;
52
+ /**
53
+ * Every event, in order. It always ends with one `dictation.closed`; a failure
54
+ * comes as `error` just before it. A connection Alia never answered on is
55
+ * reported as `TRANSCRIPTION_FAILED`, retryable.
56
+ */
57
+ readonly onEvent: (event: DictationServerEvent) => void;
58
+ /** Aborting it is `close()`. */
59
+ readonly signal?: AbortSignal;
60
+ /** The socket factory; the platform's `WebSocket` by default. */
61
+ readonly openSocket?: (url: string) => DictationSocket;
62
+ /** How long `end()` waits for Alia's `dictation.closed` before closing the socket. */
63
+ readonly endTimeoutMs?: number;
64
+ };
65
+
66
+ export interface DictationStream {
67
+ /** One microphone frame: PCM16 LE, mono, {@link DICTATION_SAMPLE_RATE} Hz. */
68
+ append(pcm16: Uint8Array): void;
69
+ /** End the current segment now (push-to-talk). */
70
+ commit(): void;
71
+ /** No more audio: resolves with how the dictation closed, once the finals have arrived. */
72
+ end(): Promise<DictationCloseReason>;
73
+ /** Drop the dictation now; nothing more is reported. */
74
+ close(): void;
75
+ /** Settles with how the dictation closed, whatever closed it. */
76
+ readonly closed: Promise<DictationCloseReason>;
77
+ }
78
+
79
+ const OPEN = 1;
80
+ const DEFAULT_END_TIMEOUT_MS = 8_000;
81
+ /** Audio held until the socket is open and the start sent: ten seconds. */
82
+ const MAX_HELD_BYTES = 10 * DICTATION_SAMPLE_RATE * 2;
83
+
84
+ function defaultSocket(url: string): DictationSocket {
85
+ const WebSocketClass = (globalThis as { WebSocket?: new (url: string) => DictationSocket })
86
+ .WebSocket;
87
+ if (!WebSocketClass) throw new Error('This platform has no WebSocket');
88
+ return new WebSocketClass(url);
89
+ }
90
+
91
+ /** Open one streaming dictation. */
92
+ export function openDictationStream(options: DictationStreamOptions): DictationStream {
93
+ const url = dictationStreamUrl(options.apiUrl);
94
+ const credentialAbort = new AbortController();
95
+ let socket: DictationSocket | null = null;
96
+ let started = false;
97
+ let finished = false;
98
+ let held: Uint8Array[] = [];
99
+ let heldBytes = 0;
100
+ let endRequested = false;
101
+ let endTimer: ReturnType<typeof setTimeout> | null = null;
102
+ let resolveClosed!: (reason: DictationCloseReason) => void;
103
+ const closed = new Promise<DictationCloseReason>((resolve) => {
104
+ resolveClosed = resolve;
105
+ });
106
+
107
+ const sendEvent = (event: DictationClientEvent) => {
108
+ if (socket?.readyState === OPEN) socket.send(JSON.stringify(event));
109
+ };
110
+
111
+ const finish = (reason: DictationCloseReason, report: boolean) => {
112
+ if (finished) return;
113
+ finished = true;
114
+ if (endTimer !== null) clearTimeout(endTimer);
115
+ credentialAbort.abort();
116
+ held = [];
117
+ const current = socket;
118
+ socket = null;
119
+ if (current) {
120
+ current.onmessage = null;
121
+ current.onclose = null;
122
+ current.onerror = null;
123
+ if (current.readyState === OPEN || current.readyState === 0) current.close(1000);
124
+ }
125
+ if (report) options.onEvent({ type: 'dictation.closed', reason });
126
+ resolveClosed(reason);
127
+ };
128
+
129
+ const fail = () => {
130
+ if (finished) return;
131
+ options.onEvent({
132
+ type: 'error',
133
+ code: 'TRANSCRIPTION_FAILED',
134
+ message: 'The dictation lost its connection to Alia.',
135
+ retryable: true,
136
+ });
137
+ finish('error', true);
138
+ };
139
+
140
+ const readToken = async (): Promise<string | null> => {
141
+ if (options.token !== undefined) return options.token || null;
142
+ try {
143
+ const token = await options.getToken(url, { signal: credentialAbort.signal });
144
+ return token || null;
145
+ } catch {
146
+ return null;
147
+ }
148
+ };
149
+
150
+ const begin = async (opened: DictationSocket) => {
151
+ const token = await readToken();
152
+ if (finished || socket !== opened) return;
153
+ if (!token) {
154
+ options.onEvent({
155
+ type: 'error',
156
+ code: 'NOT_AUTHENTICATED',
157
+ message: 'Sign in to Alia to dictate.',
158
+ retryable: false,
159
+ });
160
+ finish('error', true);
161
+ return;
162
+ }
163
+ sendEvent({ type: 'auth', token });
164
+ sendEvent({
165
+ type: 'dictation.start',
166
+ protocol: DICTATION_PROTOCOL_VERSION,
167
+ sampleRate: DICTATION_SAMPLE_RATE,
168
+ ...(options.language === undefined ? {} : { language: options.language }),
169
+ });
170
+ started = true;
171
+ for (const frame of held) opened.send(frame);
172
+ held = [];
173
+ heldBytes = 0;
174
+ if (endRequested) sendEvent({ type: 'dictation.end' });
175
+ };
176
+
177
+ try {
178
+ socket = (options.openSocket ?? defaultSocket)(url);
179
+ } catch {
180
+ queueMicrotask(fail);
181
+ }
182
+ if (socket) {
183
+ const opened = socket;
184
+ opened.binaryType = 'arraybuffer';
185
+ opened.onopen = () => {
186
+ void begin(opened);
187
+ };
188
+ opened.onmessage = (message) => {
189
+ if (typeof message.data !== 'string') return;
190
+ const event = parseDictationServerEvent(message.data);
191
+ if (event === null || finished) return;
192
+ if (event.type === 'dictation.closed') {
193
+ options.onEvent(event);
194
+ finish(event.reason, false);
195
+ return;
196
+ }
197
+ options.onEvent(event);
198
+ };
199
+ opened.onclose = () => fail();
200
+ opened.onerror = () => undefined;
201
+ }
202
+
203
+ const close = () => finish('ended', false);
204
+ if (options.signal) {
205
+ if (options.signal.aborted) queueMicrotask(close);
206
+ else options.signal.addEventListener('abort', close, { once: true });
207
+ }
208
+
209
+ return {
210
+ append(pcm16) {
211
+ if (finished || endRequested || pcm16.byteLength === 0) return;
212
+ if (started && socket?.readyState === OPEN) {
213
+ socket.send(pcm16);
214
+ return;
215
+ }
216
+ if (heldBytes + pcm16.byteLength > MAX_HELD_BYTES) return;
217
+ held.push(pcm16.slice());
218
+ heldBytes += pcm16.byteLength;
219
+ },
220
+ commit() {
221
+ if (!finished && started) sendEvent({ type: 'dictation.commit' });
222
+ },
223
+ end() {
224
+ if (finished) return closed;
225
+ if (!endRequested) {
226
+ endRequested = true;
227
+ if (started) sendEvent({ type: 'dictation.end' });
228
+ endTimer = setTimeout(
229
+ () => finish('ended', true),
230
+ options.endTimeoutMs ?? DEFAULT_END_TIMEOUT_MS,
231
+ );
232
+ }
233
+ return closed;
234
+ },
235
+ close,
236
+ closed,
237
+ };
238
+ }