@volter/twin-livekit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +106 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +23 -0
- package/dist/src/egress-service-cli.d.ts +2 -0
- package/dist/src/egress-service-cli.js +101 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +53 -0
- package/dist/src/livekit-budget.d.ts +57 -0
- package/dist/src/livekit-budget.js +131 -0
- package/dist/src/livekit-capabilities.d.ts +3 -0
- package/dist/src/livekit-capabilities.js +728 -0
- package/dist/src/livekit-conformance.d.ts +8 -0
- package/dist/src/livekit-conformance.js +13 -0
- package/dist/src/livekit-connector.d.ts +130 -0
- package/dist/src/livekit-connector.js +382 -0
- package/dist/src/livekit-data.d.ts +86 -0
- package/dist/src/livekit-data.js +246 -0
- package/dist/src/livekit-perform-harness.d.ts +5 -0
- package/dist/src/livekit-perform-harness.js +17 -0
- package/dist/src/livekit-server.d.ts +23 -0
- package/dist/src/livekit-server.js +45 -0
- package/dist/src/livekit-service-cli.d.ts +2 -0
- package/dist/src/livekit-service-cli.js +88 -0
- package/dist/src/livekit-token.d.ts +68 -0
- package/dist/src/livekit-token.js +76 -0
- package/dist/src/livekit-twin.d.ts +4 -0
- package/dist/src/livekit-twin.js +1161 -0
- package/dist/src/livekit-types.d.ts +203 -0
- package/dist/src/livekit-types.js +1 -0
- package/dist/src/livekit-webhook.d.ts +26 -0
- package/dist/src/livekit-webhook.js +79 -0
- package/dist/src/redis-service-cli.d.ts +2 -0
- package/dist/src/redis-service-cli.js +35 -0
- package/dist/test-fixtures/livekit-openapi-operations.SOURCE.md +88 -0
- package/dist/test-fixtures/livekit-openapi-operations.json +442 -0
- package/dist/test-fixtures/protobufs/cloud_replay.proto +82 -0
- package/dist/test-fixtures/protobufs/livekit_agent.proto +185 -0
- package/dist/test-fixtures/protobufs/livekit_agent_dispatch.proto +103 -0
- package/dist/test-fixtures/protobufs/livekit_agent_simulation.proto +422 -0
- package/dist/test-fixtures/protobufs/livekit_agent_worker.proto +29 -0
- package/dist/test-fixtures/protobufs/livekit_agentdb.proto +226 -0
- package/dist/test-fixtures/protobufs/livekit_analytics.proto +312 -0
- package/dist/test-fixtures/protobufs/livekit_cloud_agent.proto +376 -0
- package/dist/test-fixtures/protobufs/livekit_connector.proto +41 -0
- package/dist/test-fixtures/protobufs/livekit_connector_twilio.proto +65 -0
- package/dist/test-fixtures/protobufs/livekit_connector_whatsapp.proto +171 -0
- package/dist/test-fixtures/protobufs/livekit_egress.proto +641 -0
- package/dist/test-fixtures/protobufs/livekit_ingress.proto +222 -0
- package/dist/test-fixtures/protobufs/livekit_internal.proto +228 -0
- package/dist/test-fixtures/protobufs/livekit_metrics.proto +103 -0
- package/dist/test-fixtures/protobufs/livekit_models.proto +992 -0
- package/dist/test-fixtures/protobufs/livekit_phone_number.proto +151 -0
- package/dist/test-fixtures/protobufs/livekit_room.proto +312 -0
- package/dist/test-fixtures/protobufs/livekit_rtc.proto +673 -0
- package/dist/test-fixtures/protobufs/livekit_sip.proto +1000 -0
- package/dist/test-fixtures/protobufs/livekit_token_source.proto +49 -0
- package/dist/test-fixtures/protobufs/livekit_webhook.proto +62 -0
- package/package.json +58 -0
- package/src/cli.ts +22 -0
- package/src/egress-service-cli.ts +111 -0
- package/src/index.ts +103 -0
- package/src/livekit-budget.ts +158 -0
- package/src/livekit-capabilities.ts +789 -0
- package/src/livekit-conformance.ts +15 -0
- package/src/livekit-connector.ts +406 -0
- package/src/livekit-data.ts +278 -0
- package/src/livekit-perform-harness.ts +17 -0
- package/src/livekit-server.ts +67 -0
- package/src/livekit-service-cli.ts +93 -0
- package/src/livekit-token.ts +133 -0
- package/src/livekit-twin.ts +1162 -0
- package/src/livekit-types.ts +211 -0
- package/src/livekit-webhook.ts +91 -0
- package/src/redis-service-cli.ts +39 -0
- package/test-fixtures/livekit-openapi-operations.SOURCE.md +88 -0
- package/test-fixtures/livekit-openapi-operations.json +442 -0
- package/test-fixtures/protobufs/cloud_replay.proto +82 -0
- package/test-fixtures/protobufs/livekit_agent.proto +185 -0
- package/test-fixtures/protobufs/livekit_agent_dispatch.proto +103 -0
- package/test-fixtures/protobufs/livekit_agent_simulation.proto +422 -0
- package/test-fixtures/protobufs/livekit_agent_worker.proto +29 -0
- package/test-fixtures/protobufs/livekit_agentdb.proto +226 -0
- package/test-fixtures/protobufs/livekit_analytics.proto +312 -0
- package/test-fixtures/protobufs/livekit_cloud_agent.proto +376 -0
- package/test-fixtures/protobufs/livekit_connector.proto +41 -0
- package/test-fixtures/protobufs/livekit_connector_twilio.proto +65 -0
- package/test-fixtures/protobufs/livekit_connector_whatsapp.proto +171 -0
- package/test-fixtures/protobufs/livekit_egress.proto +641 -0
- package/test-fixtures/protobufs/livekit_ingress.proto +222 -0
- package/test-fixtures/protobufs/livekit_internal.proto +228 -0
- package/test-fixtures/protobufs/livekit_metrics.proto +103 -0
- package/test-fixtures/protobufs/livekit_models.proto +992 -0
- package/test-fixtures/protobufs/livekit_phone_number.proto +151 -0
- package/test-fixtures/protobufs/livekit_room.proto +312 -0
- package/test-fixtures/protobufs/livekit_rtc.proto +673 -0
- package/test-fixtures/protobufs/livekit_sip.proto +1000 -0
- package/test-fixtures/protobufs/livekit_token_source.proto +49 -0
- package/test-fixtures/protobufs/livekit_webhook.proto +62 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// LiveKit's CLIENT-SIDE RATE BUDGET — the pack's DECLARATION (the numbers) plus the thin typed
|
|
2
|
+
// bindings `liveLiveKitExecute` uses. The MECHANISM — the durable token-keyed ledger, the rolling
|
|
3
|
+
// window, reserve-under-lock, the `Retry-After`/429 cooldown, fail-CLOSED on a corrupt ledger — lives
|
|
4
|
+
// ONCE in the vendor-agnostic kernel (`@volter/world-core` → `rateBudget.ts`). Read that module's header
|
|
5
|
+
// for the full rationale AND for the honest list of what the guard does not guarantee (an injected
|
|
6
|
+
// clock or ledger path still defeats it — it guards carelessness, not malice).
|
|
7
|
+
//
|
|
8
|
+
// ── WHY THIS EXISTS: THE MISSING CHOKE POINT ─────────────────────────────────────────────────
|
|
9
|
+
// A ~4.5-day Figma token lockout (2026-07-25) came from a burst of raw vendor calls made OUTSIDE any
|
|
10
|
+
// guarded client. The lesson generalized: a pack with no single construction site for its live client
|
|
11
|
+
// is a pack that CANNOT be protected, because there is nowhere for a guard to sit.
|
|
12
|
+
//
|
|
13
|
+
// This connector took an INJECTED `LiveKitExecute` and nothing more. Excellent for testing — every
|
|
14
|
+
// verify runs offline against a fake — but it meant the pack never built a network-calling execute, so
|
|
15
|
+
// a rate budget could only ever be something the CALLER opted into. `liveLiveKitExecute` (in
|
|
16
|
+
// `livekit-connector.ts`) is the choke point that fixes that: the ONE place in this pack that turns a
|
|
17
|
+
// key/secret pair into a function that really talks to a LiveKit server, with this budget charged
|
|
18
|
+
// BEFORE every request goes out. The injected-function contract is untouched.
|
|
19
|
+
//
|
|
20
|
+
// ── WHY THE FAN-OUT SHAPE MATTERS HERE ───────────────────────────────────────────────────────
|
|
21
|
+
// `collectLiveKitSnapshot` lists rooms and then, FOR EACH ROOM, lists that room's participants; the
|
|
22
|
+
// agent-dispatch collector likewise issues one `ListDispatch` per room. So one innocent-looking
|
|
23
|
+
// `syncLiveKitFromReal` over a busy project emits O(rooms) Server-API requests as fast as the event
|
|
24
|
+
// loop allows. That is exactly the shape a budget exists to bound.
|
|
25
|
+
//
|
|
26
|
+
// ── HOW THE CEILING WAS CHOSEN ───────────────────────────────────────────────────────────────
|
|
27
|
+
// LiveKit publishes a scalar for precisely this surface: "All projects have a Server API rate limit of
|
|
28
|
+
// 1,000 requests per minute" — applying to RoomService/EgressService-style requests, NOT to SDK
|
|
29
|
+
// operations like joining a room or sending data packets (docs.livekit.io/deploy/admin/quotas-and-limits/;
|
|
30
|
+
// Scale-plan customers may request an increase). Every call this connector makes is a Server API call.
|
|
31
|
+
//
|
|
32
|
+
// So: 200 weighted units per 60s. With the cheap list/get reads priced at 1 that is 200 requests a
|
|
33
|
+
// minute — 20% of the documented limit — which comfortably covers a real snapshot (a project with a
|
|
34
|
+
// few dozen rooms) while a runaway per-room loop is refused rather than allowed to consume the whole
|
|
35
|
+
// project-wide allowance that the app's OWN server also depends on.
|
|
36
|
+
//
|
|
37
|
+
// ── HOW THE WEIGHTS WERE CHOSEN ──────────────────────────────────────────────────────────────
|
|
38
|
+
// LiveKit counts REQUESTS, so weight 1 is the faithful price for a read, and the flat list/get calls
|
|
39
|
+
// get it. Two get 2 — `RoomService/ListParticipants` and `AgentDispatchService/ListDispatch` — not
|
|
40
|
+
// because LiveKit charges more (it does not) but because they are the two whose call count is
|
|
41
|
+
// UNBOUNDED IN THE ROOM COUNT: they are the inner loop above. Doubling their price halves the room
|
|
42
|
+
// count at which a runaway walk is stopped while barely touching a small snapshot. A judgement call,
|
|
43
|
+
// not a published cost.
|
|
44
|
+
//
|
|
45
|
+
// Everything unclassified costs `defaultWeight` (2) — above a read, because a mutating Twirp method
|
|
46
|
+
// (StartEgress, CreateIngress, SendData) is not a list read and this pack has read no published
|
|
47
|
+
// per-method figure for it. Nothing is ever free.
|
|
48
|
+
import { declareRateBudget, rateBudgetPath, rateBudgetWeight, RateBudget, } from '@volter/world-core';
|
|
49
|
+
const VENDOR = 'livekit';
|
|
50
|
+
/** Rolling window, in ms. Matches LiveKit's own per-MINUTE Server API limit. */
|
|
51
|
+
export const LIVEKIT_BUDGET_WINDOW_MS = 60_000;
|
|
52
|
+
/** Weighted units allowed inside one window. 200 reads/60s = 20% of the documented 1,000/min. */
|
|
53
|
+
export const LIVEKIT_BUDGET_CEILING = 200;
|
|
54
|
+
/** Seconds. A `Retry-After` above this means the project is throttled hard — fail loudly. */
|
|
55
|
+
export const LIVEKIT_BUDGET_MAX_RETRY_AFTER_S = 300;
|
|
56
|
+
/**
|
|
57
|
+
* Per-call cost, keyed by the Twirp RPC id (`"<Service>/<Method>"`) this connector is about to invoke.
|
|
58
|
+
* A LiveKit call is not a REST path — it is an RPC — so the RPC id is the honest call key.
|
|
59
|
+
*/
|
|
60
|
+
export const LIVEKIT_CALL_WEIGHTS = {
|
|
61
|
+
/** The per-room inner loop: one `ListParticipants` / `ListDispatch` per room. Unbounded fan-out. */
|
|
62
|
+
perRoomFanOut: 2,
|
|
63
|
+
/** A flat `List…` / `Get…` read: one call for a whole collection. */
|
|
64
|
+
read: 1,
|
|
65
|
+
/** Anything else, including every mutating method. Priced above a read, never free. */
|
|
66
|
+
other: 2,
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* THE PACK'S DECLARATION — pure data, the only LiveKit-specific thing in the whole budget. Rules are
|
|
70
|
+
* ordered and first-match-wins, and they are keyed on the Twirp RPC id, so the per-room fan-out rules
|
|
71
|
+
* must come BEFORE the generic list/get rule.
|
|
72
|
+
*
|
|
73
|
+
* Also exported as `pack.rateBudget` (see index.ts), so `registerPack` arms it too.
|
|
74
|
+
*/
|
|
75
|
+
export const LIVEKIT_RATE_BUDGET = {
|
|
76
|
+
windowMs: LIVEKIT_BUDGET_WINDOW_MS,
|
|
77
|
+
ceiling: LIVEKIT_BUDGET_CEILING,
|
|
78
|
+
defaultWeight: LIVEKIT_CALL_WEIGHTS.other,
|
|
79
|
+
maxRetryAfterSeconds: LIVEKIT_BUDGET_MAX_RETRY_AFTER_S,
|
|
80
|
+
rules: [
|
|
81
|
+
{ match: '^RoomService/ListParticipants$', weight: LIVEKIT_CALL_WEIGHTS.perRoomFanOut },
|
|
82
|
+
{ match: '^AgentDispatchService/ListDispatch$', weight: LIVEKIT_CALL_WEIGHTS.perRoomFanOut },
|
|
83
|
+
{ match: '^[A-Za-z]+/(List|Get)[A-Za-z]*$', weight: LIVEKIT_CALL_WEIGHTS.read },
|
|
84
|
+
],
|
|
85
|
+
reason: 'LiveKit documents that "All projects have a Server API rate limit of 1,000 requests per minute" — ' +
|
|
86
|
+
'covering RoomService/Egress/Ingress/SIP-style requests, not SDK room operations ' +
|
|
87
|
+
'(docs.livekit.io/deploy/admin/quotas-and-limits/). Every call this connector makes is a Server API ' +
|
|
88
|
+
'call. 200 weighted units / 60s prices a flat list/get at 1, so the ceiling is 20% of that ' +
|
|
89
|
+
'project-wide limit — which the app\'s own server shares. ListParticipants and ListDispatch cost 2 ' +
|
|
90
|
+
'because the snapshot issues one PER ROOM, so their call count is unbounded in the room count — ' +
|
|
91
|
+
'a judgement call, not a published cost.',
|
|
92
|
+
};
|
|
93
|
+
// Declared at module load, so merely importing this module (which `livekit-connector.ts` does) is
|
|
94
|
+
// enough to arm the real ceiling. A budget constructed BEFORE this runs falls back to the kernel's
|
|
95
|
+
// DEFAULT_RATE_BUDGET, which is tighter in call count but prices every call at 2 — cheaper than
|
|
96
|
+
// nothing here, yet it also prices a cheap read at 2, so the two are not ordered. `RateBudget` reads
|
|
97
|
+
// its policy live precisely so this declaration takes effect the moment it lands, and constructing
|
|
98
|
+
// through the subclass below (which imports this module) makes the ordering a non-issue in practice.
|
|
99
|
+
declareRateBudget(VENDOR, LIVEKIT_RATE_BUDGET);
|
|
100
|
+
/**
|
|
101
|
+
* Price one call. Keyed off the Twirp RPC the connector is ABOUT to invoke, so an unclassified method
|
|
102
|
+
* still costs `defaultWeight` — an unknown method must never be free.
|
|
103
|
+
*/
|
|
104
|
+
export function liveKitCallWeight(service, method) {
|
|
105
|
+
return rateBudgetWeight(VENDOR, `${service}/${method}`);
|
|
106
|
+
}
|
|
107
|
+
/** Where LiveKit's ledger lives. API-key-keyed and cwd-independent by default (the limit is per
|
|
108
|
+
* PROJECT and the API key identifies the project, so a cwd-scoped ledger would hand the same project
|
|
109
|
+
* a fresh allowance in every checkout, worktree and CI matrix leg); pass `root` to opt into
|
|
110
|
+
* world-scoped accounting instead. */
|
|
111
|
+
export function liveKitBudgetPath(opts = {}) {
|
|
112
|
+
const o = typeof opts === 'string' ? { root: opts } : opts;
|
|
113
|
+
// VENDOR spread LAST: a loosely-typed `{ vendor: 'other', … }` slipping through (TypeScript's
|
|
114
|
+
// excess-property check only catches object literals) must not redirect this pack's ledger to
|
|
115
|
+
// another vendor's file.
|
|
116
|
+
return rateBudgetPath({ ...o, vendor: VENDOR });
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* LiveKit's budget — the shared kernel guard bound to this vendor's declaration. A real subclass, not
|
|
120
|
+
* an alias, so `budget instanceof LiveKitBudget` in `liveLiveKitExecute` still means "a budget that
|
|
121
|
+
* accounts against LIVEKIT's ledger under LIVEKIT's ceiling": another vendor's `RateBudget` (with its
|
|
122
|
+
* own, possibly larger, ceiling) is NOT assignable there.
|
|
123
|
+
*/
|
|
124
|
+
export class LiveKitBudget extends RateBudget {
|
|
125
|
+
constructor(opts = {}) {
|
|
126
|
+
super({ ...opts, vendor: VENDOR });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
|
|
130
|
+
* which one refused, and `err.kind` says why. */
|
|
131
|
+
export { RateBudgetError as LiveKitBudgetError } from '@volter/world-core';
|