@volter/twin-twelvelabs
v0.1.37
Published
Local Twelve Labs twin for the video-understanding lifecycle (indexes, async video-indexing tasks, videos, search, analyze/summarize/gist) built on @volter/world-core.
Readme
@volter/twin-twelvelabs
Connector pull:
syncTwelvelabsFromRealreads the first page of each vendor list and hands the resources to the kernel'sobserveResources, which appends only what changed to the root's log. Check the generated index for protocol standing and use the shared model for current state semantics.
Local Twelve Labs twin for the video-understanding lifecycle: indexes, the async
video-indexing task flow (multipart submit → poll Validating → Pending → Queued → Indexing →
Ready → the video materialises in its index), videos, any-to-video search with real page
tokens, and the Pegasus generation endpoints (analyze / summarize / gist) — built on the
shared @volter/world-core kernel. State is the kernel's append-only action log folded into a
projection; there is no side-store and no real Twelve Labs is ever contacted.
bun packages/twin/twelvelabs/src/cli.ts serve # world-twelvelabs serve|conformancePoint the real, first-party twelvelabs-js SDK at it with its own public option — no patching:
new TwelvelabsApiClient({ apiKey: 'tlk_local', baseUrl: `http://127.0.0.1:${port}/v1.3` });Coverage
This is a v1 slice of the Twelve Labs v1.3 API, not the full surface: 58 of 128 in-scope
manifest capabilities are done (132 total) — partial by
construction, and the denominator is the real vendor
surface (authored top-down from docs.twelvelabs.io's v1.3 reference and cross-checked against
the first-party [email protected] SDK's serializers), not a self-portrait.
Modeled done:
- Indexes — create (
201 {_id}, immutable model config, duplicate-name refusal), get, list (with paging +index_namefilter), rename, delete (cascading to its videos and tasks), andvideo_count/total_durationfolded from the videos actually present. - The async video-indexing task lifecycle (the priority surface) —
POST /v1.3/tasksovermultipart/form-dataexactly as the SDK sends it (JSON is accepted too), the documentedValidating → Pending → Queued → Indexing → Readyprogression advancing one step per poll, the video id minted at submit but only resolving once the task isReady, terminal-only deletion (task_cannot_be_deleted),user_metadataround-trip, and list filtering by index and status. - Videos — list, get,
user_metadataupdate, delete. - Search — ranked matches with the v1.3 envelope (
data[].rank/start/end/video_id,page_info,search_pool), an empty result when nothing matches, realnext_page_tokenpaging redeemed throughGET /v1.3/search/{page-token}, and the vendor's refusals (search_option_not_supported,index_not_supported_for_search,page_limitbounds). - Generation —
analyze({id, data, finish_reason, usage}),summarize(summary/chapter/highlight), andgist(title/topic/hashtag), each with the vendor'sindex_not_supported_for_generaterefusal when the index has no Pegasus model. - Auth —
x-api-keyis required whenever a request carries an auth surface; missing or sentinel-invalid keys get the vendor's401 api_key_invalid, before any state is written. - Safety —
readOnlyrefuses writes with 405 and stops the task poll from advancing (advancing the status machine is a kernel write, and atReadyit mints a whole video); an unmodeled route (including real-but-unbuilt vendor endpoints such as/v1.3/embed) returns the vendor's404 {code,message}, never a fabricated success. - Connector —
syncTwelvelabsFromRealpulls indexes, their videos and their tasks over an injected client (a fake in tests, a realTwelvelabsApiClientin prod), idempotently. A task it merely observed is never advanced by the poll machine: simulating a mirrored job's completion would mint a video the real account never returned, and no re-pull could heal it.
Two things this coverage is deliberately honest about:
POST /v1.3/tasksis the vendor's legacy upload path. Twelve Labs now steers new integrations to direct/multipart uploads followed byPOST /v1.3/index-content, and the SDK markstasks.createfor future removal. The legacy path is modeled first because it is the one that still exercises the whole async lifecycle in a single call; its successor is acoretodo.- The connector is pull-only. Push-to-real is a filed todo (
twelvelabs.connector.push), not an implied capability.
Left as todo (honest gaps, not yet modeled): the whole asset/upload pipeline (direct upload,
multipart uploads, /index-content), embeddings (/embed, /embed/tasks), async and batch
analysis, NDJSON streaming for analyze, response_format JSON-schema output, group_by=video
and media/filter search, transcription, HLS streams and the thumbnail addon, entities, knowledge
stores, the Responses API, data connectors and imports, persisting uploaded media bytes so the
thumbnail/HLS URLs the twin returns resolve, 429 rate-limit parity, full per-endpoint
error-code parity, and two connector gaps (pulling assets/indexed content, and push-to-real). See
src/twelvelabs-capabilities.ts for the full manifest.
Fidelity
src/twelvelabs-sdk.integration.test.ts drives the unmodified first-party twelvelabs-js
SDK at the twin over real HTTP for indexes, the full task lifecycle and search — including the
multipart/form-data encoding the SDK uses for tasks.create and search.create, and the SDK's
own response deserializers, which throw when a shape is wrong. The generation endpoints
(analyze/summarize/gist) are driven over real-transport fetch in the same file, because
that SDK version types models[].model_name as the single literal marengo3.0 (so a Pegasus
index cannot be expressed through it) and has moved its typed analyze() helper to the newer
asset-based video context object while the v1.3 REST body is still {video_id, prompt}.
Two deliberate wire-shape decisions
Search hits carry rank (1 = most relevant), not score/confidence. Both the current v1.3
reference example and [email protected]'s SearchItem serializer model rank, so that is
what the twin emits; emitting a score field neither source shows would be fabrication.
The next_page_token is not opaque. The real API caches a result page server-side and hands
back a token that redeems it; the twin encodes the query into the token instead, so paging is
stateless and keeps working under readOnly. That makes the token readable and rewritable, so
every bound the POST path enforces is re-enforced on redemption (page_limit ≤ 50, page ≥ 1) —
a forged token is refused, never served. The remaining gap, that a page is recomputed rather than
replayed from a snapshot taken at query time, is filed as twelvelabs.search.page_cache_snapshot.
What the SDK cross-check does and does not cover
Index, task, video, search, analyze and summarize shapes are all pinned by types that ship in
[email protected] (IndexSchema, VideoIndexingTask, SearchItem, SearchResultsPageInfo,
SearchPool, NonStreamAnalyzeResponse, TokenUsage, Summarize{Summary,Chapter,Highlight}Result).
/v1.3/gist has no type in that SDK at all, and summarize's types ship without a client
method — so those two endpoints rest on the published v1.3 reference alone and cannot be checked
against the SDK offline. Stated here rather than folded into a blanket "cross-checked" claim.
No UI mirror
The API is the product here. An integrator's core job with Twelve Labs is done in code —
upload and index video, then search or analyze it through the API/SDK from inside their own
application. The Twelve Labs Playground is a demo/eval console for trying prompts and managing
keys, not the surface where the integration work happens, so this pack ships no mirror and has no
UI capabilities (see ui-scope.json).
Rate budget
Twelve Labs publishes an exact per-minute table by endpoint category and plan. On the Free plan
(and Developer Tier 1, documented as identical): Index 60 RPM, Upload 60 RPM, Search 600 RPM,
Analyze 60 RPM, Embed text/image 600 RPM, Embed video/audio 25 RPM. This pack's declaration
(src/twelvelabs-budget.ts) admits 24 calls/minute at weight 1 — 40% of the tightest tier this
connector can reach, and tighter than the kernel's own fallback — with embed.* priced at 3 so
the window admits 8 embedding calls/minute. Every connector entrypoint applies
guardTwelvelabsClient unconditionally; there is no option that turns it off.
See src/twelvelabs-capabilities.ts for the full manifest (the real vendor surface is the
denominator — coverage is honest and partial until the twin reaches it).
