1import * as Schema from "effect/Schema";
2
3/** Cancellation is cooperative: settle only after the underlying work has stopped. */
4export type VoiceTranscriptionOptions = {
5 readonly signal: AbortSignal;
6};
7
8export type VoiceStreamingSession = {
9 readonly finish: (options: VoiceTranscriptionOptions) => Promise<string>;
10};
11
12export type PreparedVoiceTranscription = {
13 readonly locale: string;
14 /**
15 * Runs alongside capture once recording starts. Resolves with the transcript when it
16 * transcribed live, or null when `transcribe` still runs after the recording stops.
17 * A rejection stops the recording.
18 */
19 readonly start?: (uri: string, options: VoiceTranscriptionOptions) => Promise<string | null>;
20 readonly transcribe: (uri: string, options: VoiceTranscriptionOptions) => Promise<string>;
21 /** Present when audio streams during capture; `finish` then replaces `transcribe`. */
22 readonly streaming?: VoiceStreamingSession;
23};
24
25export type VoiceTranscriber = {
26 readonly prepare: (options: VoiceTranscriptionOptions) => Promise<PreparedVoiceTranscription>;
27};
28
29const VoiceTranscriptionErrorCode = Schema.Literals([
30 "unavailable",
31 "unsupported-locale",
32 "preparation-failed",
33 "transcription-failed",
34 "cancelled",
35]);
36export type VoiceTranscriptionErrorCode = typeof VoiceTranscriptionErrorCode.Type;
37
38export class VoiceTranscriptionError extends Schema.TaggedError<VoiceTranscriptionError>()(
39 "VoiceTranscriptionError",
40 {
41 code: VoiceTranscriptionErrorCode,
42 message: Schema.String,
43 cause: Schema.optional(Schema.Defect()),
44 },
45) {}
46
47export function throwIfVoiceTranscriptionAborted(signal: AbortSignal): void {
48 if (signal.aborted) {
49 throw new VoiceTranscriptionError({
50 code: "cancelled",
51 message: "Voice transcription was cancelled.",
52 });
53 }
54}