@@ -23,6 +23,7 @@ import {
2323 getSchemaParseFn ,
2424 lifecycleHooks ,
2525 makeIdempotencyKey ,
26+ packetRequiresOffloading ,
2627 parsePacket ,
2728 RateLimitError ,
2829 resourceCatalog ,
@@ -1655,6 +1656,12 @@ async function executeBatchTwoPhase(
16551656 } ,
16561657 requestOptions ?: TriggerApiRequestOptions
16571658) : Promise < { id : string ; runCount : number ; publicAccessToken : string ; taskIdentifiers : string [ ] } > {
1659+ // Offload any oversized per-item payloads to object storage before streaming, so a batch
1660+ // of large items keeps the request body small — the same treatment single triggers get.
1661+ // Both the array and streaming batch paths funnel through here, so this is the one place
1662+ // batch offloading needs to happen.
1663+ items = await offloadBatchItemPayloads ( items , apiClient ) ;
1664+
16581665 let batch : Awaited < ReturnType < typeof apiClient . createBatch > > | undefined ;
16591666
16601667 try {
@@ -1701,6 +1708,81 @@ async function executeBatchTwoPhase(
17011708 } ;
17021709}
17031710
1711+ /**
1712+ * Offload any oversized per-item payloads in a batch to object storage, mirroring the
1713+ * single-trigger offload path. Small payloads pass through untouched. Every returned item
1714+ * carries `options.payloadSize` (the pre-offload serialized byte size) so the pipeline can
1715+ * reason about payload size without downloading an "application/store" reference.
1716+ *
1717+ * Uploads run with bounded concurrency so a batch of large items doesn't fire an unbounded
1718+ * number of simultaneous presigned PUTs.
1719+ *
1720+ * @internal Exported for testing.
1721+ */
1722+ export async function offloadBatchItemPayloads (
1723+ items : BatchItemNDJSON [ ] ,
1724+ apiClient : ApiClient ,
1725+ concurrency = 10
1726+ ) : Promise < BatchItemNDJSON [ ] > {
1727+ if ( items . length === 0 ) {
1728+ return items ;
1729+ }
1730+
1731+ const results = new Array < BatchItemNDJSON > ( items . length ) ;
1732+ let cursor = 0 ;
1733+
1734+ async function worker ( ) {
1735+ while ( cursor < items . length ) {
1736+ const index = cursor ++ ;
1737+ results [ index ] = await offloadBatchItemPayload ( items [ index ] ! , apiClient ) ;
1738+ }
1739+ }
1740+
1741+ await Promise . all ( Array . from ( { length : Math . min ( concurrency , items . length ) } , ( ) => worker ( ) ) ) ;
1742+
1743+ return results ;
1744+ }
1745+
1746+ async function offloadBatchItemPayload (
1747+ item : BatchItemNDJSON ,
1748+ apiClient : ApiClient
1749+ ) : Promise < BatchItemNDJSON > {
1750+ // The batch builders serialize each payload to a string via stringifyIO before we get
1751+ // here; anything else has no inline body to offload or measure.
1752+ if ( typeof item . payload !== "string" || item . payload . length === 0 ) {
1753+ return item ;
1754+ }
1755+
1756+ const dataType = item . options ?. payloadType ?? "application/json" ;
1757+
1758+ // Already an object-store reference — the caller pre-offloaded it, nothing to do.
1759+ if ( dataType === "application/store" ) {
1760+ return item ;
1761+ }
1762+
1763+ const packet : IOPacket = { data : item . payload , dataType } ;
1764+ // Measure before offload so the size reflects the real payload, not the small reference
1765+ // we may replace it with.
1766+ const { size : payloadSize } = packetRequiresOffloading ( packet ) ;
1767+
1768+ const exported = await conditionallyExportPacket (
1769+ packet ,
1770+ createTriggerPayloadPathPrefix ( item . task ) ,
1771+ undefined ,
1772+ apiClient
1773+ ) ;
1774+
1775+ return {
1776+ ...item ,
1777+ payload : exported . data ,
1778+ options : {
1779+ ...item . options ,
1780+ payloadType : exported . dataType ,
1781+ payloadSize,
1782+ } ,
1783+ } ;
1784+ }
1785+
17041786/**
17051787 * Error thrown when batch trigger operations fail.
17061788 * Includes context about which phase failed and the batch details.
@@ -2205,7 +2287,11 @@ async function trigger_internal<TRunTypes extends AnyRunTypes>(
22052287 const apiClient = apiClientManager . clientOrThrow ( requestOptions ?. clientConfig ) ;
22062288
22072289 const parsedPayload = parsePayload ? await parsePayload ( payload ) : payload ;
2208- const triggerPayloadPacket = await prepareTriggerPayload ( parsedPayload , apiClient , id ) ;
2290+ const { packet : triggerPayloadPacket , payloadSize } = await prepareTriggerPayload (
2291+ parsedPayload ,
2292+ apiClient ,
2293+ id
2294+ ) ;
22092295
22102296 // Process idempotency key and extract options for storage
22112297 const processedIdempotencyKey = await makeIdempotencyKey ( options ?. idempotencyKey ) ;
@@ -2222,6 +2308,7 @@ async function trigger_internal<TRunTypes extends AnyRunTypes>(
22222308 concurrencyKey : options ?. concurrencyKey ,
22232309 test : taskContext . ctx ?. run . isTest ,
22242310 payloadType : triggerPayloadPacket . dataType ,
2311+ payloadSize,
22252312 idempotencyKey : processedIdempotencyKey ?. toString ( ) ,
22262313 idempotencyKeyTTL : options ?. idempotencyKeyTTL ,
22272314 idempotencyKeyOptions,
@@ -2460,7 +2547,11 @@ async function triggerAndWait_internal<TIdentifier extends string, TPayload, TOu
24602547 const apiClient = apiClientManager . clientOrThrow ( requestOptions ?. clientConfig ) ;
24612548
24622549 const parsedPayload = parsePayload ? await parsePayload ( payload ) : payload ;
2463- const triggerPayloadPacket = await prepareTriggerPayload ( parsedPayload , apiClient , id ) ;
2550+ const { packet : triggerPayloadPacket , payloadSize } = await prepareTriggerPayload (
2551+ parsedPayload ,
2552+ apiClient ,
2553+ id
2554+ ) ;
24642555
24652556 // Process idempotency key and extract options for storage
24662557 const processedIdempotencyKey = await makeIdempotencyKey ( options ?. idempotencyKey ) ;
@@ -2481,6 +2572,7 @@ async function triggerAndWait_internal<TIdentifier extends string, TPayload, TOu
24812572 concurrencyKey : options ?. concurrencyKey ,
24822573 test : taskContext . ctx ?. run . isTest ,
24832574 payloadType : triggerPayloadPacket . dataType ,
2575+ payloadSize,
24842576 delay : options ?. delay ,
24852577 ttl : options ?. ttl ,
24862578 tags : options ?. tags ,
@@ -2546,7 +2638,11 @@ async function triggerAndSubscribe_internal<TIdentifier extends string, TPayload
25462638 const apiClient = apiClientManager . clientOrThrow ( requestOptions ?. clientConfig ) ;
25472639
25482640 const parsedPayload = parsePayload ? await parsePayload ( payload ) : payload ;
2549- const triggerPayloadPacket = await prepareTriggerPayload ( parsedPayload , apiClient , id ) ;
2641+ const { packet : triggerPayloadPacket , payloadSize } = await prepareTriggerPayload (
2642+ parsedPayload ,
2643+ apiClient ,
2644+ id
2645+ ) ;
25502646
25512647 const processedIdempotencyKey = await makeIdempotencyKey ( options ?. idempotencyKey ) ;
25522648 const idempotencyKeyOptions = processedIdempotencyKey
@@ -2566,6 +2662,7 @@ async function triggerAndSubscribe_internal<TIdentifier extends string, TPayload
25662662 concurrencyKey : options ?. concurrencyKey ,
25672663 test : taskContext . ctx ?. run . isTest ,
25682664 payloadType : triggerPayloadPacket . dataType ,
2665+ payloadSize,
25692666 delay : options ?. delay ,
25702667 ttl : options ?. ttl ,
25712668 tags : options ?. tags ,
@@ -3070,14 +3167,18 @@ async function prepareTriggerPayload(
30703167 payload : unknown ,
30713168 apiClient : ApiClient ,
30723169 taskId : string
3073- ) : Promise < IOPacket > {
3170+ ) : Promise < { packet : IOPacket ; payloadSize : number } > {
30743171 const payloadPacket = await stringifyIO ( payload ) ;
3075- return await conditionallyExportPacket (
3172+ // Measure the serialized size before any offload, so it reflects the real payload
3173+ // size rather than the small "application/store" reference we may send instead.
3174+ const { size : payloadSize } = packetRequiresOffloading ( payloadPacket ) ;
3175+ const packet = await conditionallyExportPacket (
30763176 payloadPacket ,
30773177 createTriggerPayloadPathPrefix ( taskId ) ,
30783178 undefined ,
30793179 apiClient
30803180 ) ;
3181+ return { packet, payloadSize } ;
30813182}
30823183
30833184function createTriggerPayloadPathPrefix ( taskId : string ) : string {
0 commit comments