npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ephia-protocol

v1.0.0

Published

Contrat universel EphiaAudioEvent — types et schémas

Downloads

130

Readme

ephia-protocol

Contrat universel EphiaAudioEvent — le cœur du système.

Rôle et consommateurs autorisés

ephia-protocol est le contrat wire interne d'Ephia : types, schemas Zod, fixtures et changelog protocolaire. Voir docs/wire-contract.md et docs/protocol-versioning.md pour le détail des invariants actuels (protocole v3 : segment.canonical, commandSeq, ephiaWireServerEventSchema).

Il est consommé directement par :

  • @ephia/sdk (packages/ephia-sdk) — seul runtime client autorisé à interpréter le wire ;
  • les tests de contrat de ce package ;
  • le backend Python via les fixtures JSON de src/fixtures/.

Il n'est pas une API cliente. Les apps ne doivent pas importer les schemas wire (ephiaWireServerEventSchema, etc.) pour parser ou appliquer la dictée directement — elles consomment uniquement @ephia/sdk, qui réexporte les types publics nécessaires.

Note : le reste de ce README (sections « Enveloppe commune », « Événements par catégorie », exemple transcript.segment.committed) décrit un modèle d'événements antérieur au protocole v3 actuel et n'a pas été mis à jour dans le cadre de cette PR — à corriger séparément.

Principe fondamental

Protocol > Framework

Ce package est indépendant. Il ne dépend ni du SDK, ni de React, ni de LiveKit, ni du backend.

Il contient :

  • types TypeScript des événements publics
  • schémas Zod pour validation runtime
  • messages client
  • états de session
  • codes d'erreur
  • version du protocole (3 wire / "3.0" REST capabilities)

À quoi sert ce dossier ?

packages/protocol est le contrat partagé du produit audio. Il évite que le backend et le SDK évoluent avec des définitions d'événements divergentes.

Concrètement :

  • le backend doit émettre des payloads JSON conformes aux schémas de ce package
  • le SDK importe les types pour typer ses reducers, handlers et callbacks
  • le SDK valide chaque événement reçu avec ephiaAudioEventSchema.safeParse()
  • les tests peuvent vérifier la compatibilité backend/SDK à partir d'une seule source de vérité

Le backend Python n'importe pas le code TypeScript directement. La contrainte est contractuelle : les noms d'événements, champs obligatoires et formats de payload doivent rester compatibles avec les schémas Zod publiés ici.

Enveloppe commune (tous les événements)

{
  "protocolVersion": 3,
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "sessionId": "sess_abc",
  "type": "transcript.segment.committed",
  "seq": 42,
  "timestampMs": 1716200000000,
  "payload": { "..." },
  "meta": { "provider": "voxtral-small", "latencyMs": 340 }
}
  • seq : strictement croissant par session
  • eventId : UUID unique
  • timestampMs : timestamp serveur
  • sessionId : obligatoire sur tous les events
  • meta : optionnel — diagnostics, latences, modes

Règles du protocole

  • Aucun preview/stable ne doit écraser un segment déjà committed
  • committed insère la version courante du texte de façon atomique
  • reviewing signale une review SMART sans changer le texte
  • corrected remplace exactement le segment committed correspondant
  • review.rejected termine une review sans changement de texte
  • Ordre événementiel déterministe par seq

Règle d'atomicité des fusions

transcript.segment.committed est l'événement atomique d'application documentaire.

Lorsqu'un segment absorbe d'autres segments, le payload mergedWith contient les IDs absorbés. Le client doit appliquer le commit final et la suppression des absorbés dans une seule transaction visuelle atomique — jamais en deux étapes séparées.

segment.absorbed est un événement de lifecycle/debug. Il est émis après transcript.segment.committed correspondant. Il ne doit jamais provoquer de suppression visuelle avant le commit fusionné.

Événements par catégorie

Session

| Type | Payload clés | Description | |---|---|---| | session.started | clientType, mode, language | Session initiée | | session.ready | roomName | Room LiveKit prête, dictée possible | | session.stt_ready | bufferedFramesFlushed | STT initialisé (lazy init terminé) | | session.paused | — | Session mise en pause | | session.closed | reason | Session terminée (user_stop\|disconnect\|error\|timeout) | | session.error | code, message, recoverable | Erreur session | | session.pong | — | Réponse à un ping keepalive | | session.context.reset | contextOnly, reason | Contexte backend réinitialisé | | session.catchup.started | trackName, durationMs | Catchup audio démarré | | session.catchup.completed | segmentCount, durationMs | Catchup terminé | | session.catchup.failed | reason | Catchup échoué |

Audio / VAD

| Type | Payload clés | Description | |---|---|---| | audio.vad.start | timestampMs | Voix détectée | | audio.vad.end | timestampMs, durationMs | Fin de voix |

Pipeline segment (dual)

| Type | Payload clés | Description | |---|---|---| | segment.opened | segmentId, segmentSeq, startMs | Segment audio ouvert | | segment.audio.closed | segmentId, endMs, reason | Partie audio du segment terminée | | segment.dropped | segmentId, reason | Segment silencieux/bruit, ignoré | | segment.absorbed | segmentId, absorbedInto | Segment fusionné dans un autre |

Transcription — temps réel

| Type | Payload clés | Description | |---|---|---| | transcript.preview | segmentId, segmentSeq, text, revision | Texte en cours (peut se répéter) | | transcript.preview.stable | segmentId, segmentSeq, text, revision | Partie stable du preview |

Transcription — cycle de vie segment

| Type | Payload clés | Description | |---|---|---| | transcript.segment.opened | segmentId, segmentSeq, startedAtMs | Début segment (pipeline legacy) | | transcript.segment.processing | segmentId, durationMs | Finalisation en cours | | transcript.segment.completed | segmentId, provider | Finalisation terminée | | transcript.segment.ready | segmentId, text, tags | Texte finalisé (bridge visuel avant commit) | | transcript.segment.committed | segmentId, text, tags, mergedWith? | Texte définitif — à appliquer atomiquement | | transcript.segment.reviewing | segmentId, tags | Review SMART en cours | | transcript.segment.corrected | segmentId, text | Correction review appliquée | | transcript.segment.revised | segmentId, newText, oldText | Révision post-commit | | transcript.segment.review.queued | segmentId | Review mise en attente | | transcript.segment.review.rejected | segmentId, reason, text | Review terminée sans correction | | transcript.segment.review.failed | segmentId, error | Erreur review | | transcript.segment.error | segmentId, code, message, recoverable | Erreur finalisation |

Document

| Type | Payload clés | Description | |---|---|---| | document.patch | ops[] | Opérations structurelles sur le document |

Flux normal d'un segment

segment.opened
  → transcript.preview         (texte interim, 1..N fois)
  → transcript.preview.stable  (texte stable, 0..N fois)
  → segment.audio.closed       (audio terminé, traitement en cours)
  → transcript.segment.committed  ← appliquer atomiquement
  → [transcript.segment.reviewing
     → transcript.segment.corrected | transcript.segment.review.rejected]

Exemple de payload committed

{
  "protocolVersion": 3,
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "sessionId": "sess_abc",
  "type": "transcript.segment.committed",
  "seq": 42,
  "timestampMs": 1716200000000,
  "payload": {
    "segmentId": "seg_12",
    "segmentSeq": 12,
    "text": "Le foie est de taille normale.",
    "source": "voxtral-small",
    "tags": [{ "tag": "anatomy", "word": "foie" }]
  },
  "meta": {
    "provider": "voxtral-small",
    "latencyMs": 820,
    "audioDurationMs": 2300
  }
}

Messages client → backend

| Message | Envoyé via | Description | |---|---|---| | { type: "session.pause" } | data_channel | Mettre en pause | | { type: "session.resume" } | data_channel | Reprendre | | { type: "session.reset", payload: { contextOnly: true } } | data_channel | Reset contexte | | { type: "catchup.start", payload: { catchupId, source, sampleRate, channels, bytesTotal, durationMs, clientStartedAtMs?, clientEndedAtMs?, catchupSeq? } } | data_channel | Démarrer un burst PCM16 mono 16 kHz catchup | | { type: "catchup.chunk", payload: { catchupId, chunkIndex, offsetBytes, dataBase64 } } | data_channel | Envoyer un chunk catchup fiable et ordonné | | { type: "catchup.end", payload: { catchupId, chunkCount, bytesTotal } } | data_channel | Finaliser le burst catchup | | { type: "catchup.abort", payload: { catchupId, reason } } | data_channel | Annuler le burst catchup | | ephia.session.pause | RPC | Identique pause (mode useRpcCommands) | | ephia.session.resume | RPC | Identique resume (mode useRpcCommands) | | ephia.session.reset | RPC | Identique reset (mode useRpcCommands) |

Usage

import {
  ephiaAudioEventSchema,
  type EphiaAudioEvent,
  type TranscriptSegmentCommittedEvent,
  PROTOCOL_VERSION,  // "2.0"
} from "ephia-protocol";

// Validation runtime
const parsed = ephiaAudioEventSchema.safeParse(raw);
if (!parsed.success) {
  console.warn("Invalid event:", parsed.error.issues);
  return;
}

// Discrimination par type
const event: EphiaAudioEvent = parsed.data;
if (event.type === "transcript.segment.committed") {
  const { segmentId, text, tags, mergedWith } = event.payload;
  applyToEditor(segmentId, text, { absorbed: mergedWith ?? [] });
}