From 6aa6e8e78fe4b6c59307c8fbc83d22da10cdb7c0 Mon Sep 17 00:00:00 2001 From: 0xShug0 <231717474+0xShug0@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:36:01 -0400 Subject: [PATCH] Support textless diarization in transcription details --- app/server/runtime.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/server/runtime.cpp b/app/server/runtime.cpp index 9aea40042..b50656f5f 100644 --- a/app/server/runtime.cpp +++ b/app/server/runtime.cpp @@ -2695,7 +2695,11 @@ HttpResponse ServerState::run_transcription( : run_model(model, request, busy_timeout_ms); const auto & result = timed_result.result; if (!result.text_output.has_value()) { - throw std::runtime_error("model result did not contain transcript text"); + std::shared_lock metadata_lock(model.metadata_mutex); + // Diarization has no transcript, even when silence yields no speaker turns. + if (!detail || model.task.task != engine::runtime::VoiceTaskKind::Diarization) { + throw std::runtime_error("model result did not contain transcript text"); + } } if (!request.audio_input.has_value()) { throw std::runtime_error("transcription timing requires audio_input"); @@ -2710,8 +2714,8 @@ HttpResponse ServerState::run_transcription( // discards them. This is the opt-in route that keeps them, so the shape stays // a superset of the plain one: text first, timing last, details in between. std::ostringstream out; - out << "{\"text\":" << json_quote(result.text_output->text); - if (!result.text_output->language.empty()) { + out << "{\"text\":" << (result.text_output ? json_quote(result.text_output->text) : "\"\""); + if (result.text_output && !result.text_output->language.empty()) { out << ",\"language\":" << json_quote(result.text_output->language); } write_transcript_detail_fields(out, result, [&](const std::string & name) {