A live-stream AI co-host runtime for the codai gateway. Feed it chat,
gift, join, like, follow and share events from any platform; it moderates, remembers viewers,
scores and triages every event, drafts short spoken replies (streaming, one sentence at a time)
and hands them to your text-to-speech — never over itself, never faster than your rate limit,
and, in approve mode, never before you approve.
Extracted from TikSee, where it runs the co-host on TikTok LIVE.
- Platform-agnostic — one Zod-validated
LiveEventshape; write a 20-line adapter per platform. - Pluggable — memory store, moderation, LLM backend and speech output are interfaces.
- One runtime dependency (
zod); uses the platformfetch. Node 20+ and edge runtimes. - Every building block is exported on its own: use the whole
LiveAgentor just the scorer.
npm install codai-live-agent
# or
pnpm add codai-live-agentYou need a codai API key. Get one at codai.ro. The key is passed in by your code; the package never reads it from the environment and never logs it.
import { LiveAgent } from 'codai-live-agent';
const agent = new LiveAgent(
{
llm: { model: 'codai-fast' }, // any codai model alias; baseUrl defaults to https://ai.codai.ro/v1
persona: {
name: 'Codai',
platform: 'Twitch',
language: 'en',
aboutStreamer: 'Speedruns Celeste.',
},
replyMode: 'approve', // or 'auto'
repliesPerMinute: 4,
moderation: { blockedWords: ['scam', '*crypto*'], alertKeywords: ['help'] },
},
{
apiKey: myKeyFromASecretStore, // string or () => Promise<string | null>
speak: async (sentence) => tts.say(sentence), // resolve when the audio finished
onQueue: (entries) => ui.renderQueue(entries),
onSuggestion: (s) => ui.toast(s.text),
onError: (error, where) => log.warn({ where, error }),
},
);
agent.startSession('stream-2026-10-05');
// From your platform adapter:
await agent.handle({
id: msg.id,
kind: 'chat',
at: Date.now(),
user: { id: msg.userId, name: msg.displayName, isSubscriber: msg.sub },
text: msg.text,
});
// Streamer controls (from your UI):
agent.approve(entryId); // speak the draft
agent.approve(entryId, 'My own words.'); // edit + approve
agent.edit(entryId, 'Fixed text.'); // edit, keep waiting
agent.skip(entryId); // drop it (an unspoken AI draft refunds its token)
agent.setMode('auto'); // speak drafts as soon as they are ready
agent.say('Back in five minutes!'); // manual announcement
agent.pause(); // mute; agent.resume()
// Optional: streamer speech-to-text gives replies context
agent.onTranscript('we are on the last level now');
await agent.dispose();type LiveEvent = {
id: string;
kind: 'chat' | 'gift' | 'join' | 'like' | 'follow' | 'share';
at: number; // epoch ms
user: { id: string; name: string; avatarUrl?; isModerator?; isSubscriber?; isFollower? };
text: string; // chat text, '' otherwise
gift?: { name: string; count: number; value: number }; // value of ONE gift, platform units
replay?: boolean; // replays never write memory
};LiveEventSchema / parseLiveEvent validate untrusted input; agent.handle validates too.
event ─► moderation hook ─► memory.observe ─► scoreEvent ─► triage
├─ read (template, no LLM)
├─ thanks (template, no LLM)
├─ greeting (template, no LLM)
└─ ai ─► ReplyQueue ─► TokenBucket ─► draftReply (stream)
└─► approve / edit / skip / auto ─► speak()
| Export | What it does |
|---|---|
scoreEvent, decayed, DuplicateTracker |
Deterministic priority: questions, mentions, gifts (log value), first-time, roles; decay. |
TokenBucket |
Replies per minute with burst and refund. |
ReplyQueue |
Ranked, capped, expiring queue; approve / edit / skip / setMode('auto'). |
triage |
Pure routing decision with reasons (ai:low-score, thanks:dedupe...). |
CodaiLlmClient, draftReply |
Streaming responder on /v1/chat/completions, sentence callbacks, sentence cap. |
WordListModerator, ModerationHook |
Word / user / spam moderation (leetspeak-folded), alert keywords, output check. |
MemoryStore, InMemoryStore, createMemoryHooks |
Viewer profiles, visits, gift totals, facts. Store failures fail open. |
FactCollector |
Batched extraction of durable self-statements ("lives in Cluj") into memory. |
Coach |
Teleprompter: unanswered question, thank gifts, quiet chat, activity spike. |
thanksLine, greetingLine, readAloudLine |
Template lines in English or Romanian. |
import type { MemoryStore } from 'codai-live-agent';
const store: MemoryStore = {
getViewer: (id) => db.viewer.find(id),
putViewer: (p) => db.viewer.upsert(p),
getFacts: (id) => db.facts.list(id),
addFacts: (id, facts, at) => db.facts.add(id, facts, at),
};
new LiveAgent(config, { apiKey, store });Implement LlmClient (complete + stream) and pass it as deps.llm instead of apiKey.
- Moderation hook errors fail closed (the event is dropped); memory errors fail open.
- The system prompt is stable per persona (gateway prompt cache hits) and forbids politics, medical/financial/legal advice, impersonating a human and promising prizes.
- Drafted sentences pass the output moderation hook before they can be spoken.
MIT