@crvouga/mockingbird-service-aws-speech 0.1.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 +5 -0
- package/README.md +132 -0
- package/dist/chunk-3DE3INNY.js +3410 -0
- package/dist/chunk-3DE3INNY.js.map +7 -0
- package/dist/chunk-A46XUZ6Z.js +306 -0
- package/dist/chunk-A46XUZ6Z.js.map +7 -0
- package/dist/cli.js +371 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +1066 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +7 -0
- package/dist/server.d.ts +1314 -0
- package/dist/server.js +14 -0
- package/dist/server.js.map +7 -0
- package/package.json +96 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# @crvouga/mockingbird-service-aws-speech
|
|
2
|
+
|
|
3
|
+
Stateful mock of **Amazon Polly** and **Amazon Transcribe** for test suites: Polly
|
|
4
|
+
`SynthesizeSpeech` and `StartSpeechSynthesisStream` (HTTP/2 duplex event stream), Transcribe
|
|
5
|
+
Streaming `StartStreamTranscription` (HTTP/2 duplex) and Transcribe batch
|
|
6
|
+
`StartTranscriptionJob` / `GetTranscriptionJob`. Chat voice turns run end to end with no audio
|
|
7
|
+
leaving the machine: Transcribe "hears" what a test scripts, Polly answers with deterministic
|
|
8
|
+
synthetic audio whose length follows the text.
|
|
9
|
+
|
|
10
|
+
- Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/aws-speech/SUPPORT.md)
|
|
11
|
+
- Proven with the official clients our consumer pins: `@aws-sdk/client-polly`,
|
|
12
|
+
`@aws-sdk/client-transcribe-streaming`, `@aws-sdk/client-transcribe` (3.1132.0), and the MP3
|
|
13
|
+
decoder our backend uses (`mpg123-decoder@1.0.3`).
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -D @crvouga/mockingbird-service-aws-speech
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
|
|
22
|
+
`npx mockingbird-aws-speech serve` (h2c + HTTP/1.1 on one port), `createServer` from
|
|
23
|
+
`./server`, or `createRuntime` with any Fetch server (HTTP/1.1 only).
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx mockingbird-aws-speech serve --port 8797
|
|
29
|
+
export AWS_ENDPOINT_URL_POLLY=http://127.0.0.1:8797
|
|
30
|
+
export AWS_ENDPOINT_URL_TRANSCRIBE_STREAMING=http://127.0.0.1:8797
|
|
31
|
+
export AWS_ENDPOINT_URL_TRANSCRIBE=http://127.0.0.1:8797
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { createServer } from "@crvouga/mockingbird-service-aws-speech/server"
|
|
36
|
+
|
|
37
|
+
const speech = await createServer({ port: 8797 })
|
|
38
|
+
// What the next voice session "hears": partials while audio streams in, then the final.
|
|
39
|
+
await fetch(`${speech.url}/__admin/transcripts`, {
|
|
40
|
+
method: "PUT",
|
|
41
|
+
headers: { "content-type": "application/json" },
|
|
42
|
+
body: JSON.stringify({ match: { any: true }, partials: ["I have", "I have a headache"], final: "I have a headache." }),
|
|
43
|
+
})
|
|
44
|
+
// …the backend's /v2/chatbot/voice turn transcribes, replies, and speaks through Polly…
|
|
45
|
+
await speech.close()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Protocols
|
|
49
|
+
|
|
50
|
+
Polly and Transcribe Streaming clients default to `NodeHttp2Handler`: against an `http://`
|
|
51
|
+
endpoint they speak **h2c** (cleartext HTTP/2, prior knowledge), and their bidirectional
|
|
52
|
+
operations need HTTP/2 duplex. Transcribe batch uses HTTP/1.1. `mockingbird-aws-speech serve`
|
|
53
|
+
and `createServer` sniff each connection and serve both on one port (`serve --config` from
|
|
54
|
+
another service's CLI, and `createRuntime` behind a plain Fetch server, speak HTTP/1.1 only).
|
|
55
|
+
|
|
56
|
+
### Routes
|
|
57
|
+
|
|
58
|
+
| Route | Behaviour |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `POST /v1/speech` | Polly `SynthesizeSpeech`. `OutputFormat: pcm` → raw PCM s16le mono 440 Hz tone (`audio/pcm`, even byte count, `SampleRate` 8000/16000, default 16000); `mp3` → valid MPEG-2 Layer III frames (`audio/mpeg`, 8000/16000/22050/24000). 60 ms of audio per character, byte-identical on every run; `x-amzn-RequestCharacters`. Errors: `ValidationException` (unknown voice or engine), `InvalidSampleRateException`, `TextLengthExceededException` (> 3000 characters). |
|
|
61
|
+
| `POST /v1/synthesisStream` | Polly `StartSpeechSynthesisStream` (HTTP/2 duplex, parameters in `x-amzn-Engine` / `x-amzn-VoiceId` / `x-amzn-OutputFormat` / `x-amzn-SampleRate`; generative engine only). Each `TextEvent` answers `AudioEvent`s (24 kHz MP3 frames mpg123 decodes); `CloseStreamEvent` (or the end of input) answers `StreamClosedEvent {RequestCharacters}`. |
|
|
62
|
+
| `POST /stream-transcription` | Transcribe `StartStreamTranscription` (HTTP/2 duplex). Validates the `x-amzn-transcribe-*` parameters (400 `BadRequestException`), echoes them in the response headers with `x-amzn-transcribe-session-id`. Sends one scripted partial (`IsPartial: true`) per `AudioEvent` received, the rest when the audio ends, then the final result, then closes. Audio bytes are counted, never kept. |
|
|
63
|
+
| `POST /` | Transcribe batch (AWS JSON 1.1, `X-Amz-Target`). `StartTranscriptionJob` → `IN_PROGRESS` (`ConflictException` for a repeated name); `GetTranscriptionJob` → `COMPLETED` once `jobDurationMs` (2 s) have passed on the mock clock, with `Transcript.TranscriptFileUri` = `https://s3.<region>.amazonaws.com/<OutputBucketName>/<OutputKey>`; `BadRequestException` for an unknown job. |
|
|
64
|
+
|
|
65
|
+
### Transcripts (`PUT /__admin/transcripts`)
|
|
66
|
+
|
|
67
|
+
`{match?, partials?, final, times?}` (or `{"transcripts": [...]}`). `match` is
|
|
68
|
+
`{sessionIndex}` (0-based count of streaming sessions in the namespace), `{jobName}` (a batch
|
|
69
|
+
job), or `{any: true}`; an exact match wins over `any`. Unscripted sessions and jobs hear
|
|
70
|
+
`defaultTranscript` (`"Hello."`, `PUT /__admin/settings`) and count as `unscripted`
|
|
71
|
+
(`GET /__admin/transcripts` → `stats`).
|
|
72
|
+
|
|
73
|
+
### Admin (beyond the standard contract)
|
|
74
|
+
|
|
75
|
+
| Route | Effect |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| `PUT` / `POST` / `GET` / `DELETE /__admin/transcripts` | Replace, add, list (with `stats`), remove (`?id=`). |
|
|
78
|
+
| `GET /__admin/jobs` | Batch jobs (advanced on the mock clock). |
|
|
79
|
+
| `POST /__admin/jobs/:name/complete` | Complete now, optionally with `{transcript}`. |
|
|
80
|
+
| `POST /__admin/jobs/:name/fail` | Fail with `{reason}` (`FailureReason`). |
|
|
81
|
+
| `GET /__admin/jobs/:name/transcript` | The transcript JSON Transcribe would write to S3. |
|
|
82
|
+
| `GET /__admin/speech` | Metadata of every synthesis / transcription (voice, engine, format, characters, bytes, script id — never text). |
|
|
83
|
+
| `GET/PUT /__admin/settings` | `defaultTranscript`, `jobDurationMs`. |
|
|
84
|
+
|
|
85
|
+
With `--s3-endpoint <url>` (`transcriptStore` in code) a completed job's transcript JSON is also
|
|
86
|
+
written to that S3 (the stack's s3rver) at `OutputBucketName/OutputKey`, so the app's own
|
|
87
|
+
`GetObject` on `TranscriptFileUri` finds it.
|
|
88
|
+
|
|
89
|
+
Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`):
|
|
90
|
+
`polly_throttling` (429 `ThrottlingException`), `polly_service_failure` (500),
|
|
91
|
+
`polly_stream_throttling` / `polly_stream_validation` / `polly_stream_quota` (an exception
|
|
92
|
+
event before any audio: our adapter falls back to `SynthesizeSpeech`), `polly_stream_failure`
|
|
93
|
+
(`ServiceFailureException` after audio), `polly_stream_no_close` (no `StreamClosedEvent`),
|
|
94
|
+
`transcribe_bad_request` (400), `transcribe_limit_exceeded` (429), `transcribe_service_unavailable`
|
|
95
|
+
(503), `transcribe_mid_stream_failure` (`InternalFailureException` after the first partial),
|
|
96
|
+
`transcribe_job_limit` (`LimitExceededException`), `transcribe_job_failed` (next job `FAILED`).
|
|
97
|
+
|
|
98
|
+
### Namespaces
|
|
99
|
+
|
|
100
|
+
`x-mockingbird-namespace`, a `/ns/<name>` prefix on the endpoint URL, or the SigV4 access key id:
|
|
101
|
+
`PUT /__admin/credentials {"credentials": {"<AWS_ACCESS_KEY_ID>": "<namespace>"}}`. The journal
|
|
102
|
+
records voice, engine, format, character counts and script ids — never text.
|
|
103
|
+
|
|
104
|
+
### Deliberately not modelled
|
|
105
|
+
|
|
106
|
+
- Real speech: Polly audio is a tone (PCM) or silent-but-valid MP3 frames (a tone needs a real
|
|
107
|
+
MP3 encoder); Transcribe never listens — words come only from scripts.
|
|
108
|
+
- `ogg_vorbis`, `ogg_opus`, `mulaw`, `alaw` and speech-mark (`json`) output, SSML semantics (tags
|
|
109
|
+
are stripped for length), lexicons, per-voice engine availability.
|
|
110
|
+
- Transcribe language identification, speaker labels, custom vocabularies (accepted and echoed,
|
|
111
|
+
not applied), Call Analytics and Medical variants, batch jobs reading their media from S3.
|
|
112
|
+
- SigV4 signatures and event signatures are not verified; the access key id only selects a
|
|
113
|
+
namespace.
|
|
114
|
+
|
|
115
|
+
## API
|
|
116
|
+
|
|
117
|
+
| Export | Kind | Description |
|
|
118
|
+
| --- | --- | --- |
|
|
119
|
+
| `SpeechAPI` | class | The in-process mock: `fetch`, `reset`, `jobs()`, `complete(name, transcript?)`, `fail(name, reason)`, `transcriptDocument(job)`, `speechLog()`, `stats()`. Options: `sqlite`, `now`, `namespace`, `settings`, `transcripts`, `transcriptStore`. |
|
|
120
|
+
| `createRuntime` | function | The mock with the full service contract. Options: `settings`, `transcripts`, `transcriptStore`, `clock`, `seed`, `adminKey`, `onLog`, `sqlite`. |
|
|
121
|
+
| `SPEECH_PRESETS` | object | Every named fault preset. |
|
|
122
|
+
| `SPEECH_NAMESPACE` | string | The service name, `"aws-speech"`. |
|
|
123
|
+
| `POLLY_VOICES` | array | Every Polly voice id the mock accepts. |
|
|
124
|
+
| `speechError` | function | A restJson1 error response (`x-amzn-ErrorType` + `{message}`). |
|
|
125
|
+
| `accessKeyCredential` | function | The SigV4 access key id of a request (how credentials map to namespaces). |
|
|
126
|
+
| `pcmTone`, `mp3Audio`, `mp3Frame`, `durationFor`, `MS_PER_CHARACTER`, `MP3_SAMPLE_RATES`, `MP3_SAMPLES_PER_FRAME` | audio | The deterministic synthetic audio. |
|
|
127
|
+
| `DEFAULT_SETTINGS` | value | Default settings. |
|
|
128
|
+
| `encodeMessage`, `decodeMessage`, `FrameReader`, `readFrames`, `eventFrame`, `exceptionFrame`, `unwrapSigned`, `crc32`, `EventStreamError` | codec | The event-stream codec (exact prelude, headers and CRC32s), in both directions. |
|
|
129
|
+
| `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
|
|
130
|
+
| `createServer`, `serveTarget`, `DEFAULT_PORT`, `listenH2c` (`./server`) | Node | Serve h2c + HTTP/1.1 on one port; the `serve` CLI target; port 8797; the dual-protocol listener. |
|
|
131
|
+
|
|
132
|
+
Part of [mockingbird](https://github.com/crvouga/mockingbird).
|