Commit 0d532ae
authored
cmd: add --wait polling framework (PRINFRA-125) (#34)
## Description
Adds `--wait` flag to async commands (video create, video-translate create, video-agent create). When set, the CLI polls the status endpoint with exponential backoff until the resource reaches a terminal state, then outputs the final resource JSON — same as running the corresponding `get` command after completion.
**How it works:**
1. Execute the create request, extract the resource ID from the response (via dot-notation `IDField`, e.g., `data.video_id`)
2. Poll the status endpoint (e.g., `GET /v3/videos/{video_id}`) with exponential backoff (2s initial, up to 30s, with jitter)
3. Check the status field (e.g., `data.status`) against terminal states
4. Success (`completed`) → stdout gets the final resource JSON, exit 0
5. Failure (`failed`) → stdout gets the failure response (contains error details), stderr gets structured error, exit 1
6. Timeout (`--timeout`, default 10m) → stderr gets timeout message with hint, exit 1
**PollConfig registrations** (hand-written in `cmd/heygen/poll_configs.go`):
| Command | Status Endpoint | ID Field | Terminal States |
|---|---|---|---|
| `video create` | `GET /v3/videos/{video_id}` | `data.video_id` | OK: `completed`, Fail: `failed` |
| `video-translate create` | `GET /v3/video-translations/{video_translation_id}` | `data.video_translation_ids.0` | OK: `completed`, Fail: `failed` |
| `video-agent create` | `GET /v3/videos/{video_id}` | `data.video_id` | OK: `completed`, Fail: `failed` |
PollConfigs are looked up at call time in the builder via `pollConfigs[spec.Group+"/"+spec.Name]` — Specs are never mutated. Adding polling for a new async command is one entry in `poll_configs.go`. Field names and terminal states verified against experiment-framework DTOs.
**Timeout:** Owned by `ensurePollContext` inside `ExecuteAndPoll`. The builder passes `cmd.Context()` and `Timeout` in `PollOptions`; `ensurePollContext` adds the deadline. Single timeout owner.
**Context-aware:** Ctrl-C cancellation and timeout both work. Context errors from `Execute()` (wrapped as `network_error` in CLIError) are unwrapped and translated to clean `timeout`/`canceled` CLIErrors.
**Progress:** Only emitted in `--human` mode — one line per status *change* (`Polling: status=processing (elapsed 12s)`). JSON mode keeps stderr clean for machine consumption (structured errors only).
**Failure response preserved:** `ErrPollFailed` carries the full status response. The builder outputs it to stdout (users see error details without a second API call) then returns exit 1.
**`--human` support:** The `--wait` path passes `client.APIDataField` and nil columns to the formatter — single objects render as key-value pairs, matching the corresponding `get` command.
**Batch rejection:** `video-translate create` returns a list of IDs (`video_translation_ids`). `--wait` extracts the first element via array index (`.0`). If the list has >1 element (multi-language batch), returns `batch_not_supported` error with hint to poll manually. Single-language requests work normally.
**`extractJSONPath` supports array indices:** Dot-notation paths like `data.ids.0` walk into arrays. Used for video-translate's list-based ID field.
Stacked on PR #31 (pagination) → PR #29 (retries).
Linear: PRINFRA-125
## Testing
10 executor tests: immediate success, polls-until-complete (3 status calls), failure state with response preserved, timeout during backoff, timeout during HTTP request (server blocks), create fails (no polling), status callback, extractJSONPath (nested, missing, array index, array out of bounds, non-array), batch rejection (3 IDs), single-array-element OK.
5 builder integration tests: wait success (no progress in JSON mode), wait failure (response on stdout), wait timeout, no-wait (create response only), wait on non-pollable command (unknown flag).
1 validation test: all PollConfig keys match generated Specs (catches orphaned configs).
All tests use `httptest.Server` — no real API calls. Polling delays set to 1ms in tests for fast execution.1 parent 89a8115 commit 0d532ae
6 files changed
Lines changed: 1005 additions & 0 deletions
File tree
- cmd/heygen
- internal/client
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
| 11 | + | |
10 | 12 | | |
11 | 13 | | |
12 | 14 | | |
13 | 15 | | |
| 16 | + | |
14 | 17 | | |
15 | 18 | | |
16 | 19 | | |
| |||
42 | 45 | | |
43 | 46 | | |
44 | 47 | | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
45 | 99 | | |
46 | 100 | | |
47 | 101 | | |
| |||
56 | 110 | | |
57 | 111 | | |
58 | 112 | | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
59 | 117 | | |
60 | 118 | | |
61 | 119 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
34 | 46 | | |
35 | 47 | | |
36 | 48 | | |
| |||
90 | 102 | | |
91 | 103 | | |
92 | 104 | | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
93 | 276 | | |
94 | 277 | | |
95 | 278 | | |
| |||
407 | 590 | | |
408 | 591 | | |
409 | 592 | | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
410 | 596 | | |
411 | 597 | | |
412 | 598 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
0 commit comments