mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
* fix(capture): let vision captioning authenticate the way a server can Three defects in one phase, all of which end with a capture that reports "Captioned N/N images" and then "0 images captioned with Gemini" — a successful-looking run that hands the agent nothing to see by. 1. Credential. The captioner only accepted an API key. A server deployment holds a service account, not a key, and a rejected key is indistinguishable from an unset one here: every request returns empty text and no error. Vertex is now a first-class provider, ranked above the bare key and below an explicit OPENROUTER_API_KEY opt-in, configured by HYPERFRAMES_VERTEX_SERVICE_ACCOUNT + HYPERFRAMES_VERTEX_PROJECT_ID (region via HYPERFRAMES_VERTEX_LOCATION). It carries its own model default because the Gemini API's flash-lite preview id is not resolvable on Vertex. 2. Empty captions. Thinking tokens are drawn from maxOutputTokens, so a model left free to think can spend the whole budget and return no text — a successful request with no caption. thinkingBudget is pinned to 0; a one-line factual caption needs no reasoning. 3. Native abort. Rasterizing a batch of SVGs concurrently drove up to SVG_BATCH simultaneous librsvg renders through libvips and corrupted the heap: `free(): unaligned chunk detected in tcache 2` (SIGABRT) during this phase, twice in fourteen days, losing the whole capture each time. A native abort cannot be caught, so the concurrency is removed rather than handled — rasterization is serialized and libvips' worker pool is bounded, while the vision requests, which are the slow leg, stay parallel. Throughput barely moves: 225 captions across three real captures, 0 failures, 13-25s each. * test(capture): pin the rasterization loop to one render at a time The serialization fix shipped without a regression test on the grounds that native heap corruption is not unit-testable. The corruption is not, but the property that prevents it is: `sharp` is mocked to record how many renders are in flight, and a six-SVG batch must never reach two. A deliberately slow caption stub makes overlapping renders the faster path, so a future refactor that "optimises" the loop back to `Promise.all` fails here instead of aborting in production. Also covered: `sharp.concurrency(1)` is applied — serializing the loop while leaving libvips' pool at the host core count still fans one render across every core — and an unrasterizable SVG is skipped without breaking serialization for its siblings. Verified as a real guard: reverting only contentExtractor.ts to origin/main fails 7 of the 22 cases in this file. * fix(capture): tell the truth in the asset-descriptions header when Vertex captioned The provider gate in `contentExtractor` accepts Vertex when a project and a service account are both set -- which is the configuration a server deployment actually has. The header written next to the captions still tested only for an API key, so a capture whose captions Vertex had just generated was labelled "GEMINI_API_KEY not set -- descriptions below are catalog-derived". That header is not cosmetic: it travels into the context the template editor reads, telling it to distrust captions that are real. Mirror the same two variables here, and name every provider in the fallback text instead of only the API key. * fix(capture): hand libvips' worker pool back after the renders `sharp.concurrency(1)` is process-global and was set once, for the whole life of the process. The bound is right for the rasterize loop -- a native abort in libvips cannot be caught, so the renders must not overlap -- but its scope was every later sharp caller in the process, none of which asked for captioning, all of them pinned to one thread from then on. Now the host's value is read first and restored in a `finally` around the rasterize loop, so a skipped SVG cannot cost the process its threads either. The vision requests below are network work and gain nothing from a pinned pool. The mock had to grow the getter half of sharp's API -- `concurrency()` with no argument reports the current value -- since save-and-restore is untestable without it. Verified as a real guard: dropping only the restore fails both new cases. Raised by Rames Jusso in review of #3561 and concurred by Magi.