@hartren/assistant-js 0.0.0-stage → 2.3.1
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 +140 -0
- package/LICENSE +21 -0
- package/README.md +273 -2
- package/THIRD_PARTY_NOTICES.md +917 -0
- package/dist/CopilotAgent/AuthenticationService.d.ts +47 -0
- package/dist/CopilotAgent/CommandsManager.d.ts +20 -0
- package/dist/CopilotAgent/CopilotAgent.d.ts +148 -0
- package/dist/CopilotAgent/CopilotProxy.d.ts +17 -0
- package/dist/CopilotAgent/UserSessionService.d.ts +9 -0
- package/dist/CopilotAgent/index.d.ts +2 -0
- package/dist/CustomSpeechRecognition/CustomSpeechRecognition.d.ts +93 -0
- package/dist/MicrophoneAccess/MicrophoneAccess.d.ts +65 -0
- package/dist/SpeakingService/SpeakingService.d.ts +14 -0
- package/dist/SpeakingService/index.d.ts +2 -0
- package/dist/SpeechRecognitionService/ResultsGroupingService.d.ts +44 -0
- package/dist/SpeechRecognitionService/SpeechRecognitionService.d.ts +108 -0
- package/dist/SpeechRecognitionService/index.d.ts +2 -0
- package/dist/SpeechRecognitionService/speechConfig.d.ts +96 -0
- package/dist/debugLog.d.ts +9 -0
- package/dist/enums.d.ts +42 -0
- package/dist/httpError.d.ts +9 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +2 -0
- package/dist/index.js.LICENSE.txt +5 -0
- package/dist/types.d.ts +57 -0
- package/package.json +54 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.3.1 (unreleased)
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- First version for the public npm registry (`registry.npmjs.org`). 2.3.0 is skipped because tag
|
|
8
|
+
`v2.3.0` already exists in the repository. Built from source in CI; the pack's SHA-256 and file list
|
|
9
|
+
are kept as a CI artifact. Third-party notices are included.
|
|
10
|
+
- `package.json` no longer has `homepage` or `bugs` fields.
|
|
11
|
+
|
|
12
|
+
## 2.3.0 (2026-11-02)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **Renamed to `@hartren/assistant-js`** (GitHub Packages). Replace `@hartren/copilotagent-js` with
|
|
17
|
+
`@hartren/assistant-js` in your `package.json` and imports. The API is unchanged. Existing
|
|
18
|
+
`@hartren/copilotagent-js` versions stay published unchanged.
|
|
19
|
+
|
|
20
|
+
## 2.2.0 (2026-10-25)
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **`browserTranscriptionFallback`** (boolean, default `false`). When `true`, dictation switches to
|
|
25
|
+
the browser's `SpeechRecognition` when the hosted transcription socket cannot connect or drops
|
|
26
|
+
mid-session while the browser is online, and returns to the hosted service on `online` or at the
|
|
27
|
+
next `startListening()`. Offline behaves as without the option (cloud Web Speech cannot work
|
|
28
|
+
offline either): no switch, and a fallback already running is paused, with no restarts and no
|
|
29
|
+
"network hiccup" errors, until `online`. New `onCopilotError` codes `transcription-fallback` and
|
|
30
|
+
`transcription-restored` (both `final: false`), and `agent.isUsingBrowserTranscriptionFallback()`.
|
|
31
|
+
This restores, behind an opt-in, the automatic Web Speech fallback removed in 2.0.0 (ce9e), now
|
|
32
|
+
with hand-back.
|
|
33
|
+
**Warning:** when on, dictation audio may be processed by the browser vendor's speech service (no
|
|
34
|
+
BAA). Only enable it where that is acceptable, never with PHI. With the default (`false`),
|
|
35
|
+
`SpeechRecognition` is never constructed and nothing changes.
|
|
36
|
+
|
|
37
|
+
## 2.1.0 (2026-10-24)
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- **Renamed to `@hartren/copilotagent-js` and published to GitHub Packages** (`npm.pkg.github.com`)
|
|
42
|
+
instead of public npm. Replace `@radaid/copilotagent-js` with `@hartren/copilotagent-js` in
|
|
43
|
+
`package.json` and imports, and add the `@hartren` registry line to `.npmrc` (see the README). The
|
|
44
|
+
API is unchanged. Existing `@radaid/copilotagent-js` versions (up to 2.0.1) stay on public npm.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- Legacy PAT mode: when `/token/refresh` fails (expired, used or revoked refresh token, or a server
|
|
49
|
+
error), the library now falls back to one PAT re-exchange, shared by concurrent requests, instead
|
|
50
|
+
of failing every request until reload.
|
|
51
|
+
- Errors are logged by name or code only (`errorLabel`), never with their message or response text.
|
|
52
|
+
- HTTP failures throw `HttpError` (exported; `status`, `code` such as `HTTP_500`) and are logged by
|
|
53
|
+
that code. The error message is unchanged.
|
|
54
|
+
- Browser speech start/restart errors log only the error name, and a closed speech socket logs only
|
|
55
|
+
its close code; the full error and the close reason are logged only with debug logging on.
|
|
56
|
+
- Concurrent legacy token refreshes share one request, cleared once when it settles.
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- **Host token provider is the supported way to authenticate.** Pass `getAccessToken` (for example
|
|
61
|
+
`() => auth.getAccessToken()` from `@hartren/auth`) and, optionally, `onUnauthorized`
|
|
62
|
+
(`(rejectedToken) => auth.handleUnauthorized(rejectedToken)`). The provider is asked on every API
|
|
63
|
+
request and for the speech socket, and no copy of the token is kept. On a `401` the request is
|
|
64
|
+
retried once, only with a different token from `onUnauthorized` (or, without it, from the provider).
|
|
65
|
+
New exported types `AccessTokenProvider` and `UnauthorizedHandler`.
|
|
66
|
+
- With a provider and no PAT, a missing token rejects with `Not signed in` instead of falling through
|
|
67
|
+
to the PAT exchange.
|
|
68
|
+
|
|
69
|
+
### Deprecated
|
|
70
|
+
|
|
71
|
+
- `pat`, `setUserId`, the browser PAT exchange (`/token/exchange`) and the legacy refresh
|
|
72
|
+
(`/token/refresh`). They still work when a PAT is passed and the provider is missing or returns
|
|
73
|
+
nothing, and will be removed in a future major version.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- `setUserId` no longer starts a PAT exchange when there is no PAT (or when a provider is set), so it
|
|
78
|
+
no longer sends `Authorization: PAT undefined` or leaves an unhandled rejection.
|
|
79
|
+
- In the PAT path, a `401` now drops the cached token and refreshes before the single retry (it used
|
|
80
|
+
to retry with the same token).
|
|
81
|
+
|
|
82
|
+
## 2.0.1 (2026-10-18)
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
|
|
86
|
+
- **No user or AI text in the browser console by default.** Dictated transcripts, prompts, assistant
|
|
87
|
+
replies and raw API responses were logged with `console.log` / `console.info`. That text can be PHI.
|
|
88
|
+
These logs are now off unless a host turns on debug logging: `new CopilotAgent({ debug: true })` or
|
|
89
|
+
`setCopilotDebugLogging(true)` (new exports, with `isCopilotDebugLogging()`). Use it for local
|
|
90
|
+
debugging only. The gated logs are in `CopilotAgent` (API response, combined message, AI reply,
|
|
91
|
+
recognized text) and `ResultsGroupingService` (each transcript and the grouped results).
|
|
92
|
+
- A browser speech-synthesis error logs only its error code (for example `interrupted`), not the
|
|
93
|
+
event: the event's `utterance.text` is the AI reply. A failed tool logs only the error name or code,
|
|
94
|
+
not its message, which can echo tool arguments. The full details are logged only with debug logging on.
|
|
95
|
+
- `package.json` now has real `repository`, `homepage` and `bugs` fields
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
|
|
99
|
+
- The package no longer ships `src/`. Only `dist/` is published, plus `CHANGELOG.md`, `README.md`,
|
|
100
|
+
`LICENSE` and `package.json`. Nothing imports from `src/`: `main` and `types` point into `dist/`.
|
|
101
|
+
|
|
102
|
+
## 2.0.0 (2026-10-18)
|
|
103
|
+
|
|
104
|
+
### BREAKING CHANGE
|
|
105
|
+
|
|
106
|
+
- **The speech token is now sent as a WebSocket subprotocol, never in the URL.** The speech sockets
|
|
107
|
+
(`/ws/speech/transcribe` and `/ws/speech/speak`) offer `hartren.speech.v1` and `bearer.<token>` in
|
|
108
|
+
the `Sec-WebSocket-Protocol` header.
|
|
109
|
+
- **This needs a backend that echoes `hartren.speech.v1`.** Against an
|
|
110
|
+
older backend, Chrome refuses the socket and dictation and voice replies fail. Don't upgrade a host that talks to such a
|
|
111
|
+
backend until the backend is updated.
|
|
112
|
+
- **Deepgram (the hosted Hartren API speech service) is the default, and browser speech is opt-in
|
|
113
|
+
only.** The SDK no longer falls back to the browser's Web Speech (`SpeechRecognition` /
|
|
114
|
+
`speechSynthesis`) under any failure. A host gets it only with `speechProvider: 'browser'`. Don't use
|
|
115
|
+
that setting with PHI: browser speech can send audio and text to the browser or OS vendor.
|
|
116
|
+
|
|
117
|
+
### Changed
|
|
118
|
+
|
|
119
|
+
- The speech socket URL now defaults to the `copilotApi` origin (`https://api.example.com/api` →
|
|
120
|
+
`wss://api.example.com`). A relative `copilotApi` resolves against `window.location.origin`.
|
|
121
|
+
- Hosted speech failures reconnect with exponential backoff (`speechRetry`, default 3 retries,
|
|
122
|
+
1 s base, 8 s cap). Each failure is reported through `onCopilotError` with a `code` from
|
|
123
|
+
`SpeechErrorCodes`.
|
|
124
|
+
- The token is sent only over `wss://`, or plain `ws://` to `localhost`, `127.0.0.1` or `[::1]`. Any
|
|
125
|
+
other `ws://` URL is refused before the token is read (`transcription-not-configured` /
|
|
126
|
+
`tts-not-configured`, no retry).
|
|
127
|
+
- A token that cannot be sent as a subprotocol fails once with `transcription-failed`,
|
|
128
|
+
`reason: 'invalid-token'`, with no retry.
|
|
129
|
+
- Microphone errors (ported from #30): `audio-setup-failed` with `reason` (for example `mic-denied`,
|
|
130
|
+
`mic-dismissed`, `mic-policy-blocked`, `mic-no-device`, `vad-asset-failed`), `detail` (the browser
|
|
131
|
+
error name) and `environment`. A session failure (`reason: 'session-failed'`) is retried.
|
|
132
|
+
- The token and token-bearing URLs are never logged.
|
|
133
|
+
- New exports: `speechProvider`, `SpeechErrorCodes`, `DEFAULT_SPEECH_RETRY`, `SPEECH_WS_PROTOCOL`,
|
|
134
|
+
`SPEECH_RECOGNITION_FAILURES`, and the types `CopilotError`, `SpeechProvider`, `SpeechRetryOptions`,
|
|
135
|
+
`SpeechErrorCode`, `SpeechRecognitionFailure`, `MicrophoneEnvironment`.
|
|
136
|
+
|
|
137
|
+
### Notes
|
|
138
|
+
|
|
139
|
+
- 1.1.10 to 1.1.16 fell back to browser speech when the hosted socket failed. Don't roll back to
|
|
140
|
+
them in production.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hartren LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,274 @@
|
|
|
1
|
-
# Temporary Holding Version
|
|
2
1
|
|
|
3
|
-
|
|
2
|
+
# @hartren/assistant-js
|
|
3
|
+
|
|
4
|
+
Browser client for the Hartren Assistant: dictation (speech socket) and the AI requests behind the Assistant and Reporting panels in the Hartren viewer and report builder.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
Published on the public npm registry:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @hartren/assistant-js
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Previously published as `@hartren/copilotagent-js` (and before that `@radaid/copilotagent-js`). Use `@hartren/assistant-js` for 2.3.0 and later.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Development
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# from a checkout of this repository
|
|
21
|
+
npm ci
|
|
22
|
+
npm test
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Build
|
|
26
|
+
To compile and bundle the package into a single JavaScript file:
|
|
27
|
+
```bash
|
|
28
|
+
yarn run build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The output will be generated in the `dist/` directory.
|
|
32
|
+
|
|
33
|
+
## Directory Structure
|
|
34
|
+
```
|
|
35
|
+
/
|
|
36
|
+
├── src/ # Source files
|
|
37
|
+
│ └── index.ts # Entry point for the package
|
|
38
|
+
├── dist/ # Bundled output
|
|
39
|
+
├── webpack.config.js # Webpack configuration
|
|
40
|
+
├── package.json # Package configuration
|
|
41
|
+
├── tsconfig.json # TypeScript configuration
|
|
42
|
+
├── README.md # This README file
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
```ts
|
|
47
|
+
import CopilotAgent from '@hartren/assistant-js';
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Scripts
|
|
51
|
+
- **Build**: Compile TypeScript and bundle files:
|
|
52
|
+
```bash
|
|
53
|
+
yarn run build
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## How to Configure Package Locally
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
1. **Unlink the package** (if it's already linked)
|
|
61
|
+
```bash
|
|
62
|
+
yarn unlink @hartren/assistant-js
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
2. **Link the package**
|
|
66
|
+
```bash
|
|
67
|
+
yarn dev
|
|
68
|
+
yarn link
|
|
69
|
+
yarn watch
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
3. **Go to another repo** where you want to configure this package
|
|
73
|
+
|
|
74
|
+
4. **Remove the package** from `package.json`
|
|
75
|
+
|
|
76
|
+
5. **Link the package** in the new repo
|
|
77
|
+
```bash
|
|
78
|
+
yarn link @hartren/assistant-js
|
|
79
|
+
yarn dev
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Releasing
|
|
83
|
+
|
|
84
|
+
1. Merge a PR that sets the new `version` in `package.json` and dates its `CHANGELOG.md` entry.
|
|
85
|
+
2. A maintainer runs **Actions > Publish > Run workflow** on main with that version.
|
|
86
|
+
3. The workflow checks that the version matches `package.json`, runs the tests and the build, and
|
|
87
|
+
publishes to npmjs (`--access public`) with npm Trusted Publishing or the `NPM_TOKEN` environment secret in `npm-publish`.
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## Debug logging (off by default)
|
|
94
|
+
|
|
95
|
+
The agent never writes dictated text, prompts, AI replies or raw API responses to the browser
|
|
96
|
+
console unless you turn on debug logging. That text can be PHI, so do this for local debugging only:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
new CopilotAgent({ ...options, debug: true });
|
|
100
|
+
// or, at any time:
|
|
101
|
+
import { setCopilotDebugLogging } from '@hartren/assistant-js';
|
|
102
|
+
setCopilotDebugLogging(true);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Authentication
|
|
106
|
+
|
|
107
|
+
Pass the user's token through a provider. The agent asks for it on every API request and for the
|
|
108
|
+
speech socket, and keeps no copy. With `@hartren/auth`:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
new CopilotAgent({
|
|
112
|
+
copilotApi: 'https://api.hartren.com/api',
|
|
113
|
+
commands,
|
|
114
|
+
getAccessToken: () => auth.getAccessToken(), // resolve null when signed out
|
|
115
|
+
onUnauthorized: rejected => auth.handleUnauthorized(rejected), // optional: a different token to retry once
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
On a `401` the request is retried once, and only with a different token. Without `onUnauthorized` the
|
|
120
|
+
agent asks `getAccessToken` again.
|
|
121
|
+
|
|
122
|
+
`pat` and `setUserId` (browser PAT exchange plus `/token/refresh`) are deprecated. They still work
|
|
123
|
+
when a PAT is passed and the provider is missing or returns nothing, and will be removed in a future major version.
|
|
124
|
+
|
|
125
|
+
## Speech (dictation and voice replies)
|
|
126
|
+
|
|
127
|
+
> **BREAKING CHANGE in 2.0.0.** The speech token is sent as the WebSocket subprotocol
|
|
128
|
+
> `bearer.<token>`, next to `hartren.speech.v1`. This needs a backend that echoes
|
|
129
|
+
> `hartren.speech.v1` (the current Hartren API does). Against older backends, Chrome refuses the socket and dictation fails. Deepgram (hosted speech) is the
|
|
130
|
+
> default, and browser speech is opt-in only (`speechProvider: 'browser'`). See
|
|
131
|
+
> [CHANGELOG.md](CHANGELOG.md).
|
|
132
|
+
|
|
133
|
+
Dictation audio and spoken replies can contain PHI. By default the agent uses **only** the hosted
|
|
134
|
+
Hartren API speech service (Deepgram): `/ws/speech/transcribe` for dictation and `/ws/speech/speak`
|
|
135
|
+
for voice replies. Browsers cannot set headers on a WebSocket, so the user's access token is sent
|
|
136
|
+
as a WebSocket subprotocol: the socket offers `hartren.speech.v1` and `bearer.<token>` in the
|
|
137
|
+
`Sec-WebSocket-Protocol` header, and the server echoes only `hartren.speech.v1`. The token is never
|
|
138
|
+
put in the URL, so it cannot end up in access logs. This needs a Hartren API version that reads and
|
|
139
|
+
echoes the subprotocol (Chrome fails the handshake when a server does not echo one).
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
new CopilotAgent({
|
|
143
|
+
copilotApi: 'https://api.hartren.com/api',
|
|
144
|
+
getAccessToken,
|
|
145
|
+
// Optional. Defaults to the copilotApi origin with a ws/wss scheme (wss://api.hartren.com).
|
|
146
|
+
transcribeWssUrl: 'wss://api.hartren.com',
|
|
147
|
+
// Optional reconnect policy for the transcription socket (these are the defaults).
|
|
148
|
+
speechRetry: { maxRetries: 3, baseDelayMs: 1000, maxDelayMs: 8000 },
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
If the hosted service fails (handshake rejected, socket dropped, missing token or session, no URL),
|
|
153
|
+
the agent never falls back to browser speech by default (see
|
|
154
|
+
[Browser transcription fallback](#browser-transcription-fallback-opt-in) for the opt-in). Instead it reports the failure through
|
|
155
|
+
`onCopilotError` and reconnects with exponential backoff. Each reconnect raises
|
|
156
|
+
`code: 'transcription-retrying'` with `final: false`. When the retries run out, it raises
|
|
157
|
+
`code: 'transcription-failed'` with `final: true` and stops dictation. If hosted TTS fails it raises
|
|
158
|
+
`code: 'tts-failed'`; with no hosted TTS configured it raises `code: 'tts-not-configured'`. If the
|
|
159
|
+
transcription service sends an error on an open socket, it raises `code: 'transcription-error'` with
|
|
160
|
+
`final: false`. The full list is exported as `SpeechErrorCodes`.
|
|
161
|
+
|
|
162
|
+
**Log `code`, not `message`, from `onCopilotError`.** Most messages are fixed strings, but for
|
|
163
|
+
`transcription-error` the `message` is the server's own text, which can echo what was dictated. The
|
|
164
|
+
agent itself prints only a fixed line (the server text only with `debug: true`). Type the listener with
|
|
165
|
+
the exported `CopilotError`:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import type { CopilotError } from '@hartren/assistant-js';
|
|
169
|
+
agent.onCopilotError((e: CopilotError) => console.warn('[Copilot] error:', e.code ?? 'unknown'));
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**The token is only sent over `wss://`.** A plain `ws://` speech URL is accepted only for
|
|
173
|
+
`localhost`, `127.0.0.1` or `[::1]` (local development). Any other `ws://` URL, including one derived
|
|
174
|
+
from an `http://` copilotApi, is refused before the token is read: no socket is opened, and the agent
|
|
175
|
+
raises `transcription-not-configured` (or `tts-not-configured` for voice replies) with no retry.
|
|
176
|
+
|
|
177
|
+
**Microphone problems** raise `code: 'audio-setup-failed'` with no retry. `reason` names the cause
|
|
178
|
+
(`mic-denied`, `mic-dismissed`, `mic-os-blocked`, `mic-policy-blocked`, `mic-no-device`, `mic-busy`,
|
|
179
|
+
`mic-constraints`, `mic-unsupported`, `mic-insecure-context`, `mic-failed`, `vad-asset-failed`),
|
|
180
|
+
`detail` is the browser's error name (never its raw message), and `environment` says what the browser
|
|
181
|
+
reported about microphone access. The messages are exported as `SPEECH_RECOGNITION_FAILURES`. A
|
|
182
|
+
session that cannot be started is reported as `reason: 'session-failed'` and is retried like a
|
|
183
|
+
connection failure.
|
|
184
|
+
|
|
185
|
+
**Browser speech is opt-in only.** `speechProvider: 'browser'` uses the browser's `SpeechRecognition`
|
|
186
|
+
and `speechSynthesis`. Those can send audio or text to the browser or OS vendor (for example Google
|
|
187
|
+
or Microsoft), and there is no BAA with them. Do not use this setting with PHI.
|
|
188
|
+
|
|
189
|
+
### Browser transcription fallback (opt-in)
|
|
190
|
+
|
|
191
|
+
> **Warning.** When `browserTranscriptionFallback` is on, dictation audio may be processed by the
|
|
192
|
+
> browser vendor's speech service (for example Google for Chrome, Microsoft for Edge). There is no BAA
|
|
193
|
+
> with them. Only enable it where that is acceptable, never where dictation can contain PHI. Hartren's
|
|
194
|
+
> own deployments leave it off.
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
new CopilotAgent({
|
|
198
|
+
copilotApi: 'https://api.example.com/api',
|
|
199
|
+
getAccessToken,
|
|
200
|
+
browserTranscriptionFallback: true, // default false
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`browserTranscriptionFallback` (boolean, default `false`) keeps dictation going on a spotty
|
|
205
|
+
connection by switching to the browser's `SpeechRecognition` / `webkitSpeechRecognition`. Only the
|
|
206
|
+
boolean `true` turns it on; any other value (including the string `'true'`) leaves it off. It applies
|
|
207
|
+
to the hosted (default) provider and to dictation only; voice replies stay hosted-only.
|
|
208
|
+
|
|
209
|
+
- **Off (default):** nothing changes. `SpeechRecognition` is never constructed, and hosted failures
|
|
210
|
+
retry and then raise `transcription-failed` as described above.
|
|
211
|
+
- **When it kicks in:** while dictation is running, on a hosted connection failure that would
|
|
212
|
+
otherwise be retried: the socket cannot be prepared or opened, the handshake is refused, the socket
|
|
213
|
+
drops mid-session, or the session cannot be started. The browser recognizer is created on the first
|
|
214
|
+
switch, not before. The agent raises `code: 'transcription-fallback'` (`final: false`, `reason`
|
|
215
|
+
such as `socket-closed` or `connect-failed`)
|
|
216
|
+
through `onCopilotError`, and `agent.isUsingBrowserTranscriptionFallback()` returns `true`.
|
|
217
|
+
- **Not for configuration or microphone errors:** a refused `ws://` URL, an unusable token and
|
|
218
|
+
`audio-setup-failed` are reported as before, with no fallback.
|
|
219
|
+
- **Offline behaves as without the option.** Chrome and Edge run Web Speech in the cloud, so it
|
|
220
|
+
cannot work offline either. The `offline` event never switches, and a hosted failure while
|
|
221
|
+
`navigator.onLine` is `false` retries and then raises `transcription-failed` as usual.
|
|
222
|
+
- **Going offline while the fallback is active pauses it.** The browser recognizer's `network`
|
|
223
|
+
errors are expected then, so it is not restarted and the "network hiccup" error is not raised
|
|
224
|
+
(one console warning, fixed text). Dictation resumes on the hosted service on `online`. While
|
|
225
|
+
online, a `network` error from browser speech keeps the normal restart-and-report behavior.
|
|
226
|
+
- **Handing back:** when the browser fires `online` during the dictation, the agent stops browser
|
|
227
|
+
speech, reconnects to the hosted service and raises `code: 'transcription-restored'`
|
|
228
|
+
(`final: false`). Every new `startListening()` tries the hosted service first. If the hosted
|
|
229
|
+
service fails again, it falls back again.
|
|
230
|
+
- **Unsupported browsers** (no `SpeechRecognition`, e.g. Firefox): no error from the fallback; the
|
|
231
|
+
hosted retries and the final `transcription-failed` happen as if the option were off.
|
|
232
|
+
- Browser speech needs a network connection itself (Chrome sends audio to Google), so the fallback
|
|
233
|
+
helps when the hosted service or its route is unreachable or flaky but the browser vendor is not.
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
237
|
+
|
|
238
|
+
## Authentication and the speech socket
|
|
239
|
+
|
|
240
|
+
- Get the user's token with `@hartren/auth` (which uses `POST /api/token/user-exchange`; your backend
|
|
241
|
+
does the first exchange with its integration token, the browser only refreshes) and pass
|
|
242
|
+
`getAccessToken: () => auth.getAccessToken()` and `onUnauthorized: (t) => auth.handleUnauthorized(t)`.
|
|
243
|
+
Don't use the legacy `/api/token/exchange` with `userId`; it is denied by default (`403`).
|
|
244
|
+
- Dictation opens `wss://{api host}/ws/speech/transcribe` offering two WebSocket subprotocols:
|
|
245
|
+
`hartren.speech.v1` and `bearer.<access token>`. The server echoes only `hartren.speech.v1`.
|
|
246
|
+
A handshake without a valid token gets `401`; a user who hasn't accepted the Terms gets
|
|
247
|
+
`403 tos_required`, and a user with report generation disabled gets `403`, both before the socket opens.
|
|
248
|
+
|
|
249
|
+
## Server responses to handle (Hartren API)
|
|
250
|
+
|
|
251
|
+
Every API error body is JSON. Token endpoints use `{ "error", "error_description" }`; the other
|
|
252
|
+
endpoints use `application/problem+json` (`title`, `detail`, `status`).
|
|
253
|
+
|
|
254
|
+
| Status | When | What the client should do |
|
|
255
|
+
|---|---|---|
|
|
256
|
+
| `204 No Content` | Success with no body (for example deletes and some updates) | Treat as success; don't parse a body |
|
|
257
|
+
| `401` | Token missing, expired or revoked (and the speech handshake before the socket opens) | Get a fresh token once (`onUnauthorized` / `handleUnauthorized`), retry once, then sign the user in again |
|
|
258
|
+
| `403 tos_required` | The user hasn't accepted the current Terms of Use in this tenant. Returned on every API call and on the speech handshake | Read `GET /api/legal/status`, show the Terms (`GET /api/legal/tos`), then `POST /api/legal/accept` with the version. Don't retry the original call until then |
|
|
259
|
+
| `403` "Report generation is disabled for this user" | The organization turned report generation (and dictation) off for this user | Show the message; don't retry |
|
|
260
|
+
| `403` "Report limit reached for your plan" | The user's plan limit is used up | Show the message; don't retry |
|
|
261
|
+
| `403 access_denied` / `unauthorized_client` | `user-exchange`: the user can't get a token through your integration, or wrong token type / IP | Don't retry; check the user mapping or integration token |
|
|
262
|
+
| `409 tos_version_stale` | `POST /api/legal/accept` with an old version: the Terms changed | Fetch the current Terms, show them again, accept the new version |
|
|
263
|
+
| `409` (other endpoints) | A conflicting change (for example the item already exists or changed concurrently) | Reload, then retry once if it still applies |
|
|
264
|
+
| `429 slow_down` | Too many requests | Wait `Retry-After` seconds |
|
|
265
|
+
| `503 legal_storage_unavailable` | Terms status couldn't be read or written (fails closed) | Retry later |
|
|
266
|
+
|
|
267
|
+
Never retry `400`, `401` (after the one refresh), `403` or `409` in a loop.
|
|
268
|
+
|
|
269
|
+
### Protected npm publishing
|
|
270
|
+
|
|
271
|
+
Store `NPM_TOKEN` as an environment secret in the main-only `npm-publish` GitHub
|
|
272
|
+
environment, never as a repository secret. Configure the npm trusted publisher
|
|
273
|
+
with environment name `npm-publish` (alongside this repository and `publish.yml`).
|
|
274
|
+
The first publish requires the short-lived token; subsequent publishing can use OIDC.
|