med-scribe-alliance-ts-sdk 2.0.8 → 2.0.9
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/README.md +247 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,8 +4,7 @@ TypeScript SDK for the [MedScribe Alliance Protocol](https://github.com/MedScrib
|
|
|
4
4
|
|
|
5
5
|
## npm package link
|
|
6
6
|
|
|
7
|
-
https://www.npmjs.com/package/med-scribe-alliance-ts-sdk
|
|
8
|
-
|
|
7
|
+
https://www.npmjs.com/package/med-scribe-alliance-ts-sdk
|
|
9
8
|
|
|
10
9
|
## Installation
|
|
11
10
|
|
|
@@ -19,57 +18,269 @@ Peer dependencies (installed automatically):
|
|
|
19
18
|
- `@breezystack/lamejs` — MP3 encoding
|
|
20
19
|
- `zod` — Schema validation
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Integration Guide (Step-by-Step)
|
|
24
|
+
|
|
25
|
+
### Step 1: Create the Client
|
|
26
|
+
|
|
27
|
+
The `baseUrl` is **required** — all API calls (session creation, upload, status polling) go through this URL.
|
|
23
28
|
|
|
24
29
|
```ts
|
|
25
30
|
import { ScribeClient } from 'med-scribe-alliance-ts-sdk';
|
|
26
31
|
|
|
27
32
|
const client = new ScribeClient({
|
|
28
|
-
baseUrl: 'https://api.
|
|
33
|
+
baseUrl: 'https://api.eka.care/voice/api/v2', // your scribe service URL
|
|
29
34
|
accessToken: 'your-bearer-token',
|
|
35
|
+
debug: true, // optional: logs SDK activity to console
|
|
30
36
|
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Step 2: Initialize (Discovery)
|
|
40
|
+
|
|
41
|
+
`init()` fetches the discovery document from the server. This tells the SDK what the server supports (models, languages, upload methods, audio formats, etc.).
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const initResult = await client.init();
|
|
45
|
+
if (!initResult.success) {
|
|
46
|
+
console.error('Init failed:', initResult.error.message);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> `startRecording()` calls `init()` automatically if not already initialized. You can skip this step if you go directly to recording.
|
|
52
|
+
|
|
53
|
+
### Step 3: Register Callbacks
|
|
54
|
+
|
|
55
|
+
Register callbacks **before** starting a recording. These are how you receive events from the SDK.
|
|
31
56
|
|
|
32
|
-
|
|
57
|
+
```ts
|
|
58
|
+
// Upload progress
|
|
33
59
|
client.registerCallback('onUploadEvent', (event) => {
|
|
34
60
|
if (event.type === 'progress') {
|
|
35
61
|
console.log(`Uploaded ${event.data.successCount}/${event.data.totalCount}`);
|
|
36
62
|
}
|
|
37
63
|
});
|
|
38
64
|
|
|
65
|
+
// Recording state changes
|
|
66
|
+
client.registerCallback('onRecordingStateChange', (event) => {
|
|
67
|
+
console.log('Recording state:', event.type); // 'started' | 'paused' | 'resumed' | 'ended'
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Errors (VAD failures, network issues, validation)
|
|
39
71
|
client.registerCallback('onError', (event) => {
|
|
40
72
|
console.error(`[${event.error.code}] ${event.error.message}`);
|
|
41
73
|
});
|
|
42
74
|
|
|
43
|
-
//
|
|
75
|
+
// Auto token refresh on 401
|
|
76
|
+
client.registerCallback('onTokenRequired', async (event) => {
|
|
77
|
+
const newToken = await refreshMyAuthToken();
|
|
78
|
+
event.resolve(newToken);
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 4: Start Recording
|
|
83
|
+
|
|
84
|
+
Creates a session, starts the microphone, and begins chunked upload in one call.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
44
87
|
const result = await client.startRecording({
|
|
45
|
-
templates: ['soap',
|
|
46
|
-
uploadType: 'chunked',
|
|
88
|
+
templates: ['soap'], // required: template IDs for extraction
|
|
89
|
+
uploadType: 'chunked', // 'chunked' (default) | 'single' | 'stream'
|
|
90
|
+
sessionMode: 'consultation', // optional: 'consultation' | 'dictation'
|
|
91
|
+
transcriptLanguage: 'en', // optional: language code for transcript output
|
|
92
|
+
languageHint: ['en', 'hi'], // optional: language codes for audio input
|
|
93
|
+
patientDetails: { // optional
|
|
94
|
+
name: 'John Doe',
|
|
95
|
+
age: '45',
|
|
96
|
+
gender: 'male',
|
|
97
|
+
},
|
|
98
|
+
additionalData: {}, // optional: any extra data for the session
|
|
99
|
+
txnId: 'your-transaction-id', // optional: external transaction ID
|
|
47
100
|
});
|
|
48
101
|
|
|
49
102
|
if (!result.success) {
|
|
50
|
-
console.error(result.error);
|
|
103
|
+
console.error('Failed to start:', result.error.message);
|
|
104
|
+
return;
|
|
51
105
|
}
|
|
52
106
|
|
|
53
|
-
|
|
107
|
+
const sessionId = result.data.session_id;
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### Pause / Resume
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
client.pauseRecording(); // pauses VAD — mic stays open, no new chunks created
|
|
114
|
+
client.resumeRecording(); // resumes VAD processing
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Step 5: End Recording
|
|
118
|
+
|
|
119
|
+
Stops the microphone, flushes the last audio chunk, waits for all uploads to complete, and tells the server the session has ended (triggers server-side processing).
|
|
54
120
|
|
|
55
|
-
|
|
121
|
+
```ts
|
|
56
122
|
const stopResult = await client.endRecording();
|
|
57
123
|
|
|
58
124
|
if (stopResult.success) {
|
|
59
125
|
console.log(`${stopResult.data.totalFiles} files uploaded`);
|
|
60
126
|
console.log(`${stopResult.data.failedUploads.length} failed`);
|
|
61
127
|
}
|
|
128
|
+
```
|
|
62
129
|
|
|
63
|
-
|
|
64
|
-
|
|
130
|
+
### Step 6: Poll for Results
|
|
131
|
+
|
|
132
|
+
After ending the recording, poll the server until processing is complete.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const abortController = new AbortController();
|
|
136
|
+
|
|
137
|
+
const status = await client.getSessionStatus(sessionId, {
|
|
65
138
|
poll: {
|
|
66
139
|
maxAttempts: 60,
|
|
67
140
|
intervalMs: 2000,
|
|
68
|
-
|
|
141
|
+
signal: abortController.signal, // optional: abort polling early
|
|
142
|
+
onProgress: (s) => {
|
|
143
|
+
console.log(`Status: ${s.status}`);
|
|
144
|
+
if (s.templates) {
|
|
145
|
+
console.log('Templates:', s.templates);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
69
148
|
},
|
|
70
149
|
});
|
|
150
|
+
|
|
151
|
+
if (status.success) {
|
|
152
|
+
console.log('Final status:', status.data.status);
|
|
153
|
+
console.log('Templates:', status.data.templates);
|
|
154
|
+
console.log('Transcript:', status.data.transcript);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Step 7: Clean Up
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
await client.reset(); // stops recording if active, clears all state and caches
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Flow Diagram
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
new ScribeClient({ baseUrl, accessToken })
|
|
168
|
+
│
|
|
169
|
+
▼
|
|
170
|
+
init() ────────── Fetches discovery (auto-called by startRecording)
|
|
171
|
+
│
|
|
172
|
+
▼
|
|
173
|
+
registerCallback() ── Set up event handlers before recording
|
|
174
|
+
│
|
|
175
|
+
▼
|
|
176
|
+
startRecording() ──── Creates session → starts mic → begins upload
|
|
177
|
+
│
|
|
178
|
+
pause / resume ──── Optional during recording
|
|
179
|
+
│
|
|
180
|
+
▼
|
|
181
|
+
endRecording() ────── Stops mic → flushes audio → ends session → triggers processing
|
|
182
|
+
│
|
|
183
|
+
▼
|
|
184
|
+
getSessionStatus() ── Poll until completed/failed
|
|
185
|
+
│
|
|
186
|
+
▼
|
|
187
|
+
Read results ──────── templates, transcript, errors
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Important Notes
|
|
193
|
+
|
|
194
|
+
- **`baseUrl` is the root for all API calls.** Session creation, audio upload, status polling — everything uses this URL. Make sure it's correct and accessible.
|
|
195
|
+
- **`accessToken` must be a valid Bearer token.** All API requests include `Authorization: Bearer <token>`. If it expires, register `onTokenRequired` to auto-refresh.
|
|
196
|
+
- **Register callbacks before `startRecording()`.** Events fire immediately once recording starts — if callbacks aren't registered, you'll miss upload progress and errors.
|
|
197
|
+
- **`endRecording()` triggers server processing.** Once you call it, the server begins processing the uploaded audio. Use `cancelSession()` instead if you don't want processing to happen.
|
|
198
|
+
- **`cancelSession()` does NOT trigger processing.** It stops the recorder locally, cleans up state, and tells the server the session is cancelled. No `endSession` call is made to the backend.
|
|
199
|
+
- **All async methods return `SDKResult<T>`, never throw.** Always check `result.success` before accessing `result.data`. Errors are in `result.error`.
|
|
200
|
+
- **The SDK validates inputs against the discovery document.** If the server doesn't support an upload type, language, or model you requested, you'll get a `ValidationError` before the API call is made.
|
|
201
|
+
- **SharedWorker is optional.** If you provide `workerScriptUrl`, the SDK offloads MP3 compression and upload to a SharedWorker. If the worker fails to load, it silently falls back to main-thread processing.
|
|
202
|
+
- **Microphone permission is requested on `startRecording()`.** The browser will prompt the user for mic access. If denied, you'll get an error via `onError` callback.
|
|
203
|
+
- **`reset()` is a full teardown.** It destroys the transport, clears discovery cache, removes all callbacks, and sets the client back to uninitialized state. You'll need to call `init()` (or `startRecording()`) again after reset.
|
|
204
|
+
- **Polling supports `AbortSignal`.** Pass `signal` in poll options to cancel polling early (e.g. when the user navigates away).
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Other Operations
|
|
209
|
+
|
|
210
|
+
### Cancel a Session
|
|
211
|
+
|
|
212
|
+
Stops the recorder locally **without** triggering server-side processing, then tells the server the session is cancelled.
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
await client.cancelSession(); // cancels the current active session
|
|
216
|
+
await client.cancelSession('specific-session-id'); // or by ID
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Update a Session (Patch)
|
|
220
|
+
|
|
221
|
+
Update session properties after creation.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
await client.updateSession({
|
|
225
|
+
patient_details: { name: 'Jane Doe', age: '30', gender: 'female' },
|
|
226
|
+
additional_data: { notes: 'Follow-up visit' },
|
|
227
|
+
templates: ['soap', 'prescription'],
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Process a Specific Template
|
|
232
|
+
|
|
233
|
+
Trigger server-side processing for a specific template.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
await client.processTemplate('soap');
|
|
237
|
+
await client.processTemplate('soap', 'session-id'); // with explicit session ID
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Two-Step Flow (Create Session + Record Separately)
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
// Step 1: Create session
|
|
244
|
+
const session = await client.createSession({
|
|
245
|
+
templates: ['soap'],
|
|
246
|
+
upload_type: 'chunked',
|
|
247
|
+
communication_protocol: 'http',
|
|
248
|
+
session_mode: 'consultation',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
if (!session.success) return;
|
|
252
|
+
|
|
253
|
+
// Step 2: Start recording with the existing session
|
|
254
|
+
await client.startRecordingWithSession(session.data, {
|
|
255
|
+
uploadType: 'chunked',
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Get Status for a Specific Template
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const status = await client.getSessionStatus(sessionId, {
|
|
263
|
+
templateId: 'soap',
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Retry Failed Uploads
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
if (client.hasFailedUploads()) {
|
|
271
|
+
const retryResult = await client.retryFailedUploads();
|
|
272
|
+
console.log(`Retried: ${retryResult.data.retried}, Succeeded: ${retryResult.data.succeeded}`);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Update Auth Token
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
client.setAccessToken('new-bearer-token');
|
|
71
280
|
```
|
|
72
281
|
|
|
282
|
+
---
|
|
283
|
+
|
|
73
284
|
## Configuration
|
|
74
285
|
|
|
75
286
|
```ts
|
|
@@ -100,6 +311,24 @@ interface ScribeSDKConfig {
|
|
|
100
311
|
}
|
|
101
312
|
```
|
|
102
313
|
|
|
314
|
+
## Recording Options
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
interface RecordingOptions {
|
|
318
|
+
templates: string[]; // Template IDs for extraction (required)
|
|
319
|
+
model?: string; // Model ID from discovery
|
|
320
|
+
languageHint?: string[]; // Language codes for audio input
|
|
321
|
+
transcriptLanguage?: string; // Language code for transcript output
|
|
322
|
+
uploadType?: string; // 'chunked' | 'single' | 'stream' (default: 'chunked')
|
|
323
|
+
communicationProtocol?: string; // 'http' | 'websocket' (default: 'http')
|
|
324
|
+
additionalData?: Record<string, any>; // Extra data for the session
|
|
325
|
+
deviceId?: string; // Specific microphone device ID
|
|
326
|
+
sessionMode?: string; // 'consultation' | 'dictation'
|
|
327
|
+
patientDetails?: PatientDetails; // Patient info
|
|
328
|
+
txnId?: string; // External transaction ID
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
103
332
|
## API Reference
|
|
104
333
|
|
|
105
334
|
### Lifecycle
|
|
@@ -123,42 +352,16 @@ interface ScribeSDKConfig {
|
|
|
123
352
|
| `retryFailedUploads()` | `SDKResult<RetryUploadResult>` | Retry uploads that failed during the last recording. |
|
|
124
353
|
| `hasFailedUploads()` | `boolean` | Whether there are retryable failed uploads. |
|
|
125
354
|
|
|
126
|
-
#### Recording Options
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
interface RecordingOptions {
|
|
130
|
-
templates: string[]; // Template IDs for extraction (required)
|
|
131
|
-
model?: string; // Model ID from discovery
|
|
132
|
-
languageHint?: string[]; // Language codes for audio input
|
|
133
|
-
transcriptLanguage?: string[];// Language codes for transcript output
|
|
134
|
-
uploadType?: string; // 'chunked' | 'single' (default: 'chunked')
|
|
135
|
-
communicationProtocol?: string;// 'http' | 'websocket' (default: 'http')
|
|
136
|
-
additionalData?: Record<string, any>;
|
|
137
|
-
deviceId?: string; // Specific microphone device ID
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
355
|
### Session
|
|
142
356
|
|
|
143
357
|
| Method | Returns | Description |
|
|
144
358
|
|---|---|---|
|
|
145
359
|
| `createSession(request)` | `SDKResult<CreateSessionResponse>` | Create a session without starting a recording. |
|
|
146
|
-
| `getSessionStatus(sessionId?, options?)` | `SDKResult<GetSessionStatusResponse>` | Get status.
|
|
360
|
+
| `getSessionStatus(sessionId?, options?)` | `SDKResult<GetSessionStatusResponse>` | Get status. Supports `poll` and `templateId` options. |
|
|
147
361
|
| `getCurrentSession()` | `CreateSessionResponse \| null` | Get the active session if any. |
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
Pass `poll` options to `getSessionStatus` to poll until the session reaches a terminal state:
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
const result = await client.getSessionStatus(sessionId, {
|
|
155
|
-
poll: {
|
|
156
|
-
maxAttempts: 60,
|
|
157
|
-
intervalMs: 2000,
|
|
158
|
-
onProgress: (status) => console.log(status.status),
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
```
|
|
362
|
+
| `updateSession(request, sessionId?)` | `SDKResult<PatchSessionResponse>` | Patch session (patient details, status, etc.). |
|
|
363
|
+
| `processTemplate(templateId, sessionId?)` | `SDKResult<ProcessTemplateResponse>` | Trigger processing for a specific template. |
|
|
364
|
+
| `cancelSession(sessionId?)` | `SDKResult<PatchSessionResponse>` | Cancel session (stops recorder, no server processing). |
|
|
162
365
|
|
|
163
366
|
### Discovery
|
|
164
367
|
|
|
@@ -225,19 +428,6 @@ console.log(result.data.session_id);
|
|
|
225
428
|
| `WorkerError` | — | SharedWorker failure |
|
|
226
429
|
| `UploadError` | — | Audio upload failure |
|
|
227
430
|
|
|
228
|
-
### Auto Token Refresh
|
|
229
|
-
|
|
230
|
-
When a 401 is received, the SDK dispatches `onTokenRequired`. Supply a new token to retry the request:
|
|
231
|
-
|
|
232
|
-
```ts
|
|
233
|
-
client.registerCallback('onTokenRequired', async (event) => {
|
|
234
|
-
const newToken = await refreshMyAuthToken();
|
|
235
|
-
event.resolve(newToken);
|
|
236
|
-
});
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Concurrent 401s are deduplicated — only one callback fires regardless of how many requests failed simultaneously.
|
|
240
|
-
|
|
241
431
|
## SharedWorker Support
|
|
242
432
|
|
|
243
433
|
The SDK offloads MP3 compression and upload to a SharedWorker for better main-thread performance. The worker is bundled separately as `dist/worker.bundle.js`.
|
|
@@ -299,26 +489,6 @@ const client = new ScribeClient({
|
|
|
299
489
|
|
|
300
490
|
IPC mode always uses main-thread compression (SharedWorker can't access the IPC bridge).
|
|
301
491
|
|
|
302
|
-
## Two-Step Flow
|
|
303
|
-
|
|
304
|
-
For apps that need to create the session separately from recording:
|
|
305
|
-
|
|
306
|
-
```ts
|
|
307
|
-
// Step 1: Create session
|
|
308
|
-
const session = await client.createSession({
|
|
309
|
-
templates: ['soap'],
|
|
310
|
-
upload_type: 'chunked',
|
|
311
|
-
communication_protocol: 'http',
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
if (!session.success) return;
|
|
315
|
-
|
|
316
|
-
// Step 2: Start recording with the existing session
|
|
317
|
-
await client.startRecordingWithSession(session.data, {
|
|
318
|
-
uploadType: 'chunked',
|
|
319
|
-
});
|
|
320
|
-
```
|
|
321
|
-
|
|
322
492
|
## Building from Source
|
|
323
493
|
|
|
324
494
|
```bash
|