CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# Changelog
## v0.12 - Unreleased
### Added
- OpenAI-compatible layer: `POST /v1/audio/speech`, `GET /v1/models`, `GET /v1/audio/voices`.
- `/v1` strong auth before body/route validation; OpenAI-shaped pre-stream errors with `X-Request-ID`; `Retry-After` on `/v1` `429`.
- Field mapping `input`→text, `model`→engine; default engine `edge`; seven engine-aware aliases including OpenClaw's default `coral`.
- OpenAI binary output formats `mp3`, `opus`, `aac`, `flac`, `wav`, and raw 24 kHz mono s16le `pcm`, with matching MIME and filename extensions.
- Explicit request boundaries: binary `stream_format=audio` only; unsupported SSE and non-empty `instructions`, `lang_code`, `lang`, `language`, or guide-only `format` return `400` instead of being silently ignored.
- Offline contract tests in `tests/test_openai_compat.py`.
### Fixed
- Bound declared and chunked HTTP request bodies, while preserving `/v1` authentication precedence: missing or invalid credentials return `401` before an oversized authenticated request can return `413`.
- Cancel and await a pending HTTP response-body child write when its request owner is cancelled, preventing orphaned ASGI send tasks while preserving downstream exceptions and write-timeout errors.
- Close the same-tick Kokoro waiter-admission race and keep speculative Kokoro prefetch out of the normal waiter budget by failing it fast when no inference slot is immediately available.
- Use one static Unicode 15.0 Han table in the Python backend and browser router, so Python 3.10/3.12 Unicode database differences cannot route assigned Han differently.
- Reject Kokoro voice/text language mismatches on `/v1/audio/speech` before encoder startup, preventing silent partial success when monolingual filtering would otherwise drop CJK or Latin content.
- Reject Unicode letter scripts outside the selected Kokoro voice's Han/Latin script before `/v1` synthesis; neutral punctuation and numbers remain accepted.
- Preserve the primary Edge synthesis/queue exception when stream cleanup also fails, while still reaping the decoder and releasing its ffmpeg slot.
- Preserve Unicode word-internal multiplication stars and exponent expressions during Markdown cleanup while retaining bounded bold/italic removal.
- Return stable non-null `error.code` values for authenticated `/v1` route failures: `not_found` for `404` and `method_not_allowed` for `405`.
- Treat the Bearer authentication scheme case-insensitively and include the required nullable `param` member in OpenAI-shaped errors.
- Require both submitted source audio and non-empty encoder output before committing `200`; preserve the prefetched output exactly once and reject encoder EOF/read errors or observed non-zero exits before handoff.
- Stop waiting indefinitely when encoder stdout has already failed, even if the global synthesis timeout is disabled; keep same-tick feed/upstream errors correctly classified.
- Distinguish local ffmpeg stdin failures (`500`) from Edge upstream failures (`502`).
- Abort an already-started transfer when a late feed failure, premature EOF, observed non-zero encoder exit, or stream timeout is detected, instead of normally completing a known partial body.
- Keep a cancelled in-flight Kokoro worker inside the synthesis limit until the real thread exits, and wake queued Kokoro requests directly on WebSocket cancellation without dispatching the pipeline or leaking a permit.
- Continue controlled process reaping and release the ffmpeg limiter exactly once when `kill()` raises a non-race `OSError`, instead of skipping `wait()` and the release path; preserve the original stream-timeout or owner-cancellation exception when an earlier termination attempt fails.
- Correct the Caddy Basic Auth coexistence examples so programmatic `/v1/*` Bearer requests bypass Basic Auth and remain protected by `TTS_API_KEY`.
- Emit a `{"type":"ping"}` WebSocket heartbeat every 15s while a request is still synthesizing, so long Edge/Kokoro generation gaps no longer trip clients that treat frame silence as a dead stream; `ping` carries no timeline meaning and clients should treat any frame as activity.
- Preserve newline counts in Markdown cleanup for inline code, images, links, headings, block quotes, list items (including empty list markers like a lone `*`), and horizontal rules, keeping the per-line unit parity between the browser splitter and WebSocket `seg` events; lone marker/decoration lines (`*`, `-`, `***`, `* * *`) are cleared instead of being read as literal symbols.
- Route letterless synthesis units (separators, pure punctuation) to the neighboring language run via cross-sentence backfill (previous run first, then the following one) in `auto` and Kokoro single-voice modes, instead of spawning phantom runs whose cleaned text is empty and fails the whole stream; a fully letterless document falls back to zh in `auto` mode and to the picked voice in Kokoro single-voice mode.
- Keep Edge upstream-failure attribution in the REST MP3 path: a failing stream `aclose()` during feed cleanup is logged and no longer replaces the real cause (a local stdin failure was misclassified as `502 upstream` instead of `500`), matching the WebSocket path's semantics.
- Catch `RecursionError` from deeply nested WebSocket JSON payloads and answer with the normal invalid-JSON error frame instead of letting the exception escape the handler.
### Known limits (honest)
- `speed` max remains `3.0` (OpenAI cloud allows up to `4.0`).
- SSE, speech instructions, language hints, the full official voice catalog, and custom voices are not implemented; known unsupported fields fail explicitly.
- Markdown is cleaned before synthesis (differs from raw OpenAI cloud text).
- Edge voice pre-check only trusts a fresh, non-empty cached catalog. Cold, empty, or stale cache states allow synthesis through without refreshing and may still fail later as `502`.
- HTTP streaming cannot replace `200` after response headers are committed. Detected late failures now interrupt the transfer, but cannot be converted to an OpenAI JSON error; real proxy/client behavior still needs end-to-end validation.
- Real ffmpeg output for all six formats and Hermes/OpenClaw end-to-end playback have not yet been verified in this offline test environment.
## v0.11
### Added
- Added fixed runtime and test dependency files for reproducible local, Docker, and CI installs.
- Added Gitea Actions CI for Python compile checks and the `unittest` regression suite.
- Added API key auth for REST and WebSocket clients, including a key probe endpoint and same-origin UI exemption.
- Added `GET /api/logs` with API-key enforcement and response redaction.
- Added REST MP3 synthesis improvements: download mode, request IDs, preflight failure handling, short voice previews, and explicit error status codes.
- Added runtime controls for synthesis timeout, CORS allowlist, Edge voice cache TTL, and concurrent `ffmpeg` process limits.
- Added `TTS_MAX_SYNTHESIS_CONCURRENCY` (default `2`) to cap concurrent Kokoro inference for both REST and WebSocket, closing a WebSocket-only path that previously had no gate.
### Changed
- The main UI now pipelines cross-engine and cross-voice runs through a two-slot prefetch window, preserving PCM/timeline order and using a bounded fallback when the current prefetched run stalls.
- Unified the synthesis speed/engine bounds into shared constants (`SPEED_MIN`/`SPEED_MAX`/`VALID_ENGINES`) across the REST model, WebSocket parser, and voice-preview query, removing the duplicated `0.5`/`3.0`/`("kokoro","edge")` literals. REST still rejects out-of-range speed with `422`; WebSocket still clamps (each keeps its protocol semantics).
- Widened cross-language stripping and speakable-content detection to cover CJK extension A, compatibility ideographs, and the supplementary planes, not just the basic block.
- Documented that a `POST /api/tts` stream can end truncated/empty after the `200` is committed if transcoding fails mid-stream.
- Replaced FastAPI startup events with lifespan startup while preserving the existing startup initialization path.
- Disabled FastAPI's default `/docs`, `/redoc`, and `/openapi.json` surfaces in favor of the bundled `/api` tester.
- Health checks now require both Kokoro pipelines and `ffmpeg` availability before reporting ready.
- Documentation now calls out the current CPU-only Kokoro/PyTorch deployment path and public deployment limits.
### Fixed
- Kept speculative Edge prefetch below normal REST/WebSocket priority by reserving decoder capacity and waiting for main-request decoder admission before opening the Edge prefetch window.
- Prevented WebSocket synthesis failures from emitting a false `end` message.
- Fixed the main UI so later synthesis segments that disconnect before `end` are shown as errors instead of completed playback.
- Applied the `ffmpeg` process limit to WebSocket Edge synthesis and documented that voice previews are API-key protected.
- Avoided caching failed Edge voice-list refreshes; stale successful cache is served when refresh fails.
- Kept auth failure responses readable by browser clients through CORS headers.
- Rejected raw SSML explicitly instead of pretending to pass XML through `edge-tts`.