med-scribe-alliance-ts-sdk 2.0.1 → 2.0.3

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 CHANGED
@@ -1,382 +1,339 @@
1
+ # MedScribe Alliance TS SDK
1
2
 
2
- # Scribe EMR Protocol SDK
3
+ TypeScript SDK for the [MedScribe Alliance Protocol](https://github.com/MedScribeAlliance/scribe-emr-protocol) — handles discovery, recording, audio chunking (VAD), MP3 compression, upload, session lifecycle, and output retrieval.
3
4
 
4
- A TypeScript SDK for the [MedScribe Alliance Protocol](https://github.com/MedScribeAlliance/scribe-emr-protocol), providing a clean and type-safe interface for medical transcription services.
5
+ ## npm package link
5
6
 
6
- ## Features
7
+ https://www.npmjs.com/package/med-scribe-alliance-ts-sdk
7
8
 
8
- - ✅ **Protocol Compliant**: Fully implements the MedScribe Alliance Protocol specification
9
- - ✅ **Type-Safe**: Complete TypeScript definitions for all API interactions
10
- - ✅ **Auto-Discovery**: Automatic service capability discovery via well-known endpoint
11
- - ✅ **Schema Validation**: AJV-powered validation against OpenAPI schemas
12
- - ✅ **Error Handling**: Comprehensive error handling with typed error codes
13
- - ✅ **Event System**: Built-in event emitter for session lifecycle events
14
- - ✅ **Polling Support**: Automatic polling for session completion
15
9
 
16
10
  ## Installation
17
11
 
18
12
  ```bash
19
- npm install scribe-standard-sdk
13
+ npm install med-scribe-alliance-ts-sdk
20
14
  ```
21
15
 
16
+ Peer dependencies (installed automatically):
17
+
18
+ - `@ricky0123/vad-web` — Voice Activity Detection
19
+ - `@breezystack/lamejs` — MP3 encoding
20
+ - `zod` — Schema validation
21
+
22
22
  ## Quick Start
23
23
 
24
- ```typescript
25
- import { ScribeClient } from 'scribe-standard-sdk';
24
+ ```ts
25
+ import { ScribeClient } from 'med-scribe-alliance-ts-sdk';
26
26
 
27
- // Initialize the SDK
28
27
  const client = new ScribeClient({
29
- apiKey: 'your-api-key',
30
- baseUrl: 'https://api.scribe.example.com',
31
- debug: true, // Optional: enable debug logging
28
+ baseUrl: 'https://api.example.com/voice/api/v2',
29
+ accessToken: 'your-bearer-token',
32
30
  });
33
31
 
34
- // Initialize (performs discovery)
35
- await client.init();
32
+ // Register callbacks
33
+ client.registerCallback('onUploadEvent', (event) => {
34
+ if (event.type === 'progress') {
35
+ console.log(`Uploaded ${event.data.successCount}/${event.data.totalCount}`);
36
+ }
37
+ });
36
38
 
37
- // Start a recording session
38
- const session = await client.startRecording({
39
- templates: ['soap', 'medications'],
40
- languageHint: ['en'],
41
- model: 'pro',
39
+ client.registerCallback('onError', (event) => {
40
+ console.error(`[${event.error.code}] ${event.error.message}`);
42
41
  });
43
42
 
44
- console.log('Session created:', session.session_id);
45
- console.log('Upload audio to:', session.upload_url);
43
+ // Start recording — creates session, starts mic, begins chunked upload
44
+ const result = await client.startRecording({
45
+ templates: ['soap', 'prescription'],
46
+ uploadType: 'chunked',
47
+ });
46
48
 
47
- // ... Upload audio chunks to session.upload_url ...
49
+ if (!result.success) {
50
+ console.error(result.error);
51
+ }
48
52
 
49
- // End the recording session
50
- const endResponse = await client.endRecording();
51
- console.log('Session ended, processing started');
53
+ // ... user speaks ...
52
54
 
53
- // Poll for completion
54
- const result = await client.pollForCompletion(session.session_id, {
55
- maxAttempts: 60,
56
- intervalMs: 2000,
57
- onProgress: (status) => {
58
- console.log('Status:', status.status);
59
- },
60
- });
55
+ // Stop recording — flushes remaining audio, waits for uploads, ends session
56
+ const stopResult = await client.endRecording();
61
57
 
62
- // Access the results
63
- if (result.status === 'completed') {
64
- console.log('Transcript:', result.transcript);
65
- console.log('Templates:', result.templates);
58
+ if (stopResult.success) {
59
+ console.log(`${stopResult.data.totalFiles} files uploaded`);
60
+ console.log(`${stopResult.data.failedUploads.length} failed`);
66
61
  }
62
+
63
+ // Poll for results
64
+ const status = await client.getSessionStatus(result.data.session_id, {
65
+ poll: {
66
+ maxAttempts: 60,
67
+ intervalMs: 2000,
68
+ onProgress: (s) => console.log(`Status: ${s.status}`),
69
+ },
70
+ });
67
71
  ```
68
72
 
69
- ## Core API
73
+ ## Configuration
70
74
 
71
- ### ScribeClient
75
+ ```ts
76
+ interface ScribeSDKConfig {
77
+ /** Base URL of the scribe service (required) */
78
+ baseUrl: string;
72
79
 
73
- The main SDK client class.
80
+ /** Bearer token for authentication */
81
+ accessToken?: string;
74
82
 
75
- #### Constructor
83
+ /** Transport mode: 'direct' (HTTP) or 'ipc' (Electron). Default: 'direct' */
84
+ mode?: 'direct' | 'ipc';
76
85
 
77
- ```typescript
78
- new ScribeClient(config: ScribeSDKConfig)
79
- ```
86
+ /** IPC bridge — required when mode is 'ipc' */
87
+ ipcTransport?: IpcBridge;
80
88
 
81
- **Config Options:**
82
- - `apiKey` (required): Your API key for authentication
83
- - `baseUrl` (optional): Base URL of the Scribe service
84
- - `debug` (optional): Enable debug logging (default: `false`)
85
- - `autoDiscovery` (optional): Auto-fetch service capabilities (default: `true`)
89
+ /** SharedWorker: true (require), false (disable), 'auto' (detect). Default: 'auto' */
90
+ useWorker?: boolean | 'auto';
86
91
 
87
- #### Methods
92
+ /** URL to worker.bundle.js. Use getWorkerUrl() to resolve. */
93
+ workerScriptUrl?: string;
88
94
 
89
- ##### `init(): Promise<void>`
95
+ /** Enable debug logging. Default: false */
96
+ debug?: boolean;
90
97
 
91
- Initialize the SDK and perform service discovery.
98
+ /** Auto-fetch discovery document on init. Default: true */
99
+ autoDiscovery?: boolean;
100
+ }
101
+ ```
92
102
 
93
- ```typescript
94
- await client.init();
103
+ ## API Reference
104
+
105
+ ### Lifecycle
106
+
107
+ | Method | Returns | Description |
108
+ |---|---|---|
109
+ | `init()` | `SDKResult<void>` | Fetch discovery document. Called automatically by `startRecording`. |
110
+ | `reset()` | `Promise<void>` | Stop recording, clear all state and caches. |
111
+
112
+ ### Recording
113
+
114
+ | Method | Returns | Description |
115
+ |---|---|---|
116
+ | `startRecording(options)` | `SDKResult<CreateSessionResponse>` | Create session + start mic + begin upload. |
117
+ | `startRecordingWithSession(session, options?)` | `SDKResult<void>` | Attach recorder to an existing session. |
118
+ | `pauseRecording()` | `void` | Pause VAD (mic stays open, no chunks created). |
119
+ | `resumeRecording()` | `void` | Resume VAD processing. |
120
+ | `endRecording()` | `SDKResult<StopRecordingResult>` | Stop mic, flush audio, wait for uploads, end session. |
121
+ | `isRecording()` | `boolean` | Whether a recording is active. |
122
+ | `isRecordingPaused()` | `boolean` | Whether the active recording is paused. |
123
+ | `retryFailedUploads()` | `SDKResult<RetryUploadResult>` | Retry uploads that failed during the last recording. |
124
+ | `hasFailedUploads()` | `boolean` | Whether there are retryable failed uploads. |
125
+
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
+ }
95
139
  ```
96
140
 
97
- ##### `startRecording(options: RecordingOptions): Promise<CreateSessionResponse>`
141
+ ### Session
98
142
 
99
- Start a new recording session.
143
+ | Method | Returns | Description |
144
+ |---|---|---|
145
+ | `createSession(request)` | `SDKResult<CreateSessionResponse>` | Create a session without starting a recording. |
146
+ | `getSessionStatus(sessionId?, options?)` | `SDKResult<GetSessionStatusResponse>` | Get status. Pass `{ poll: PollOptions }` to poll until completion. |
147
+ | `getCurrentSession()` | `CreateSessionResponse \| null` | Get the active session if any. |
100
148
 
101
- **Options:**
102
- - `templates` (required): Array of template IDs to extract (e.g., `['soap', 'medications']`)
103
- - `model` (optional): Model ID from discovery
104
- - `languageHint` (optional): Array of ISO 639-1 language codes for audio input (e.g., `['en', 'es']`)
105
- - `transcriptLanguage` (optional): Array of ISO 639-1 codes for transcript output (e.g., `['en']`)
106
- - `uploadType` (optional): `'chunked'` or `'single'`
107
- - `communicationProtocol` (optional): `'http'`, `'websocket'`, or `'rpc'` (default: `'http'`)
108
- - `additionalData` (optional): Pass-through data for your application
149
+ #### Polling
109
150
 
110
- **Returns:**
111
- - `session_id`: Unique session identifier
112
- - `status`: Session status (`'created'`)
113
- - `created_at`: ISO 8601 timestamp
114
- - `expires_at`: ISO 8601 expiry timestamp
115
- - `upload_url`: URL for uploading audio
151
+ Pass `poll` options to `getSessionStatus` to poll until the session reaches a terminal state:
116
152
 
117
- ```typescript
118
- const session = await client.startRecording({
119
- templates: ['soap'],
120
- languageHint: ['en'],
121
- transcriptLanguage: ['en'],
122
- additionalData: {
123
- patient_id: 'pat_123',
124
- encounter_id: 'enc_456',
153
+ ```ts
154
+ const result = await client.getSessionStatus(sessionId, {
155
+ poll: {
156
+ maxAttempts: 60,
157
+ intervalMs: 2000,
158
+ onProgress: (status) => console.log(status.status),
125
159
  },
126
160
  });
127
161
  ```
128
162
 
129
- ##### `endRecording(): Promise<EndSessionResponse>`
163
+ ### Discovery
130
164
 
131
- End the current recording session and trigger processing.
165
+ | Method | Returns | Description |
166
+ |---|---|---|
167
+ | `getDiscoveryDocument()` | `DiscoveryDocument \| null` | Raw discovery document. |
168
+ | `getDiscoveryConfig()` | `SDKResult<ResolvedConfig>` | Resolved config from discovery. |
169
+ | `refreshDiscovery()` | `SDKResult<ResolvedConfig>` | Force-refresh discovery. |
132
170
 
133
- ```typescript
134
- const response = await client.endRecording();
135
- ```
136
-
137
- ##### `getOutputStatus(sessionId?: string): Promise<GetSessionStatusResponse>`
171
+ ### Auth
138
172
 
139
- Get the current status and results of a session.
173
+ | Method | Description |
174
+ |---|---|
175
+ | `setAccessToken(token)` | Update Bearer token. Propagates to transport, recorder, and worker. |
140
176
 
141
- ```typescript
142
- const status = await client.getOutputStatus(session.session_id);
177
+ ### Callbacks
143
178
 
144
- if (status.status === 'completed') {
145
- console.log('Transcript:', status.transcript);
146
- console.log('Templates:', status.templates);
147
- }
148
- ```
179
+ Register with `client.registerCallback(name, handler)`, remove with `client.removeCallback(name, handler)`.
149
180
 
150
- **Session Status Values:**
151
- - `created`: Session created, awaiting audio
152
- - `processing`: Audio is being processed
153
- - `completed`: Processing complete, all templates successful
154
- - `partial`: Processing complete, some templates failed
155
- - `failed`: Processing failed completely
181
+ | Callback | Payload | Description |
182
+ |---|---|---|
183
+ | `onRecordingStateChange` | `RecordingStateChangeEvent` | Recording started, paused, resumed, or ended. |
184
+ | `onAudioEvent` | `AudioEvent` | Speech detection, silence warnings, chunk ready. |
185
+ | `onUploadEvent` | `UploadEvent` | Upload progress and failures. |
186
+ | `onSessionEvent` | `SessionEvent` | Session created, ended, status updates. |
187
+ | `onError` | `ErrorEvent` | VAD, worker, transport, or validation errors. |
188
+ | `onTokenRequired` | `TokenRequiredEvent` | 401 received — call `event.resolve(newToken)` to retry. |
156
189
 
157
- ##### `pollForCompletion(sessionId?, options?): Promise<GetSessionStatusResponse>`
190
+ ## Error Handling
158
191
 
159
- Poll for session completion with automatic retries.
192
+ All public async methods return `SDKResult<T>` — errors are returned, not thrown:
160
193
 
161
- ```typescript
162
- const result = await client.pollForCompletion(session.session_id, {
163
- maxAttempts: 60, // Maximum polling attempts
164
- intervalMs: 2000, // Interval between polls (ms)
165
- onProgress: (status) => {
166
- console.log('Current status:', status.status);
167
- },
168
- });
194
+ ```ts
195
+ type SDKResult<T> =
196
+ | { success: true; data: T }
197
+ | { success: false; error: ScribeError };
169
198
  ```
170
199
 
171
- ##### `getCurrentSession(): CreateSessionResponse | null`
200
+ ```ts
201
+ const result = await client.startRecording({ templates: ['soap'] });
172
202
 
173
- Get the current active session.
203
+ if (!result.success) {
204
+ console.error(result.error.code, result.error.message);
205
+ return;
206
+ }
174
207
 
175
- ```typescript
176
- const currentSession = client.getCurrentSession();
208
+ // result.data is typed as CreateSessionResponse
209
+ console.log(result.data.session_id);
177
210
  ```
178
211
 
179
- ##### `getDiscoveryDocument(): DiscoveryDocument | null`
180
-
181
- Get the cached discovery document.
182
-
183
- ```typescript
184
- const discovery = client.getDiscoveryDocument();
185
- console.log('Supported models:', discovery?.models);
186
- console.log('Supported languages:', discovery?.languages.supported);
212
+ ### Error Classes
213
+
214
+ | Error | HTTP | Description |
215
+ |---|---|---|
216
+ | `ScribeError` | — | Base error class |
217
+ | `ValidationError` | 400 | Invalid request or config |
218
+ | `AuthenticationError` | 401 | Auth failed (after token refresh attempt) |
219
+ | `ForbiddenError` | 403 | Access denied |
220
+ | `SessionNotFoundError` | 404 | Session doesn't exist |
221
+ | `SessionExpiredError` | 410 | Session expired |
222
+ | `RateLimitError` | 429 | Rate limit exceeded |
223
+ | `DiscoveryError` | — | Discovery fetch/parse failed |
224
+ | `TransportError` | — | Network / IPC failure |
225
+ | `WorkerError` | — | SharedWorker failure |
226
+ | `UploadError` | — | Audio upload failure |
227
+
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
+ });
187
237
  ```
188
238
 
189
- ## Event System
239
+ Concurrent 401s are deduplicated — only one callback fires regardless of how many requests failed simultaneously.
190
240
 
191
- The SDK emits events for session lifecycle tracking.
241
+ ## SharedWorker Support
192
242
 
193
- ```typescript
194
- client.on('discovery:complete', (event) => {
195
- console.log('Discovery complete:', event.data);
196
- });
243
+ 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`.
197
244
 
198
- client.on('session:created', (event) => {
199
- console.log('Session created:', event.data);
200
- });
245
+ ### Setup
201
246
 
202
- client.on('session:ended', (event) => {
203
- console.log('Session ended:', event.data);
204
- });
247
+ ```ts
248
+ import { ScribeClient, getWorkerUrl } from 'med-scribe-alliance-ts-sdk';
205
249
 
206
- client.on('session:status_update', (event) => {
207
- console.log('Status update:', event.data);
208
- });
209
-
210
- client.on('error', (event) => {
211
- console.error('Error:', event.error);
250
+ const client = new ScribeClient({
251
+ baseUrl: 'https://api.example.com',
252
+ workerScriptUrl: getWorkerUrl(), // or a custom path
212
253
  });
213
254
  ```
214
255
 
215
- ## Schema Validation
216
-
217
- The SDK automatically validates all API requests against the OpenAPI schema using AJV. This ensures that:
218
-
219
- - Request bodies conform to the expected structure
220
- - Required fields are present
221
- - Field types match the specification
222
- - Session IDs follow the correct pattern (`ses_[a-zA-Z0-9]+`)
223
- - Enum values are valid (e.g., `model`, `upload_type`, `communication_protocol`)
224
-
225
- Validation happens automatically before any API call:
226
-
227
- ```typescript
228
- // This will throw a ValidationError if the request is invalid
229
- try {
230
- const session = await client.startRecording({
231
- templates: ['soap', 'medications'],
232
- model: 'invalid-model', // ❌ Will fail validation if not in enum
233
- uploadType: 'chunked',
234
- });
235
- } catch (error) {
236
- if (error instanceof ValidationError) {
237
- console.error('Validation failed:', error.message);
238
- // Example: "Validation failed for CreateSessionRequest:
239
- // - /model: must be equal to one of the allowed values (allowed values: pro, lite)"
240
- }
241
- }
242
- ```
256
+ ### Serving the Worker
243
257
 
244
- **Validated Operations:**
245
- - `createSession()`: Validates request body against `CreateSessionRequest` schema
246
- - `getSessionStatus()`: Validates session ID format
247
- - `endSession()`: Validates session ID format
248
- - `pollSessionStatus()`: Validates session ID format
258
+ The worker file must be served as a static asset:
249
259
 
250
- You can also use the validator directly for custom validation:
260
+ **Copy to your public directory:**
261
+ ```bash
262
+ cp node_modules/med-scribe-alliance-ts-sdk/dist/worker.bundle.js public/
263
+ ```
251
264
 
252
- ```typescript
253
- import { schemaValidator } from 'scribe-standard-sdk/utils/validator';
265
+ **Or use a CDN blob URL (avoids same-origin restrictions):**
266
+ ```ts
267
+ import { createWorkerBlobUrl } from 'med-scribe-alliance-ts-sdk';
254
268
 
255
- // Validate a session ID
256
- try {
257
- schemaValidator.validateSessionId('ses_abc123');
258
- } catch (error) {
259
- console.error('Invalid session ID:', error.message);
260
- }
261
-
262
- // Validate a create session request
263
- try {
264
- schemaValidator.validateCreateSessionRequest({
265
- templates: ['soap'],
266
- upload_type: 'chunked',
267
- communication_protocol: 'http',
268
- });
269
- } catch (error) {
270
- console.error('Invalid request:', error.message);
271
- }
269
+ const workerUrl = await createWorkerBlobUrl();
270
+ const client = new ScribeClient({
271
+ baseUrl: '...',
272
+ workerScriptUrl: workerUrl,
273
+ });
272
274
  ```
273
275
 
274
- ## Error Handling
275
-
276
- The SDK provides typed error classes for different error scenarios:
277
-
278
- ```typescript
279
- import {
280
- ScribeError,
281
- AuthenticationError,
282
- SessionNotFoundError,
283
- SessionExpiredError,
284
- RateLimitError,
285
- ValidationError,
286
- } from 'scribe-standard-sdk';
287
-
288
- try {
289
- await client.startRecording({ templates: ['soap'] });
290
- } catch (error) {
291
- if (error instanceof ValidationError) {
292
- console.error('Validation error:', error.message);
293
- } else if (error instanceof AuthenticationError) {
294
- console.error('Authentication failed:', error.message);
295
- } else if (error instanceof SessionExpiredError) {
296
- console.error('Session expired:', error.details);
297
- } else if (error instanceof RateLimitError) {
298
- console.error('Rate limited, retry after:', error.details?.retry_after_seconds);
299
- } else if (error instanceof ScribeError) {
300
- console.error('Scribe error:', error.code, error.message);
301
- }
302
- }
276
+ **Or set a global override:**
277
+ ```ts
278
+ window.__MEDSCRIBE_WORKER_URL__ = '/assets/worker.bundle.js';
303
279
  ```
304
280
 
305
- ## Template Output
281
+ If the SharedWorker fails to initialize, the SDK silently falls back to main-thread compression and upload.
306
282
 
307
- When a session completes, the `templates` field contains the extracted data:
283
+ ## Electron / IPC Mode
308
284
 
309
- ```typescript
310
- const status = await client.getOutputStatus(sessionId);
285
+ For Electron apps where network requests must go through the main process:
311
286
 
312
- if (status.templates) {
313
- // Check SOAP template
314
- const soap = status.templates['soap'];
315
- if (soap.status === 'success') {
316
- console.log('SOAP Note:', soap.data);
317
- } else {
318
- console.error('SOAP extraction failed:', soap.error);
319
- }
287
+ ```ts
288
+ import { ScribeClient, TransportMode } from 'med-scribe-alliance-ts-sdk';
320
289
 
321
- // Check medications template
322
- const meds = status.templates['medications'];
323
- if (meds.status === 'success') {
324
- console.log('Medications:', meds.data);
325
- }
326
- }
290
+ const client = new ScribeClient({
291
+ baseUrl: 'https://api.example.com',
292
+ mode: TransportMode.IPC,
293
+ ipcTransport: {
294
+ send: (request) => ipcRenderer.send('scribe-request', request),
295
+ onResponse: (handler) => ipcRenderer.on('scribe-response', (_, res) => handler(res)),
296
+ },
297
+ });
327
298
  ```
328
299
 
329
- ## Discovery Document
300
+ IPC mode always uses main-thread compression (SharedWorker can't access the IPC bridge).
330
301
 
331
- The discovery document provides service capabilities:
302
+ ## Two-Step Flow
332
303
 
333
- ```typescript
334
- const discovery = client.getDiscoveryDocument();
304
+ For apps that need to create the session separately from recording:
335
305
 
336
- // Check supported audio formats
337
- console.log('Audio formats:', discovery.capabilities.audio_formats);
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
+ });
338
313
 
339
- // Check max chunk duration
340
- console.log('Max chunk duration:', discovery.capabilities.max_chunk_duration_seconds);
314
+ if (!session.success) return;
341
315
 
342
- // Check available models
343
- discovery.models.forEach((model) => {
344
- console.log(`Model: ${model.id}`);
345
- console.log(` Languages: ${model.languages.join(', ')}`);
346
- console.log(` Max duration: ${model.max_session_duration_seconds}s`);
347
- console.log(` Features:`, model.features);
316
+ // Step 2: Start recording with the existing session
317
+ await client.startRecordingWithSession(session.data, {
318
+ uploadType: 'chunked',
348
319
  });
349
320
  ```
350
321
 
351
- ## TypeScript Support
352
-
353
- The SDK is written in TypeScript and provides full type definitions:
322
+ ## Building from Source
354
323
 
355
- ```typescript
356
- import type {
357
- ScribeSDKConfig,
358
- RecordingOptions,
359
- CreateSessionResponse,
360
- GetSessionStatusResponse,
361
- TemplatesOutput,
362
- DiscoveryDocument,
363
- } from 'scribe-standard-sdk';
324
+ ```bash
325
+ npm install
326
+ npm run build
364
327
  ```
365
328
 
366
- ## Protocol Compliance
367
-
368
- This SDK implements the following specifications:
329
+ Build output (`dist/`):
369
330
 
370
- - **Spec 04**: Discovery - Service capability discovery via well-known endpoint
371
- - **Spec 06**: Session Lifecycle - Create, get status, and end sessions
372
- - **Spec 09**: Extraction & Response - Template output and transcript handling
373
- - **Spec 11**: Error Handling - Standard error codes and HTTP status mapping
331
+ | File | Description |
332
+ |---|---|
333
+ | `index.mjs` | Minified ESM bundle |
334
+ | `index.d.ts` | Bundled type declarations |
335
+ | `worker.bundle.js` | Self-contained IIFE SharedWorker |
374
336
 
375
337
  ## License
376
338
 
377
339
  MIT
378
-
379
- ## Contributing
380
-
381
- Contributions are welcome! Please open an issue or submit a pull request.
382
-