Platform · Model families
STT
Implement Speech-to-Text — batch upload + optional realtime WebSocket streaming.
Batch mode (transcribe) is required; streaming is optional. Streaming is a property of a model, not a separate capability: a model that declares streaming is served by your createStreamingSession and openStreamingSession hooks, which own the vendor protocol. The platform sends mono 16 kHz PCM16 and receives normalised transcripts.
Define the capability
typescript
import { defineModels } from "@scrydon/sdk-authoring/extensions/define";
import type { STTCapabilityConfig } from "@scrydon/sdk-authoring/extensions/capabilities/stt";
import type { SttStreamingSpec } from "@scrydon/sdk-authoring/extensions/capabilities/stt-streaming";
const LIVE_STREAMING: SttStreamingSpec = {
protocol: "acme-live-v1",
audio: { sampleRateHz: 16000, encoding: "pcm16", channels: 1 },
};
const sttCapability: STTCapabilityConfig = {
models: [
{ id: "whisper-v3", name: "Whisper V3", benchmarks: [{ name: "WER", score: 4.2, source: "Artificial Analysis" }] },
{ id: "live-v1", name: "Live Transcribe", streaming: LIVE_STREAMING },
],
defaultModel: "whisper-v3",
// Batch mode — upload audio, get text back
async transcribe(request) {
const response = await fetch("https://api.example.com/v1/transcribe", {
method: "POST",
headers: { Authorization: `Bearer ${request.apiKey}` },
body: request.audioData,
});
const data = await response.json();
return { transcript: data.text, language: data.language, confidence: data.confidence };
},
// Streaming (optional). A model that declares `streaming` needs BOTH hooks:
// createStreamingSession mints the ticket, openStreamingSession runs the session.
async createStreamingSession(request) {
return {
...LIVE_STREAMING,
transport: {
mode: "proxied",
upstreamUrl: "wss://api.example.com/v1/live",
upstreamHeaders: { Authorization: `Bearer ${request.apiKey}` },
},
diarizes: false,
};
},
async openStreamingSession(request, context) {
const socket = new WebSocket(`wss://api.example.com/v1/live?model=${encodeURIComponent(request.model)}`);
await new Promise((resolve, reject) => {
socket.addEventListener("open", resolve, { once: true });
socket.addEventListener("error", reject, { once: true });
});
// Authenticate the way your vendor's protocol expects.
socket.send(JSON.stringify({ type: "auth", apiKey: request.apiKey }));
context.signal.addEventListener("abort", () => socket.close());
return {
sendAudio: (pcm) => socket.send(new Uint8Array(pcm)),
finalize: () => socket.send(JSON.stringify({ type: "finalize" })),
close: () => socket.close(),
onTranscript: (handler) =>
socket.addEventListener("message", (event) => {
const data = JSON.parse(String(event.data));
handler({ text: data.text, isFinal: data.final === true });
}),
onError: (handler) => socket.addEventListener("error", () => handler(new Error("stream error"))),
onClose: (handler) => socket.addEventListener("close", (event) => handler(event.reason || null)),
};
},
};
// One model family per runtime; list it under defineExtension()'s provides.models.
export const sttModels = defineModels({
id: "my-service-stt",
name: "My Service Speech-to-Text",
family: "stt",
runtime: sttCapability,
});Model metadata
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Unique model identifier (e.g. "whisper-v3") |
name | string | yes | Display name in the UI |
benchmarks | BenchmarkScore[] | no | WER scores — lower is better |
streaming | SttStreamingSpec | no | Present when the model streams: vendor protocol id and audio format, optional diarizes and maxSessionSeconds |
Standard STT benchmark is WER (Word Error Rate) — a percentage where lower is better. Include source to indicate where measured.
Realtime feature flags
| Flag | Meaning |
|---|---|
interimResults | Vendor streams partial transcripts as audio arrives |
endpointDetection | Vendor signals when the speaker stops |
multiLanguage | A single session can detect / mix languages |