run402-mcp 2.2.0 → 2.3.1

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.
@@ -137,6 +137,27 @@ export interface BlobCdnEnvelope {
137
137
  * Tag emitters require the SHA-256 (only computed when uploaded with
138
138
  * `immutable: true`). On non-immutable uploads, calling them throws.
139
139
  */
140
+ /**
141
+ * One image variant generated by the gateway (v1.49+) on `assets.put` for
142
+ * image MIMEs. Three sizes are produced (`thumb` 320w, `medium` 800w,
143
+ * `large` 1920w) plus a full-resolution `display_jpeg` for HEIC/HEIF
144
+ * sources. WebP-only in v1; AVIF is deferred (see `imgTagWithSrcSet`'s
145
+ * JSDoc for the `<picture>` type-precedence footgun).
146
+ */
147
+ export interface AssetVariant {
148
+ /** Mutable URL of the variant (preferred-host form). */
149
+ url: string;
150
+ /** Content-addressed CDN URL of the variant. Recommended for HTML/CSS. */
151
+ cdn_url: string;
152
+ /** Pixel width of the variant in display orientation. */
153
+ width_px: number;
154
+ /** Pixel height of the variant in display orientation. */
155
+ height_px: number;
156
+ /** Encoded format of the variant. */
157
+ format: "webp" | "jpeg";
158
+ /** Hex SHA-256 of the variant bytes. */
159
+ sha256: string;
160
+ }
140
161
  export interface AssetRef {
141
162
  key: string;
142
163
  size_bytes: number;
@@ -176,6 +197,42 @@ export interface AssetRef {
176
197
  /** CloudFront invalidation envelope. For immutable uploads `cdn.ready ===
177
198
  * true` and no further action is needed. */
178
199
  cdn: BlobCdnEnvelope;
200
+ /** Display-oriented width (post-EXIF rotation) of the source image, in
201
+ * pixels. Present only for image MIMEs against a v1.49+ gateway. */
202
+ width_px?: number;
203
+ /** Display-oriented height (post-EXIF rotation) of the source image, in
204
+ * pixels. Present only for image MIMEs against a v1.49+ gateway. */
205
+ height_px?: number;
206
+ /** Blurhash string suitable for a low-quality image placeholder (LQIP).
207
+ * Present only for image MIMEs against a v1.49+ gateway. */
208
+ blurhash?: string;
209
+ /** Variant spec version. v1 in the v1.49 gateway release. Future
210
+ * spec-version bumps produce different variant bytes at different URLs. */
211
+ variant_spec_version?: string;
212
+ /** Browser-renderable URL for the source. For jpeg/png/webp/avif this
213
+ * equals `cdn_url`. For HEIC/HEIF this points to a generated JPEG
214
+ * display variant so apps render correctly without HEIC-aware code. */
215
+ display_url?: string;
216
+ /** Immutable (content-addressed) form of `display_url`. */
217
+ display_immutable_url?: string;
218
+ /** Generated variant set for image MIMEs ≥320×320. Sub-320 images skip
219
+ * the WebP set (the source IS the thumbnail at that size). `display_jpeg`
220
+ * is present only for HEIC/HEIF sources (full-resolution JPEG transcode). */
221
+ variants?: {
222
+ thumb?: AssetVariant;
223
+ medium?: AssetVariant;
224
+ large?: AssetVariant;
225
+ display_jpeg?: AssetVariant;
226
+ };
227
+ /** Convenience: `variants.thumb.cdn_url` if a thumb variant exists,
228
+ * else `display_url` (acts as the thumbnail for sub-320 images). **Undefined
229
+ * for non-image AssetRefs** — TypeScript narrows accordingly; see also
230
+ * `imgTag()`. */
231
+ thumbUrl?: string;
232
+ /** Convenience: `display_url` falling back to `cdn_url` for images.
233
+ * **Undefined for non-image AssetRefs** — TypeScript narrows accordingly;
234
+ * see also `imgTag()`. */
235
+ displayUrl?: string;
179
236
  /**
180
237
  * Returns a ready-to-paste `<script>` tag with the content-addressed
181
238
  * URL + Subresource Integrity + `crossorigin`. The browser will refuse
@@ -234,8 +291,67 @@ export interface AssetRef {
234
291
  * @example
235
292
  * asset.imgTag("Company logo")
236
293
  * // → <img src="https://pr-abc.run402.com/_blob/logo-a1b2c3d4.png" alt="Company logo" loading="lazy" decoding="async">
294
+ *
295
+ * **v1.49+ HEIC handling:** when the source is HEIC/HEIF, the gateway sets
296
+ * `display_url` to a generated JPEG transcode of the source and `cdn_url`
297
+ * to the original HEIC bytes (which browsers can't render). `imgTag()`
298
+ * defaults `<img src>` to `display_url ?? cdn_url`, so HEIC uploads render
299
+ * correctly without HEIC-specific code in the caller. When the ref carries
300
+ * `width_px` and `height_px`, the emitter also adds `width`/`height`
301
+ * attributes to eliminate Cumulative Layout Shift. Non-image refs omit
302
+ * those attributes silently — `imgTag()` never throws on absence.
237
303
  */
238
304
  imgTag(alt?: string): string;
305
+ /**
306
+ * Returns a `<picture>` element with a responsive WebP `<source>` (three
307
+ * sizes: 320w / 800w / 1920w) and the gateway's `display_url` as the
308
+ * `<img>` fallback. Designed for hero images and any layout where the
309
+ * browser should pick the right resolution per viewport.
310
+ *
311
+ * **Both throw conditions fail loud — no silent fallbacks:**
312
+ *
313
+ * - **`opts.sizes` is required.** Without `sizes`, browsers conservatively
314
+ * download the largest candidate in `srcset` (defeating the variant
315
+ * set). The helper throws `LocalError` with a message that names the
316
+ * issue and gives a copy-pasteable example.
317
+ * - **`variants` must be present.** This helper assumes the gateway
318
+ * generated the three WebP variants (image source ≥320×320, encoded
319
+ * successfully). On non-image refs, sub-320 images, or older gateway
320
+ * responses without variants, the helper throws `LocalError` and tells
321
+ * the caller to use `imgTag()` instead. Silent fallback would render a
322
+ * broken layout (no srcset, no responsive benefit) with no diagnostic.
323
+ *
324
+ * **AVIF footgun — why no `<source type="image/avif">`:** `<picture>`
325
+ * browsers select sources by `type` precedence, not best size. A single
326
+ * AVIF source at 1920w would be picked for thumbnails by AVIF-capable
327
+ * browsers, defeating the variant set. AVIF support, if it returns, must
328
+ * land at all three sizes simultaneously OR via a separate `imgTagHero()`
329
+ * helper that opts in explicitly for above-the-fold heroes.
330
+ *
331
+ * @example
332
+ * ref.imgTagWithSrcSet({
333
+ * alt: "Hero",
334
+ * sizes: "(max-width: 800px) 100vw, 1920px",
335
+ * });
336
+ * // → <picture>
337
+ * // <source type="image/webp"
338
+ * // srcset="<thumb-cdn-url> 320w, <medium-cdn-url> 800w, <large-cdn-url> 1920w"
339
+ * // sizes="(max-width: 800px) 100vw, 1920px">
340
+ * // <img src="<display_url>"
341
+ * // alt="Hero"
342
+ * // width="4032" height="3024"
343
+ * // loading="lazy"
344
+ * // decoding="async">
345
+ * // </picture>
346
+ */
347
+ imgTagWithSrcSet(opts: {
348
+ alt?: string;
349
+ /** REQUIRED. Browser `sizes` attribute (e.g. `"100vw"` or
350
+ * `"(max-width: 800px) 100vw, 1920px"`). Throws when missing/empty. */
351
+ sizes: string;
352
+ /** Default `"lazy"`. Pass `"eager"` for above-the-fold heroes. */
353
+ loading?: "lazy" | "eager";
354
+ }): string;
239
355
  }
240
356
  /**
241
357
  * Return type of `client.blobs.put`. v1.45 widens this to AssetRef; the
@@ -1 +1 @@
1
- {"version":3,"file":"assets.types.d.ts","sourceRoot":"","sources":["../../src/namespaces/assets.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"assets.types.d.ts","sourceRoot":"","sources":["../../src/namespaces/assets.types.ts"],"names":[],"mappings":"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"}
@@ -145,5 +145,10 @@ export interface ByteReader {
145
145
  (): Promise<Uint8Array>;
146
146
  label?: string;
147
147
  contentType?: string;
148
+ /** Which spec slice category registered this byte reader. Set by the
149
+ * slice-tagged `remember` in `normalizeReleaseSpec` and surfaces on
150
+ * `content.upload.*` events so callers can group telemetry by slice.
151
+ * Cross-kind CAS dedup escalates the value to `"mixed"`. */
152
+ slice?: "release" | "asset" | "mixed";
148
153
  }
149
154
  //# sourceMappingURL=deploy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAiB3C,OAAO,KAAK,EACV,YAAY,EACZ,sBAAsB,EAQtB,WAAW,EACX,oBAAoB,EAEpB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EAYrB,iBAAiB,EAGjB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,2BAA2B,EAC3B,uBAAuB,EACvB,WAAW,EACX,oBAAoB,EACpB,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAiE3B,qBAAa,MAAM;IACL,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE3C;;;;OAIG;IACG,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,YAAY,CAAC;IA4C9E;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAI3E;;;;OAIG;IACG,IAAI,CACR,IAAI,EAAE,WAAW,EACjB,IAAI,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GACvD,OAAO,CAAC;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;KAAE,CAAC;IAIxE;;;;;OAKG;IACG,MAAM,CACV,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;KACxC,GACA,OAAO,CAAC,IAAI,CAAC;IAWhB;;;;;OAKG;IACG,MAAM,CACV,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;QACJ,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;QACvC,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,OAAO,CAAC,EAAE,MAAM,CAAC;KACb,GACL,OAAO,CAAC,YAAY,CAAC;IAMxB;;;;;;;;;OASG;IACG,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,GACtE,OAAO,CAAC,YAAY,CAAC;IAqBxB;;;;OAIG;IACG,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,GAC9B,OAAO,CAAC,iBAAiB,CAAC;IAmB7B;;;;;;OAMG;IACG,IAAI,CACR,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAC/B,OAAO,CAAC,kBAAkB,CAAC;IA6B9B;;;;;;;;OAQG;IACG,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GACxB,OAAO,CAAC,oBAAoB,CAAC;IAmBhC;;;;OAIG;IACG,UAAU,CAAC,IAAI,EAAE,2BAA2B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2B9E;;;;OAIG;IACG,gBAAgB,CACpB,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,sBAAsB,CAAC;IAqBlC;;;;OAIG;IACG,IAAI,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA+BnE;;;;OAIG;IACG,OAAO,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAW1E;AA04CD;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;iEAG6D;IAC7D,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;CACvC"}
@@ -362,8 +362,29 @@ function requireNonEmptyStringQueryOption(value, label, context) {
362
362
  return value;
363
363
  }
364
364
  // ─── Internal pipeline ───────────────────────────────────────────────────────
365
+ /**
366
+ * Compute the sorted set of slice kinds the spec carried. Surfaces on
367
+ * `commit.phase` and `ready` events so agents can group per-phase
368
+ * telemetry by slice category. `assets` slice → `"asset"`; any of
369
+ * `database` / `functions` / `site` → `"release"`. Order is stable
370
+ * (release before asset).
371
+ */
372
+ function deriveSliceKinds(spec) {
373
+ // Guard against non-object spec — the validate phase throws below
374
+ // (INVALID_SPEC), but this is called before validation in applyOnce so
375
+ // we must not blow up first.
376
+ if (!spec || typeof spec !== "object")
377
+ return [];
378
+ const set = new Set();
379
+ if (spec.database || spec.functions || spec.site)
380
+ set.add("release");
381
+ if (spec.assets)
382
+ set.add("asset");
383
+ return [...set].sort((a, b) => (a === "release" ? -1 : 1));
384
+ }
365
385
  async function applyOnce(client, spec, opts, emit) {
366
386
  const allowWarningCodes = normalizeAllowWarningCodes(opts.allowWarningCodes);
387
+ const sliceKinds = deriveSliceKinds(spec);
367
388
  emit({ type: "plan.started" });
368
389
  const { plan, byteReaders } = await planInternal(client, spec, opts.idempotencyKey);
369
390
  emit({ type: "plan.diff", diff: plan.diff });
@@ -383,16 +404,79 @@ async function applyOnce(client, spec, opts, emit) {
383
404
  // event and resolve before we hit upload.
384
405
  }
385
406
  await uploadMissing(client, spec.project, plan.missing_content, byteReaders, emit);
386
- emit({ type: "commit.phase", phase: "validate", status: "started" });
407
+ emit({
408
+ type: "commit.phase",
409
+ phase: "validate",
410
+ status: "started",
411
+ ...(sliceKinds.length > 0 ? { slice_kinds: sliceKinds } : {}),
412
+ });
387
413
  const { planId } = requirePersistedPlan(plan, "applying deploy");
388
414
  const commit = await commitInternal(client, planId, opts.idempotencyKey);
389
- const result = await pollUntilReady(client, commit, plan.diff, plan.warnings, emit, spec.project);
415
+ const result = await pollUntilReady(client, commit, plan.diff, plan.warnings, emit, spec.project, sliceKinds);
390
416
  // v1.48 unified-apply: thread the plan response's `asset_entries[]` back
391
417
  // into DeployResult.assets so callers reading `result.assets.byKey[key]`
392
418
  // get the gateway-authoritative `AssetRef` envelope (URLs, SRI, etag).
393
419
  // Release-only applies leave `result.assets` undefined.
394
420
  if (plan.asset_entries && plan.asset_entries.length > 0) {
395
421
  result.assets = buildAssetManifestFromPlanEntries(plan.asset_entries);
422
+ // v1.49 image-variant follow-up: variants are generated AT COMMIT TIME
423
+ // (parent gateway change Section 5 — `prepareStagedAssetVariants` runs
424
+ // before the activation txn opens). The plan that funded `result.assets`
425
+ // was built BEFORE commit, so its `asset_entries[].asset_ref` doesn't
426
+ // include the variant fields (the gateway only threads `image_data`
427
+ // through `buildAssetRefForPlan` for SHAs that ALREADY have variants
428
+ // in `internal.blob_image_variants`).
429
+ //
430
+ // For image puts, do a dry-run re-plan with the same spec. Bytes are
431
+ // now in CAS (the commit just landed) AND variant rows exist in the
432
+ // DB. The new plan response surfaces the variants. Dry-run keeps it
433
+ // cheap: no new plan_id or operation_id rows are created.
434
+ //
435
+ // Best-effort: a re-plan failure shouldn't fail the apply. The bytes
436
+ // are committed, the release is live, and the variant fields are
437
+ // strictly additive — leaving them empty when the recheck errors is
438
+ // worse than failing the apply.
439
+ const hasImagePut = spec.assets?.put?.some((entry) => {
440
+ const ct = ("content_type" in entry && typeof entry.content_type === "string")
441
+ ? entry.content_type
442
+ : "";
443
+ return ct.startsWith("image/");
444
+ });
445
+ if (hasImagePut) {
446
+ try {
447
+ const { plan: recheck } = await planInternal(client, spec, undefined, true);
448
+ if (recheck.asset_entries && recheck.asset_entries.length > 0) {
449
+ for (const recheckEntry of recheck.asset_entries) {
450
+ const existing = result.assets.byKey[recheckEntry.key];
451
+ if (!existing)
452
+ continue;
453
+ const ref = recheckEntry.asset_ref;
454
+ if (ref.width_px !== undefined)
455
+ existing.width_px = ref.width_px;
456
+ if (ref.height_px !== undefined)
457
+ existing.height_px = ref.height_px;
458
+ if (ref.blurhash !== undefined)
459
+ existing.blurhash = ref.blurhash;
460
+ if (ref.variant_spec_version !== undefined) {
461
+ existing.variant_spec_version = ref.variant_spec_version;
462
+ }
463
+ if (ref.display_url !== undefined)
464
+ existing.display_url = ref.display_url;
465
+ if (ref.display_immutable_url !== undefined) {
466
+ existing.display_immutable_url = ref.display_immutable_url;
467
+ }
468
+ if (ref.variants !== undefined)
469
+ existing.variants = ref.variants;
470
+ }
471
+ }
472
+ }
473
+ catch {
474
+ // Best-effort: leave variant fields unpopulated rather than
475
+ // failing a successful apply. Consumers can re-call
476
+ // `r.assets.put` with the same bytes (dedup) to trigger the
477
+ // plan-time surfacing if variants are missing.
478
+ }
479
+ }
396
480
  }
397
481
  return result;
398
482
  }
@@ -873,6 +957,7 @@ async function uploadMissing(client, projectId, presence, byteReaders, emit) {
873
957
  label: reader?.label ?? p.sha256,
874
958
  sha256: p.sha256,
875
959
  reason: "present",
960
+ ...(reader?.slice ? { slice_kind: reader.slice } : {}),
876
961
  });
877
962
  }
878
963
  // Filter to refs the gateway reported as missing for this project.
@@ -925,6 +1010,7 @@ async function uploadMissing(client, projectId, presence, byteReaders, emit) {
925
1010
  sha256: session.sha256,
926
1011
  done,
927
1012
  total,
1013
+ ...(reader.slice ? { slice_kind: reader.slice } : {}),
928
1014
  });
929
1015
  }
930
1016
  // Plan-level finalize — marks the plan committed in the deploy_plans
@@ -1040,7 +1126,7 @@ async function putToS3(fetchFn, url, body, checksumBase64, partNumber) {
1040
1126
  }
1041
1127
  return res.headers.get("etag");
1042
1128
  }
1043
- async function pollUntilReady(client, commit, diff, warnings, emit, projectId) {
1129
+ async function pollUntilReady(client, commit, diff, warnings, emit, projectId, sliceKinds = []) {
1044
1130
  if (commit.status === "failed") {
1045
1131
  throw translateGatewayError(commit.error, "commit", null, commit.operation_id);
1046
1132
  }
@@ -1054,7 +1140,12 @@ async function pollUntilReady(client, commit, diff, warnings, emit, projectId) {
1054
1140
  context: "committing deploy",
1055
1141
  });
1056
1142
  }
1057
- emit({ type: "ready", releaseId: commit.release_id, urls: commit.urls });
1143
+ emit({
1144
+ type: "ready",
1145
+ releaseId: commit.release_id,
1146
+ urls: commit.urls,
1147
+ ...(sliceKinds.length > 0 ? { slice_kinds: sliceKinds } : {}),
1148
+ });
1058
1149
  return {
1059
1150
  release_id: commit.release_id,
1060
1151
  operation_id: commit.operation_id,
@@ -1065,9 +1156,16 @@ async function pollUntilReady(client, commit, diff, warnings, emit, projectId) {
1065
1156
  }
1066
1157
  const opHeaders = projectId ? await apikeyHeaders(client, projectId) : {};
1067
1158
  const initialSnapshot = await client.request(`/apply/v1/operations/${encodeURIComponent(commit.operation_id)}`, { headers: opHeaders, context: "fetching deploy operation" });
1068
- return await pollSnapshotUntilReady(client, initialSnapshot, diff, warnings, emit, projectId);
1069
- }
1070
- async function pollSnapshotUntilReady(client, initial, diff, warnings, emit, projectId) {
1159
+ return await pollSnapshotUntilReady(client, initialSnapshot, diff, warnings, emit, projectId, sliceKinds);
1160
+ }
1161
+ async function pollSnapshotUntilReady(client, initial, diff, warnings, emit, projectId, sliceKinds = []) {
1162
+ // Helper to spread slice_kinds onto every commit.phase / ready emit so
1163
+ // agents grouping per-slice telemetry don't need to track the apply's
1164
+ // spec separately. The low-level commit/upload helpers that pass no
1165
+ // sliceKinds get an empty array → field is omitted from events.
1166
+ const withSliceKinds = (ev) => sliceKinds.length > 0
1167
+ ? { ...ev, slice_kinds: sliceKinds }
1168
+ : ev;
1071
1169
  let snapshot = initial;
1072
1170
  const opHeaders = projectId ? await apikeyHeaders(client, projectId) : {};
1073
1171
  let lastPhaseEmitted = null;
@@ -1107,7 +1205,7 @@ async function pollSnapshotUntilReady(client, initial, diff, warnings, emit, pro
1107
1205
  return;
1108
1206
  if (nextPhase !== undefined && prev.phase === nextPhase)
1109
1207
  return;
1110
- emit({ type: "commit.phase", phase: prev.phase, status: closeStatus });
1208
+ emit(withSliceKinds({ type: "commit.phase", phase: prev.phase, status: closeStatus }));
1111
1209
  };
1112
1210
  while (true) {
1113
1211
  if (lastPhaseEmitted !== snapshot.status) {
@@ -1115,7 +1213,7 @@ async function pollSnapshotUntilReady(client, initial, diff, warnings, emit, pro
1115
1213
  if (ev) {
1116
1214
  if (ev.type === "commit.phase")
1117
1215
  closePreviousPhase(ev.phase);
1118
- emit(ev);
1216
+ emit(withSliceKinds(ev));
1119
1217
  lastPhaseEmitted = snapshot.status;
1120
1218
  }
1121
1219
  // If `ev` is null (status not in the phase map, e.g. "ready"), leave
@@ -1133,7 +1231,7 @@ async function pollSnapshotUntilReady(client, initial, diff, warnings, emit, pro
1133
1231
  });
1134
1232
  }
1135
1233
  closePreviousPhase();
1136
- emit({ type: "ready", releaseId: snapshot.release_id, urls: snapshot.urls });
1234
+ emit(withSliceKinds({ type: "ready", releaseId: snapshot.release_id, urls: snapshot.urls }));
1137
1235
  return {
1138
1236
  release_id: snapshot.release_id,
1139
1237
  operation_id: snapshot.operation_id,
@@ -1215,12 +1313,18 @@ async function startInternal(client, spec, opts) {
1215
1313
  reason: plan.payment_required.reason,
1216
1314
  });
1217
1315
  }
1316
+ const sliceKinds = deriveSliceKinds(spec);
1218
1317
  const resultPromise = (async () => {
1219
1318
  await uploadMissing(client, spec.project, plan.missing_content, byteReaders, emit);
1220
- emit({ type: "commit.phase", phase: "validate", status: "started" });
1319
+ emit({
1320
+ type: "commit.phase",
1321
+ phase: "validate",
1322
+ status: "started",
1323
+ ...(sliceKinds.length > 0 ? { slice_kinds: sliceKinds } : {}),
1324
+ });
1221
1325
  const { planId } = requirePersistedPlan(plan, "starting deploy");
1222
1326
  const commit = await commitInternal(client, planId, opts.idempotencyKey);
1223
- return await pollUntilReady(client, commit, plan.diff, plan.warnings, emit, spec.project);
1327
+ return await pollUntilReady(client, commit, plan.diff, plan.warnings, emit, spec.project, sliceKinds);
1224
1328
  })();
1225
1329
  // Avoid an unhandled-rejection at construction time. Consumers must call
1226
1330
  // .result() to actually observe the error.
@@ -2033,7 +2137,13 @@ function invalidSecretSpec(message, resource) {
2033
2137
  }
2034
2138
  async function normalizeReleaseSpec(client, spec) {
2035
2139
  const byteReaders = new Map();
2036
- const remember = (resolved) => {
2140
+ // Slice-tagged `remember`. Each slice category creates its own remember
2141
+ // closure so the registered reader carries `reader.slice = "release" |
2142
+ // "asset"`. On cross-kind dedup (same SHA from both a release-bound
2143
+ // slice and the asset slice) the value escalates to `"mixed"`. This
2144
+ // value surfaces on `content.upload.*` events so agents can group
2145
+ // upload telemetry by slice kind.
2146
+ const makeRemember = (slice) => (resolved) => {
2037
2147
  // Propagate the final content-type onto the deferred reader so the CAS
2038
2148
  // upload session can declare it correctly. Callers may set
2039
2149
  // ref.contentType *after* resolveContent returns (e.g. normalizeFileSet
@@ -2042,18 +2152,25 @@ async function normalizeReleaseSpec(client, spec) {
2042
2152
  resolved.reader.contentType = resolved.ref.contentType;
2043
2153
  }
2044
2154
  if (!byteReaders.has(resolved.ref.sha256)) {
2155
+ resolved.reader.slice = slice;
2045
2156
  byteReaders.set(resolved.ref.sha256, resolved.reader);
2046
2157
  }
2047
2158
  else {
2048
2159
  // Already remembered — but if the existing reader has no contentType
2049
- // and we just learned it, fill it in.
2160
+ // and we just learned it, fill it in. Also escalate slice tag when
2161
+ // the second registration comes from a different kind.
2050
2162
  const existing = byteReaders.get(resolved.ref.sha256);
2051
2163
  if (resolved.ref.contentType && !existing.contentType) {
2052
2164
  existing.contentType = resolved.ref.contentType;
2053
2165
  }
2166
+ if (existing.slice && existing.slice !== slice && existing.slice !== "mixed") {
2167
+ existing.slice = "mixed";
2168
+ }
2054
2169
  }
2055
2170
  return resolved.ref;
2056
2171
  };
2172
+ const rememberRelease = makeRemember("release");
2173
+ const rememberAsset = makeRemember("asset");
2057
2174
  const normalized = { project: spec.project };
2058
2175
  if (spec.base)
2059
2176
  normalized.base = spec.base;
@@ -2074,19 +2191,19 @@ async function normalizeReleaseSpec(client, spec) {
2074
2191
  db.zero_downtime = spec.database.zero_downtime;
2075
2192
  }
2076
2193
  if (spec.database.migrations && spec.database.migrations.length > 0) {
2077
- db.migrations = await Promise.all(spec.database.migrations.map(async (m) => normalizeMigration(client, spec.project, m, remember)));
2194
+ db.migrations = await Promise.all(spec.database.migrations.map(async (m) => normalizeMigration(client, spec.project, m, rememberRelease)));
2078
2195
  }
2079
2196
  normalized.database = db;
2080
2197
  }
2081
2198
  if (spec.functions) {
2082
2199
  const fns = {};
2083
2200
  if (spec.functions.replace) {
2084
- fns.replace = await normalizeFunctionMap(spec.functions.replace, remember);
2201
+ fns.replace = await normalizeFunctionMap(spec.functions.replace, rememberRelease);
2085
2202
  }
2086
2203
  if (spec.functions.patch) {
2087
2204
  fns.patch = {};
2088
2205
  if (spec.functions.patch.set) {
2089
- fns.patch.set = await normalizeFunctionMap(spec.functions.patch.set, remember);
2206
+ fns.patch.set = await normalizeFunctionMap(spec.functions.patch.set, rememberRelease);
2090
2207
  }
2091
2208
  if (spec.functions.patch.delete)
2092
2209
  fns.patch.delete = spec.functions.patch.delete;
@@ -2096,7 +2213,7 @@ async function normalizeReleaseSpec(client, spec) {
2096
2213
  if (spec.site) {
2097
2214
  const publicPaths = "public_paths" in spec.site ? spec.site.public_paths : undefined;
2098
2215
  if ("replace" in spec.site && spec.site.replace) {
2099
- const map = await normalizeFileSet(spec.site.replace, remember);
2216
+ const map = await normalizeFileSet(spec.site.replace, rememberRelease);
2100
2217
  normalized.site = {
2101
2218
  replace: map,
2102
2219
  ...(publicPaths ? { public_paths: publicPaths } : {}),
@@ -2105,7 +2222,7 @@ async function normalizeReleaseSpec(client, spec) {
2105
2222
  else if ("patch" in spec.site && spec.site.patch) {
2106
2223
  const patch = {};
2107
2224
  if (spec.site.patch.put) {
2108
- patch.put = await normalizeFileSet(spec.site.patch.put, remember);
2225
+ patch.put = await normalizeFileSet(spec.site.patch.put, rememberRelease);
2109
2226
  }
2110
2227
  if (spec.site.patch.delete)
2111
2228
  patch.delete = spec.site.patch.delete;
@@ -2123,7 +2240,7 @@ async function normalizeReleaseSpec(client, spec) {
2123
2240
  // register a byte-reader; emit the wire-shaped `AssetPutEntry[]`.
2124
2241
  // Cross-kind SHA dedup is automatic via the shared `byteReaders` map.
2125
2242
  if (spec.assets) {
2126
- normalized.assets = await normalizeAssetSlice(spec.assets, remember);
2243
+ normalized.assets = await normalizeAssetSlice(spec.assets, rememberAsset);
2127
2244
  }
2128
2245
  return { normalized, byteReaders };
2129
2246
  }
@@ -2186,20 +2303,41 @@ function buildAssetManifestFromPlanEntries(entries) {
2186
2303
  let bytesUploaded = 0;
2187
2304
  let bytesReused = 0;
2188
2305
  for (const entry of entries) {
2306
+ const ref = entry.asset_ref;
2189
2307
  const e = {
2190
2308
  key: entry.key,
2191
2309
  sha256: entry.sha256,
2192
2310
  size_bytes: entry.size_bytes,
2193
2311
  content_type: entry.content_type,
2194
2312
  visibility: entry.visibility,
2195
- url: entry.asset_ref.url,
2196
- immutable_url: entry.asset_ref.immutable_url,
2197
- cdn_url: entry.asset_ref.cdn_url,
2198
- cdn_immutable_url: entry.asset_ref.cdn_immutable_url,
2199
- sri: entry.asset_ref.sri,
2200
- etag: entry.asset_ref.etag,
2201
- content_digest: entry.asset_ref.content_digest,
2313
+ url: ref.url,
2314
+ immutable_url: ref.immutable_url,
2315
+ cdn_url: ref.cdn_url,
2316
+ cdn_immutable_url: ref.cdn_immutable_url,
2317
+ sri: ref.sri,
2318
+ etag: ref.etag,
2319
+ content_digest: ref.content_digest,
2202
2320
  };
2321
+ // v1.49+ image-variant pass-through. Only emitted when the gateway
2322
+ // returned them (image MIMEs ≥320×320; HEIC/HEIF sources also include
2323
+ // `display_jpeg`). Pre-v1.49 plan responses omit these fields entirely
2324
+ // and the manifest entry stays bytewise-identical to before.
2325
+ if (ref.width_px !== undefined)
2326
+ e.width_px = ref.width_px;
2327
+ if (ref.height_px !== undefined)
2328
+ e.height_px = ref.height_px;
2329
+ if (ref.blurhash !== undefined)
2330
+ e.blurhash = ref.blurhash;
2331
+ if (ref.variant_spec_version !== undefined) {
2332
+ e.variant_spec_version = ref.variant_spec_version;
2333
+ }
2334
+ if (ref.display_url !== undefined)
2335
+ e.display_url = ref.display_url;
2336
+ if (ref.display_immutable_url !== undefined) {
2337
+ e.display_immutable_url = ref.display_immutable_url;
2338
+ }
2339
+ if (ref.variants !== undefined)
2340
+ e.variants = ref.variants;
2203
2341
  list.push(e);
2204
2342
  byKey[entry.key] = e;
2205
2343
  manifest[entry.key] = e;