Buzz Provider¶
BuzzProvider ¶
Bases: BuzzRelayProvider
Outbound Buzz delivery via a shared :class:BuzzRelaySource client.
The provider delegates every send to the buzzkit.BuzzClient owned by the
paired source. It does not manage the connection lifecycle — that stays
with the source. Sends use the HTTP bridge, so they succeed even when the
inbound WebSocket is mid-reconnect.
send
async
¶
Post event to Buzz channel to (a relay channel UUID).
channel_data.thread_id (the thread-root Nostr event id, as set by
the inbound parser) threads the message as a NIP-10 reply — the same
provider-native contract Discord and Teams use.
send_reaction
async
¶
React to a relay event (kind 7) through the shared client.
remove_reaction
async
¶
Retract our own reaction (kind 5) through the shared client.
BuzzConfig ¶
Bases: BaseModel
Buzz relay agent configuration.
private_key is the agent's Nostr secret (nsec… or hex); it signs the
agent's events and authenticates it to the relay (NIP-42/98).
MockBuzzProvider ¶
Bases: BuzzRelayProvider
Records sent messages for verification in tests.
Carries no buzzkit dependency and no relay client, so it can drive the
delivery path without a live connection.
BuzzRelaySource ¶
BuzzRelaySource(config, channel_id='buzz', *, relay_channel_id, parser=None, kinds=None, on_event=None)
Bases: BaseSourceProvider
Persistent Buzz relay connection emitting one channel's messages.
Owns the :class:buzzkit.BuzzClient and exposes it via :attr:client so
the paired provider can send through the same identity. Subscribes to a
single relay channel (relay_channel_id); register one source per Buzz
channel and bind each to its RoomKit room.
kinds selects the Nostr event kinds to subscribe to (default:
chat messages, kind 9). Pass other kinds — e.g. huddle announcements,
kind 48100 — together with a parser that knows how to convert
them; the default parser only understands text messages.
on_event surfaces reaction lifecycle events (kind 7 add, kind 5
remove) as normalised dicts, outside the message pipeline — matching
how Discord and WhatsApp-personal handle reactions. Providing it
widens the default subscription to kinds 9, 7 and 5; requires a relay
that scopes reactions to their target's channel (buzzkit>=0.2.0).
parse_buzz_event ¶
Convert a Nostr event dict into an :class:InboundMessage.
Duck-typed on a plain dict so it can be unit-tested without a relay.
Returns None to skip the agent's own events (echo guard) and events
with no text content.
huddle_announcement_parser ¶
Parser for huddle announcements (kind 48100).
Emits one :class:InboundMessage per announcement with the ephemeral
huddle id in metadata["ephemeral_channel_id"]. Subscribe the source
with kinds=[KIND_HUDDLE_STARTED].
started_after (unix seconds) drops announcements replayed from relay
history — the subscription replays recent events before EOSE, and a
restarted agent must not chase long-dead huddles.
Voice (huddles)¶
BuzzHuddleBackend ¶
BuzzHuddleBackend(*, silence_fill=True, provider_input_rate=16000, provider_output_rate=24000, end_when_alone=True, empty_huddle_grace=90.0)
Bases: VoiceBackend
Realtime voice transport backed by a Buzz huddle.
The connection given to :meth:accept must be a connected
buzzkit.HuddleClient. The backend owns it from that point on:
it runs the client's event loop, and :meth:disconnect leaves the
huddle and closes the socket.
Outbound pacing (one Opus frame per 20 ms) and the wire protocol live in buzzkit; this class only moves PCM bytes and session state.
silence_fill (default on) streams silence frames to the pipeline
whenever no huddle audio is arriving — huddle senders go quiet between
utterances (Opus DTX), but a realtime provider's server VAD needs to
hear the post-speech silence to close the user's turn, exactly as it
would from a continuously open microphone.
provider_input_rate / provider_output_rate are the realtime
provider's PCM rates (defaults match Gemini Live: 16 kHz in, 24 kHz
out). The backend resamples huddle audio (48 kHz) to/from those rates
internally — leave the channel's transport_sample_rate unset.
end_when_alone (default on) ends the session when the last
remote peer leaves the huddle. The relay keeps a huddle alive while
ANY member is connected — this agent included — so without it the
huddle and the provider session run forever. empty_huddle_grace
is how long to wait for a first peer in a huddle that is empty at
join time (the announcement can precede the creator's audio socket).
BuzzHuddleWatcher ¶
BuzzHuddleWatcher(kit, *, voice_channel, config, parent_channel_id, room_id, participant_id='buzz-agent', events_channel_id='buzz-huddle-events', join_attempts=3, client_factory=None)
Bridge every huddle announced on a Buzz channel to a voice channel.
Owns the whole announcement→call lifecycle so an application only builds its voice channel::
voice = RealtimeVoiceChannel("buzz-voice", provider=..., transport=BuzzHuddleBackend())
kit.register_channel(voice)
await kit.create_room(room_id="huddles")
await kit.attach_channel("huddles", "buzz-voice")
watcher = BuzzHuddleWatcher(
kit,
voice_channel=voice,
config=BuzzConfig(relay_url=..., private_key=...),
parent_channel_id=parent_uuid,
room_id="huddles",
)
await watcher.start()
start() subscribes to huddle announcements (kind 48100) on the parent
channel through a :class:~roomkit.sources.buzz.BuzzRelaySource attached
with auto_restart=True — relay reconnection is the framework's job —
and dials every announced huddle. The transport ends each call on its own
(end_when_alone, or the relay dropping the socket); the watcher reads
session.metadata["buzz_end_reason"] to choose between rejoining the
same huddle (connection loss) and waiting for the next one (call over).
bridge(huddle_id) dials one known huddle directly (no watching).
Calls are bridged one at a time; announcements that arrive mid-call are
ignored. client_factory is injectable for tests.