|
2 | 2 |
|
3 | 3 | ## [Unreleased] |
4 | 4 |
|
| 5 | +## [1.31.0] - 2026-06-10 |
| 6 | + |
5 | 7 | ### Added |
6 | 8 |
|
7 | | -- **OpenRouter async video lifecycle** — mocks OpenRouter's dedicated video-generation job API |
8 | | - alongside the existing OpenAI-shaped `/v1/videos` handler; both draw from the same |
9 | | - `endpoint: "video"` fixture pool. `POST /api/v1/videos` matches fixtures on `prompt`/`model` |
10 | | - (endpoint `video`) and returns a `{ id, polling_url, status: "pending" }` job envelope (a |
11 | | - model-less submit assumes the default `bytedance/seedance-2.0` model for fixture matching); the |
12 | | - envelope reports `"pending"` for API fidelity even though, under the default thresholds, the job |
13 | | - is already seeded terminal internally at submit. `GET /api/v1/videos/{jobId}` advances |
14 | | - `pending → in_progress → completed | failed` per the new `openRouterVideo` poll-threshold config |
15 | | - (same poll-threshold semantics as `falQueue`), adding `unsigned_urls` + `usage.cost` on |
16 | | - completion and the fixture's `error` message on failure (a default "Video generation failed" |
17 | | - message is used when the fixture has no `error` field). Because the job is server-side terminal |
18 | | - at submit by default, content is downloadable with zero polls if the client constructs the |
19 | | - content URL itself — the documented client flow still needs one status poll to learn |
20 | | - `unsigned_urls`. `GET /api/v1/videos/{jobId}/content?index=0` requires Bearer auth (401 |
21 | | - otherwise) and serves the fixture's `b64` bytes — or a built-in minimal MP4 `ftyp` placeholder — |
22 | | - always as `Content-Type: video/mp4` (matching production even when the client sends |
23 | | - `Accept: application/octet-stream`). Status polls and the models listing are served without auth |
24 | | - — only the content endpoint enforces Bearer (a deliberate, documented divergence). The `index` |
25 | | - query param is accepted but ignored (jobs are single-video) — except when the content endpoint |
26 | | - live-proxies an upstream (record mode's proxy-only operation, or the in-flight capture window |
27 | | - below), where it selects the position-aligned upstream `unsigned_urls` entry — and the content |
28 | | - endpoint does not advance job state — content URLs are only learned from a completed status poll |
29 | | - (API fidelity; diverges from fal's advance-on-result). `GET /api/v1/videos/models` synthesizes |
30 | | - the video-model |
31 | | - listing from loaded video fixtures that specify a string `match.model` (falling back to a |
32 | | - built-in default set otherwise). Video fixtures gain optional `error`, `b64`, and `cost` fields. |
33 | | - Record mode is supported via the `"openrouter"` provider key |
34 | | - (`record.providers.openrouter` / `--provider-openrouter <url>`) as a live interactive proxy: an |
35 | | - unmatched submit is forwarded upstream and answered with a mock-rewritten envelope (fresh aimock |
36 | | - jobId, `polling_url` pointing back at the mock with the testId embedded), and each client poll |
37 | | - is proxied upstream and relayed with the mock identifiers substituted — `id`, a present |
38 | | - `polling_url`, and a present `unsigned_urls` array (same length, one mock content URL per index) |
39 | | - are rewritten (a non-array `unsigned_urls` cannot be index-rewritten and is stripped from the |
40 | | - relay with a warning instead of leaking the upstream value); every other field (including |
41 | | - `usage`, untouched — a non-number `usage.cost` warns but passes through) is relayed verbatim. |
42 | | - When the upstream reports `completed`, the poll is relayed immediately and the eager capture |
43 | | - runs in the background: `unsigned_urls[0]` is origin-checked against the configured provider |
44 | | - and fetched server-side after the first completed poll has been answered — an SDK poller is |
45 | | - never blocked on a multi-minute video download; the polling client's Bearer is forwarded only |
46 | | - same-origin (off-origin content hosts are fetched without auth, with a warning) — and the |
47 | | - bytes are persisted as a normal video fixture (`match.userMessage` = prompt, `match.model` = |
48 | | - the submitted model as recorded by the standard model-normalization rules — date suffixes |
49 | | - stripped unless `recordFullModelVersion`; model-less submits record the assumed default model |
50 | | - — `video.id` = the upstream job id) that replays in-session and across sessions. While the |
51 | | - capture is in flight, the job is already observable as completed: status polls relay the |
52 | | - upstream body and the content endpoint live-proxies downloads (no 400 window). A capture |
53 | | - FAILURE — no usable `unsigned_urls`, an invalid content URL, or a content fetch that errors on |
54 | | - headers, status, or body — persists NOTHING and leaves the job a live proxy, so the next |
55 | | - completed poll retries the capture (each failure warns); the only degraded persist (fixture |
56 | | - written without `b64`) is the over-cap path below, where a retry would always re-exceed the cap. |
57 | | - `failed` jobs persist `{ status: "failed", error }` fixtures; `cancelled`/`expired` upstream |
58 | | - statuses (not representable in `video.status`) pass through verbatim with a warning and are |
59 | | - never recorded; an upstream `failed` body whose `error` is the canonical |
60 | | - `{ message, code }` object records the extracted message (an unusable non-null `error` value |
61 | | - warns and is omitted from the fixture; an explicit `null` is omitted without the warn). |
62 | | - Recorded `b64` is capped at `record.openRouterVideo.maxContentBytes` |
63 | | - (default 32 MB decoded, `0` = unlimited, negative/non-integer values are treated as the default |
64 | | - with a `createServer` warning; exported as `OPENROUTER_VIDEO_DEFAULT_MAX_CONTENT_BYTES`): the |
65 | | - cap guards memory as well as disk — a capture whose upstream response declares an over-cap |
66 | | - `Content-Length` is skipped without downloading, an undeclared-length capture is streamed with |
67 | | - the byte count enforced during the read (on exceed the download aborts and nothing oversized is |
68 | | - retained), and in both cases the fixture is persisted without `b64` (with a `_warning` in the |
69 | | - fixture file) and the placeholder is served even same-session. `GET /api/v1/videos/models` is |
70 | | - relayed verbatim from the upstream in record mode (journaled `source: "proxy"`, never recorded |
71 | | - as a fixture), falling back to the fixture-driven synthesis on upstream failure; strict mode |
72 | | - disables the models proxy entirely. Strict mode wins over record (503 on a no-match, nothing |
73 | | - proxied — a chaos roll on a strict no-fixture submit is labeled `source: "internal"`), and an |
74 | | - effective-strict request also 503s a record job's status polls and live-proxied content |
75 | | - downloads ("strict means nothing reaches an upstream", honoring per-request `X-AIMock-Strict` |
76 | | - overrides). A missing provider URL warns and falls through to 404; upstream failures return 502 |
77 | | - `proxy_error` (a hung models fetch instead falls back 200 to the synthesized listing). The |
78 | | - small-JSON upstream fetches (submit, poll, models) honor `record.upstreamTimeoutMs` (default |
79 | | - 30s) as a total deadline; the byte-bearing content fetches (eager capture, content relay) gate |
80 | | - only the response headers on `upstreamTimeoutMs` and stream the body under |
81 | | - `record.bodyTimeoutMs` idle semantics (re-armed per chunk), so a steadily-downloading long |
82 | | - render never times out — only a mid-body stall aborts. Every proxied journal entry carries |
83 | | - `source: "proxy"` (the synthesized models fallback after a failed proxy attempt is labeled |
84 | | - `source: "internal"`), a fixture-write failure surfaces as an `X-AIMock-Record-Error` header on |
85 | | - the relayed poll on the failed branch (the completed branch's capture is detached from the |
86 | | - relay, so its persist failures are logged instead), and each successful (2xx) proxied poll, each |
87 | | - replay-job poll, AND each successful content serve/relay refreshes the job's 1-hour TTL so long |
88 | | - generations — and long download sessions — are not evicted mid-recording (or mid-polling); a |
89 | | - relayed upstream 401/403 does NOT refresh the TTL. |
90 | | - Upstream `401`/`403` rejections pass through to the client verbatim on all three proxied |
91 | | - surfaces — submit, status poll, and content download — for real-API fidelity (the relayed |
92 | | - 401/403 bodies deliberately bypass the mock's identifier/URL rewriting: auth-error envelopes |
93 | | - carry no job URLs, and fidelity of the provider's error body wins). Disabling recording |
94 | | - mid-flight (`disableRecording()`) makes |
95 | | - every later poll of an orphaned record job fail loudly with 502 before contacting the upstream — |
96 | | - and content downloads too, when the job is completed (a pending/in_progress orphan's download |
97 | | - still 400s as not-completed first; the 502 gate sits behind the completed check). Under |
98 | | - `record.proxyOnly` nothing is persisted or cached: jobs stay |
99 | | - live proxies after terminal polls and content downloads are live-proxied from the stored |
100 | | - upstream `unsigned_urls[index]` on every fetch, with the bytes STREAMED to the client as they |
101 | | - arrive — the video is never buffered in memory and the first bytes reach the client while the |
102 | | - upstream is still sending (the same streamed relay serves capture-window downloads; |
103 | | - position-aligned with the relayed array; an out-of-range or unusable index warns and falls back |
104 | | - to index 0; same same-origin Bearer gate as the capture path; an upstream 401/403 on the |
105 | | - content fetch passes through to the client for real-API fidelity). The four |
106 | | - `handleOpenRouterVideo*` handlers, |
107 | | - `OpenRouterVideoJobMap`, and `OPENROUTER_VIDEO_MAX_ENTRIES` are exported from the package root |
108 | | - (matching the sibling video/fal surfaces). The CLI warns when |
109 | | - `--upstream-timeout-ms`/`--body-timeout-ms` — or any `--provider-*` flag — are passed without |
110 | | - `--record`/`--proxy-only` (previously parsed and silently dropped). |
| 9 | +- OpenRouter async video job lifecycle mock — submit, poll, content download, model listing (#262) |
| 10 | +- Record-mode live proxying for the OpenRouter video surface; captured videos replay later (#265) |
111 | 11 |
|
112 | 12 | ### Changed |
113 | 13 |
|
114 | | -- **Proxy header forwarding (all providers)** — requests forwarded to a recording upstream now |
115 | | - strip the mock-internal control headers `x-test-id`, `x-aimock-strict`, `x-aimock-context`, and |
116 | | - the `x-aimock-chaos-*` prefix family on every provider proxy path (not just the new OpenRouter |
117 | | - video surface). These headers are meaningless — and potentially confusing or leaky — on a real |
118 | | - provider's wire; everything else still passes through as before. Note that because `x-test-id` |
119 | | - is now stripped upstream, recording THROUGH a second aimock instance loses testId scoping on |
120 | | - the inner instance. |
121 | | -- **fal queue walk: same-origin envelope URLs** — the recording queue walk (fal and the legacy |
122 | | - fal-audio path) now adopts the `status_url`/`response_url` an upstream submit envelope nominates |
123 | | - only when they are same-origin with the configured upstream (the gate the OpenRouter video proxy |
124 | | - applies to envelope `polling_url`s). Every walk fetch forwards the client's headers — including |
125 | | - `Authorization` — so an envelope nominating a foreign host must never receive them; off-origin, |
126 | | - unparseable, or absent envelope URLs fall back to the constructed canonical paths on the |
127 | | - upstream origin, with a warning. |
128 | | - |
129 | | -### Fixed |
130 | | - |
131 | | -- **fal queue thresholds** — the progression resolver — now shared with the OpenRouter video |
132 | | - surface — also sanitizes the pre-existing `falQueue` config: non-finite |
133 | | - `pollsBeforeInProgress`/`pollsBeforeCompleted` values (NaN, Infinity) are treated as unset instead |
134 | | - of stranding jobs short of a terminal status, negative/fractional values are floored and clamped |
135 | | - to non-negative integers, and `createServer` now warns on invalid `falQueue`/`openRouterVideo` |
136 | | - threshold values. |
137 | | -- **fal queue walk: per-fetch timeout** — each of the walk's submit/status/result fetches is now |
138 | | - bounded by `record.upstreamTimeoutMs` (30s default, additionally clamped to the walk's remaining |
139 | | - `record.fal.timeoutMs` budget). Previously the walk-level timeout only fired BETWEEN polls, so a |
140 | | - hung upstream socket could pin the walk indefinitely; it now surfaces through the existing |
141 | | - 502/no-fixture failure handling. |
142 | | -- **fal queue walk: `pollIntervalMs: 0`** — a zero poll interval no longer produces a spurious |
143 | | - "Queue walk timed out" on the first non-terminal poll; the walk only times out when the |
144 | | - `timeoutMs` budget is actually exhausted ("poll as fast as possible" is a valid configuration). |
145 | | -- **fal/fal-audio queue-walk persist failures** — a fixture-write failure on the queue-walk record |
146 | | - paths now surfaces as an `X-AIMock-Record-Error` header on the synthesized envelope (parity with |
147 | | - the generic recorder relay and the OpenRouter video failed branch). |
| 14 | +- Recording proxies now strip aimock-internal control headers on every provider path (#265) |
| 15 | + |
| 16 | +### Fixed |
| 17 | + |
| 18 | +- Recorder and fal record paths hardened — timeouts, threshold sanitizing, persist errors (#265) |
| 19 | +- attw ^0.18 fixes the test:exports crash (#263); OpenAI /v1/videos docs corrected (#264) |
148 | 20 |
|
149 | 21 | ## [1.30.0] - 2026-06-09 |
150 | 22 |
|
|
0 commit comments