@aexhq/sdk 0.40.6 → 0.40.8

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.
Files changed (53) hide show
  1. package/README.md +159 -159
  2. package/dist/_contracts/asset-upload-helper.d.ts +38 -0
  3. package/dist/_contracts/asset-upload-helper.js +111 -0
  4. package/dist/_contracts/internal.d.ts +5 -9
  5. package/dist/_contracts/internal.js +5 -79
  6. package/dist/_contracts/operations.d.ts +9 -2
  7. package/dist/_contracts/operations.js +141 -16
  8. package/dist/_contracts/retry-core.d.ts +27 -0
  9. package/dist/_contracts/retry-core.js +75 -0
  10. package/dist/_contracts/runtime-types.d.ts +6 -0
  11. package/dist/_contracts/stable.d.ts +10 -5
  12. package/dist/_contracts/stable.js +10 -5
  13. package/dist/asset-upload.d.ts +2 -1
  14. package/dist/asset-upload.js +28 -61
  15. package/dist/asset-upload.js.map +1 -1
  16. package/dist/cli.mjs +297 -80
  17. package/dist/cli.mjs.sha256 +1 -1
  18. package/dist/client.d.ts +5 -0
  19. package/dist/client.js +8 -13
  20. package/dist/client.js.map +1 -1
  21. package/dist/retry.js +13 -41
  22. package/dist/retry.js.map +1 -1
  23. package/dist/version.d.ts +1 -1
  24. package/dist/version.js +1 -1
  25. package/docs/authentication.md +125 -125
  26. package/docs/billing.md +118 -118
  27. package/docs/concepts/agent-tools.md +66 -66
  28. package/docs/concepts/composition.md +37 -37
  29. package/docs/concepts/providers-and-runtimes.md +54 -54
  30. package/docs/concepts/runs.md +49 -49
  31. package/docs/concepts/subagents.md +88 -88
  32. package/docs/credentials.md +125 -125
  33. package/docs/defaults.md +64 -64
  34. package/docs/errors.md +196 -196
  35. package/docs/events.md +243 -243
  36. package/docs/limits-and-quotas.md +144 -144
  37. package/docs/limits.md +50 -50
  38. package/docs/mcp.md +44 -44
  39. package/docs/networking.md +163 -163
  40. package/docs/outputs.md +267 -267
  41. package/docs/public-surface.json +77 -77
  42. package/docs/quickstart.md +119 -119
  43. package/docs/release.md +38 -38
  44. package/docs/retries.md +129 -129
  45. package/docs/run-config.md +58 -58
  46. package/docs/run-record.md +55 -55
  47. package/docs/secrets.md +125 -125
  48. package/docs/testing.md +35 -35
  49. package/docs/vision-skills.md +91 -91
  50. package/docs/webhooks.md +127 -127
  51. package/examples/feature-tour.ts +282 -282
  52. package/examples/spike-settle-latency.ts +125 -125
  53. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -2261,6 +2261,8 @@ function extractErrorMessage(body) {
2261
2261
  // ../contracts/dist/operations.js
2262
2262
  var operations_exports = {};
2263
2263
  __export(operations_exports, {
2264
+ OUTPUT_FILE_TRANSFER_ATTEMPTS: () => OUTPUT_FILE_TRANSFER_ATTEMPTS,
2265
+ OUTPUT_FILE_TRANSFER_DEFAULT_TIMEOUT_MS: () => OUTPUT_FILE_TRANSFER_DEFAULT_TIMEOUT_MS,
2264
2266
  READ_OUTPUT_TEXT_DEFAULT_BYTES: () => READ_OUTPUT_TEXT_DEFAULT_BYTES,
2265
2267
  READ_OUTPUT_TEXT_MAX_BYTES: () => READ_OUTPUT_TEXT_MAX_BYTES,
2266
2268
  approveSession: () => approveSession,
@@ -3322,28 +3324,144 @@ function resolveOutputFileSelector(outputs, selector, runId) {
3322
3324
  }
3323
3325
  return { ...selector, id: selector.id };
3324
3326
  }
3325
- async function downloadOutput(http, runId, selector) {
3327
+ async function downloadOutput(http, runId, selector, options) {
3326
3328
  const output = isPathSelector(selector) ? resolveOutputFileSelector(await listOutputs(http, runId), selector, runId) : resolveOutputFileSelector([], selector, runId);
3327
- const { response } = await http.download(`/api/runs/${encodeURIComponent(runId)}/outputs/${encodeURIComponent(output.id)}/download`);
3328
- return { output, bytes: new Uint8Array(await response.arrayBuffer()) };
3329
+ const timeoutMs = normalizeOutputTransferTimeoutMs(options?.timeoutMs);
3330
+ const path = `/api/runs/${encodeURIComponent(runId)}/outputs/${encodeURIComponent(output.id)}/download`;
3331
+ return { output, bytes: await downloadOutputBytesWithRetry(http, path, timeoutMs) };
3329
3332
  }
3330
3333
  var READ_OUTPUT_TEXT_MAX_BYTES = 1e7;
3331
3334
  var READ_OUTPUT_TEXT_DEFAULT_BYTES = 5e4;
3335
+ var OUTPUT_FILE_TRANSFER_DEFAULT_TIMEOUT_MS = 15e3;
3336
+ var OUTPUT_FILE_TRANSFER_ATTEMPTS = 2;
3332
3337
  async function readOutputText(http, runId, selector, options) {
3333
3338
  const maxBytes = Math.max(1, Math.min(options?.maxBytes ?? READ_OUTPUT_TEXT_DEFAULT_BYTES, READ_OUTPUT_TEXT_MAX_BYTES));
3334
3339
  const output = isPathSelector(selector) ? resolveOutputFileSelector(await listOutputs(http, runId), selector, runId) : resolveOutputFileSelector([], selector, runId);
3335
- const { response } = await http.download(`/api/runs/${encodeURIComponent(runId)}/outputs/${encodeURIComponent(output.id)}/download`);
3336
- const capped = await readCappedText(response, maxBytes);
3340
+ const timeoutMs = normalizeOutputTransferTimeoutMs(options?.timeoutMs);
3341
+ const path = `/api/runs/${encodeURIComponent(runId)}/outputs/${encodeURIComponent(output.id)}/download`;
3342
+ const capped = await readOutputTextWithRetry(http, path, maxBytes, timeoutMs);
3337
3343
  const text = options?.grep === void 0 ? capped.text : grepLines(capped.text, options.grep);
3338
3344
  return { output, text, truncated: capped.truncated, totalBytes: capped.totalBytes };
3339
3345
  }
3340
- async function readCappedText(response, maxBytes) {
3346
+ async function downloadOutputBytesWithRetry(http, path, timeoutMs) {
3347
+ return outputTransferWithRetry(path, timeoutMs, async () => {
3348
+ const response = await downloadOutputResponse(http, path, timeoutMs);
3349
+ return readResponseBytes(response, timeoutMs);
3350
+ });
3351
+ }
3352
+ async function readOutputTextWithRetry(http, path, maxBytes, timeoutMs) {
3353
+ return outputTransferWithRetry(path, timeoutMs, async () => {
3354
+ const response = await downloadOutputResponse(http, path, timeoutMs);
3355
+ return readCappedText(response, maxBytes, timeoutMs);
3356
+ });
3357
+ }
3358
+ async function downloadOutputResponse(http, path, timeoutMs) {
3359
+ const controller = new AbortController();
3360
+ const { response } = await withOutputTransferTimeout(http.download(path, { signal: controller.signal }), timeoutMs, () => controller.abort(), "output response");
3361
+ return response;
3362
+ }
3363
+ async function outputTransferWithRetry(path, timeoutMs, action) {
3364
+ const startedMs = Date.now();
3365
+ let lastTimeout;
3366
+ for (let attempt = 1; attempt <= OUTPUT_FILE_TRANSFER_ATTEMPTS; attempt += 1) {
3367
+ try {
3368
+ return await action();
3369
+ } catch (err2) {
3370
+ if (!(err2 instanceof OutputTransferTimeoutError))
3371
+ throw err2;
3372
+ lastTimeout = err2;
3373
+ }
3374
+ }
3375
+ throw new AexNetworkError({
3376
+ method: "GET",
3377
+ host: "",
3378
+ path,
3379
+ cause: lastTimeout ?? new OutputTransferTimeoutError("output transfer", timeoutMs),
3380
+ attempts: OUTPUT_FILE_TRANSFER_ATTEMPTS,
3381
+ elapsedMs: Date.now() - startedMs
3382
+ });
3383
+ }
3384
+ function normalizeOutputTransferTimeoutMs(value) {
3385
+ if (value === void 0)
3386
+ return OUTPUT_FILE_TRANSFER_DEFAULT_TIMEOUT_MS;
3387
+ if (!Number.isFinite(value) || value <= 0) {
3388
+ throw new RunConfigValidationError("outputs.download: timeoutMs must be a positive finite number", {
3389
+ timeoutMs: value
3390
+ });
3391
+ }
3392
+ return Math.max(1, Math.floor(value));
3393
+ }
3394
+ var OutputTransferTimeoutError = class extends Error {
3395
+ code = "ETIMEDOUT";
3396
+ constructor(label, timeoutMs) {
3397
+ super(`${label} timed out after ${timeoutMs}ms`);
3398
+ this.name = "OutputTransferTimeoutError";
3399
+ }
3400
+ };
3401
+ async function withOutputTransferTimeout(promise, timeoutMs, abort, label) {
3402
+ let timedOut = false;
3403
+ let timeout;
3404
+ const timeoutPromise = new Promise((_, reject) => {
3405
+ timeout = setTimeout(() => {
3406
+ timedOut = true;
3407
+ reject(new OutputTransferTimeoutError(label, timeoutMs));
3408
+ queueMicrotask(() => {
3409
+ try {
3410
+ abort();
3411
+ } catch {
3412
+ }
3413
+ });
3414
+ }, timeoutMs);
3415
+ });
3416
+ try {
3417
+ return await Promise.race([promise, timeoutPromise]);
3418
+ } catch (err2) {
3419
+ if (timedOut && isAbortLikeError(err2))
3420
+ throw new OutputTransferTimeoutError(label, timeoutMs);
3421
+ throw err2;
3422
+ } finally {
3423
+ if (timeout !== void 0)
3424
+ clearTimeout(timeout);
3425
+ }
3426
+ }
3427
+ function isAbortLikeError(err2) {
3428
+ const name = err2?.name;
3429
+ return name === "AbortError";
3430
+ }
3431
+ async function readResponseBytes(response, timeoutMs) {
3432
+ const body = response.body;
3433
+ if (!body) {
3434
+ const buffer = await withOutputTransferTimeout(response.arrayBuffer(), timeoutMs, () => {
3435
+ }, "output body");
3436
+ return new Uint8Array(buffer);
3437
+ }
3438
+ const reader = body.getReader();
3439
+ const chunks = [];
3440
+ try {
3441
+ while (true) {
3442
+ const { done, value } = await withOutputTransferTimeout(reader.read(), timeoutMs, () => {
3443
+ void reader.cancel().catch(() => {
3444
+ });
3445
+ }, "output body");
3446
+ if (done)
3447
+ break;
3448
+ if (value && value.byteLength > 0)
3449
+ chunks.push(value);
3450
+ }
3451
+ } finally {
3452
+ void reader.cancel().catch(() => {
3453
+ });
3454
+ }
3455
+ return concatBytes(chunks);
3456
+ }
3457
+ async function readCappedText(response, maxBytes, timeoutMs) {
3341
3458
  const declaredRaw = response.headers.get("content-length");
3342
3459
  const declared = declaredRaw !== null && /^\d+$/.test(declaredRaw) ? Number(declaredRaw) : void 0;
3343
3460
  const decoder = new TextDecoder("utf-8");
3344
3461
  const body = response.body;
3345
3462
  if (!body) {
3346
- const buf = new Uint8Array(await response.arrayBuffer());
3463
+ const buf = new Uint8Array(await withOutputTransferTimeout(response.arrayBuffer(), timeoutMs, () => {
3464
+ }, "output body"));
3347
3465
  const total = declared ?? buf.byteLength;
3348
3466
  return {
3349
3467
  text: decoder.decode(buf.subarray(0, maxBytes)),
@@ -3357,7 +3475,10 @@ async function readCappedText(response, maxBytes) {
3357
3475
  let sawMore = false;
3358
3476
  try {
3359
3477
  while (read < maxBytes) {
3360
- const { done, value } = await reader.read();
3478
+ const { done, value } = await withOutputTransferTimeout(reader.read(), timeoutMs, () => {
3479
+ void reader.cancel().catch(() => {
3480
+ });
3481
+ }, "output body");
3361
3482
  if (done)
3362
3483
  break;
3363
3484
  if (value && value.byteLength > 0) {
@@ -3366,12 +3487,15 @@ async function readCappedText(response, maxBytes) {
3366
3487
  }
3367
3488
  }
3368
3489
  if (read >= maxBytes) {
3369
- const next = await reader.read();
3490
+ const next = await withOutputTransferTimeout(reader.read(), timeoutMs, () => {
3491
+ void reader.cancel().catch(() => {
3492
+ });
3493
+ }, "output body");
3370
3494
  if (!next.done && next.value && next.value.byteLength > 0)
3371
3495
  sawMore = true;
3372
3496
  }
3373
3497
  } finally {
3374
- await reader.cancel().catch(() => {
3498
+ void reader.cancel().catch(() => {
3375
3499
  });
3376
3500
  }
3377
3501
  const merged = concatBytes(chunks).subarray(0, maxBytes);
@@ -3443,17 +3567,17 @@ async function getBillingLedger(http, query) {
3443
3567
  async function getWebhookSigningSecret(http) {
3444
3568
  return http.request("/api/webhook/signing-secret", { method: "POST" });
3445
3569
  }
3446
- async function collectArtifactBytes(http, runId, items, zipPrefix, namespace2) {
3570
+ async function collectArtifactBytes(http, runId, items, zipPrefix, namespace2, timeoutMs = OUTPUT_FILE_TRANSFER_DEFAULT_TIMEOUT_MS) {
3447
3571
  const entries = [];
3448
3572
  const captured = [];
3449
3573
  const errors = [];
3450
3574
  for (const item of items) {
3451
3575
  const rel = item.filename ?? item.id;
3452
3576
  try {
3453
- const { response } = await http.download(`/api/runs/${encodeURIComponent(runId)}/${namespace2}/${encodeURIComponent(item.id)}/download`);
3577
+ const path = `/api/runs/${encodeURIComponent(runId)}/${namespace2}/${encodeURIComponent(item.id)}/download`;
3454
3578
  entries.push({
3455
3579
  path: `${zipPrefix}${rel}`,
3456
- bytes: new Uint8Array(await response.arrayBuffer()),
3580
+ bytes: await downloadOutputBytesWithRetry(http, path, timeoutMs),
3457
3581
  ...item.contentType !== void 0 ? { contentType: item.contentType } : {},
3458
3582
  customerContent: true
3459
3583
  });
@@ -3679,9 +3803,10 @@ async function download(http, runId) {
3679
3803
  jsonEntry("manifest.json", manifest)
3680
3804
  ]);
3681
3805
  }
3682
- async function downloadOutputs(http, runId) {
3806
+ async function downloadOutputs(http, runId, options) {
3683
3807
  const outputs = await listOutputs(http, runId);
3684
- const { entries, captured, errors } = await collectArtifactBytes(http, runId, outputs, "", "outputs");
3808
+ const timeoutMs = normalizeOutputTransferTimeoutMs(options?.timeoutMs);
3809
+ const { entries, captured, errors } = await collectArtifactBytes(http, runId, outputs, "", "outputs", timeoutMs);
3685
3810
  return zipEntries([
3686
3811
  ...entries,
3687
3812
  jsonEntry("manifest.json", { runId, namespace: "outputs", outputs: captured, errors })
@@ -4456,91 +4581,119 @@ function renderVerbHelp(spec) {
4456
4581
  // dist/host/run-cmd.js
4457
4582
  import { resolve as resolvePath } from "node:path";
4458
4583
 
4459
- // ../contracts/dist/post-hook.js
4460
- var DEFAULT_POST_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
4461
-
4462
- // ../contracts/dist/internal.js
4463
- var DIRECT_UPLOAD_MAX_ATTEMPTS = 3;
4464
- async function uploadAsset(args) {
4465
- const expected = args.hash.startsWith("sha256:") ? args.hash.slice("sha256:".length) : args.hash;
4466
- const actual = await computeSha256Hex(args.bytes);
4467
- if (actual !== expected) {
4468
- throw new Error(`uploadAsset: client-side hash mismatch: computed sha256:${actual} but caller declared ${args.hash}. Aborting to avoid uploading corrupted data.`);
4469
- }
4470
- const contentHashHeader = `sha256:${actual}`;
4471
- const presign = await args.http.request("/assets/presign", {
4472
- method: "POST",
4473
- headers: { "content-type": "application/json" },
4474
- body: JSON.stringify({ hash: contentHashHeader, sizeBytes: args.bytes.byteLength })
4475
- });
4476
- if (presign.exists) {
4477
- const contentHash2 = presign.contentHash ?? contentHashHeader;
4478
- return {
4479
- assetId: presign.assetId ?? assetIdFromContentHash(contentHash2),
4480
- contentHash: contentHash2,
4481
- sizeBytes: presign.sizeBytes ?? args.bytes.byteLength,
4482
- exists: true
4483
- };
4584
+ // ../contracts/dist/retry-core.js
4585
+ var RETRYABLE_HTTP_STATUS = [429, 500, 502, 503, 504, 529];
4586
+ var RATE_LIMIT_HTTP_STATUS = [429, 503, 529];
4587
+ var RETRYABLE_HTTP_STATUS_SET = new Set(RETRYABLE_HTTP_STATUS);
4588
+ var RATE_LIMIT_HTTP_STATUS_SET = new Set(RATE_LIMIT_HTTP_STATUS);
4589
+ function parseRetryAfterMs(headerValue, now = Date.now()) {
4590
+ if (headerValue === null || headerValue === void 0)
4591
+ return void 0;
4592
+ const trimmed = headerValue.trim();
4593
+ if (trimmed.length === 0)
4594
+ return void 0;
4595
+ if (/^\d+$/.test(trimmed)) {
4596
+ return Number(trimmed) * 1e3;
4484
4597
  }
4485
- if (!presign.uploadUrl) {
4486
- throw new Error("uploadAsset: presign returned no uploadUrl and exists:false");
4598
+ const dateMs = Date.parse(trimmed);
4599
+ if (!Number.isNaN(dateMs)) {
4600
+ return Math.max(0, dateMs - now);
4487
4601
  }
4488
- const doFetch = args.fetch ?? globalThis.fetch;
4489
- const putHeaders = {
4490
- "content-type": args.contentType ?? "application/zip",
4491
- ...presign.requiredHeaders ?? {}
4492
- };
4493
- await putWithRetry(doFetch, presign.uploadUrl, {
4494
- method: "PUT",
4495
- headers: putHeaders,
4496
- body: args.bytes
4497
- });
4498
- const fin = await args.http.request("/assets/finalize", {
4499
- method: "POST",
4500
- headers: { "content-type": "application/json" },
4501
- body: JSON.stringify({ hash: contentHashHeader, sizeBytes: args.bytes.byteLength })
4602
+ return void 0;
4603
+ }
4604
+ function computeRetryBackoffDelayMs(config, attemptNumber, random) {
4605
+ const exponent = Math.max(0, attemptNumber - 1);
4606
+ const nominal = Math.min(config.maxDelayMs, config.initialDelayMs * 2 ** exponent);
4607
+ return Math.round(random() * nominal);
4608
+ }
4609
+ function computeRetryDelayMs(config, attemptNumber, random, retryAfterMs) {
4610
+ const backoff = computeRetryBackoffDelayMs(config, attemptNumber, random);
4611
+ return retryAfterMs === void 0 ? backoff : Math.max(retryAfterMs, backoff);
4612
+ }
4613
+ function abortableSleep(ms, signal) {
4614
+ return new Promise((resolve, reject) => {
4615
+ if (signal?.aborted) {
4616
+ reject(abortReason(signal));
4617
+ return;
4618
+ }
4619
+ const timer = setTimeout(() => {
4620
+ signal?.removeEventListener("abort", onAbort);
4621
+ resolve();
4622
+ }, ms);
4623
+ const onAbort = () => {
4624
+ clearTimeout(timer);
4625
+ reject(signal ? abortReason(signal) : makeAbortError());
4626
+ };
4627
+ signal?.addEventListener("abort", onAbort, { once: true });
4502
4628
  });
4503
- const contentHash = fin.contentHash ?? presign.contentHash ?? contentHashHeader;
4504
- return {
4505
- assetId: fin.assetId ?? presign.assetId ?? assetIdFromContentHash(contentHash),
4506
- contentHash,
4507
- sizeBytes: fin.sizeBytes ?? args.bytes.byteLength,
4508
- exists: false
4509
- };
4510
4629
  }
4511
- async function computeSha256Hex(bytes) {
4512
- const subtle = globalThis.crypto?.subtle;
4513
- if (!subtle) {
4514
- throw new Error("uploadAsset: globalThis.crypto.subtle is not available; Bun, Node 18+, or a Web-Crypto-capable runtime is required");
4515
- }
4516
- const digest = await subtle.digest("SHA-256", bytes);
4517
- return bufferToHex(digest);
4630
+ function abortReason(signal) {
4631
+ return signal.reason instanceof Error ? signal.reason : makeAbortError();
4518
4632
  }
4519
- function assetIdFromContentHash(contentHash) {
4520
- const hex = contentHash.startsWith("sha256:") ? contentHash.slice("sha256:".length) : contentHash;
4521
- return `asset_${hex}`;
4633
+ function makeAbortError() {
4634
+ if (typeof DOMException !== "undefined")
4635
+ return new DOMException("Aborted", "AbortError");
4636
+ const err2 = new Error("Aborted");
4637
+ err2.name = "AbortError";
4638
+ return err2;
4522
4639
  }
4523
- async function putWithRetry(fetchImpl, uploadUrl, init) {
4524
- for (let attempt = 1; attempt <= DIRECT_UPLOAD_MAX_ATTEMPTS; attempt++) {
4640
+
4641
+ // ../contracts/dist/asset-upload-helper.js
4642
+ var DIRECT_UPLOAD_MAX_ATTEMPTS = 3;
4643
+ var DIRECT_UPLOAD_INITIAL_DELAY_MS = 500;
4644
+ var DIRECT_UPLOAD_MAX_DELAY_MS = 5e3;
4645
+ var DIRECT_UPLOAD_MAX_ELAPSED_MS = 3e4;
4646
+ async function putDirectUploadWithRetry(fetchImpl, uploadUrl, init, options = {}) {
4647
+ const config = resolveAssetUploadRetryConfig(options);
4648
+ const sleep5 = options.sleep ?? abortableSleep;
4649
+ const random = options.random ?? Math.random;
4650
+ const now = options.now ?? Date.now;
4651
+ const startedAt = now();
4652
+ for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
4525
4653
  let response;
4526
4654
  try {
4527
4655
  response = await fetchImpl(uploadUrl, init);
4528
4656
  } catch (err2) {
4529
- if (attempt < DIRECT_UPLOAD_MAX_ATTEMPTS && isRetryableUploadError(err2)) {
4657
+ if (attempt < config.maxAttempts && isRetryableUploadError(err2)) {
4658
+ const delay = directUploadRetryDelayMs(config, attempt, random, void 0);
4659
+ if (!withinDirectUploadRetryBudget(config, startedAt, delay, now)) {
4660
+ throw directUploadNetworkError(uploadUrl, err2, attempt);
4661
+ }
4662
+ await sleep5(delay, init.signal ?? void 0);
4530
4663
  continue;
4531
4664
  }
4532
4665
  throw directUploadNetworkError(uploadUrl, err2, attempt);
4533
4666
  }
4534
4667
  if (response.ok)
4535
4668
  return;
4536
- if (attempt < DIRECT_UPLOAD_MAX_ATTEMPTS && isRetryableUploadStatus(response.status)) {
4669
+ const retryAfterMs = parseRetryAfterMs(response.headers?.get("retry-after"), now());
4670
+ if (attempt < config.maxAttempts && isRetryableUploadStatus(response.status)) {
4671
+ const delay = directUploadRetryDelayMs(config, attempt, random, retryAfterMs);
4672
+ if (!withinDirectUploadRetryBudget(config, startedAt, delay, now)) {
4673
+ const detail2 = await response.text().catch(() => "");
4674
+ throw directUploadResponseError(uploadUrl, response.status, detail2, attempt);
4675
+ }
4537
4676
  await response.text().catch(() => "");
4677
+ await sleep5(delay, init.signal ?? void 0);
4538
4678
  continue;
4539
4679
  }
4540
4680
  const detail = await response.text().catch(() => "");
4541
4681
  throw directUploadResponseError(uploadUrl, response.status, detail, attempt);
4542
4682
  }
4543
4683
  }
4684
+ function resolveAssetUploadRetryConfig(options = {}) {
4685
+ const maxAttempts = Math.max(1, Math.floor(options.maxAttempts ?? DIRECT_UPLOAD_MAX_ATTEMPTS));
4686
+ const initialDelayMs = Math.max(0, options.initialDelayMs ?? DIRECT_UPLOAD_INITIAL_DELAY_MS);
4687
+ const maxDelayMs = Math.max(initialDelayMs, options.maxDelayMs ?? DIRECT_UPLOAD_MAX_DELAY_MS);
4688
+ const maxElapsedMs = Math.max(0, options.maxElapsedMs ?? DIRECT_UPLOAD_MAX_ELAPSED_MS);
4689
+ return { maxAttempts, initialDelayMs, maxDelayMs, maxElapsedMs };
4690
+ }
4691
+ function directUploadRetryDelayMs(config, attempt, random, retryAfterMs) {
4692
+ return computeRetryDelayMs(config, attempt, random, retryAfterMs);
4693
+ }
4694
+ function withinDirectUploadRetryBudget(config, startedAt, delayMs, now) {
4695
+ return now() - startedAt + delayMs <= config.maxElapsedMs;
4696
+ }
4544
4697
  function isRetryableUploadStatus(status) {
4545
4698
  return status === 408 || status === 425 || status === 429 || status >= 500 && status <= 599;
4546
4699
  }
@@ -4560,6 +4713,9 @@ function directUploadResponseError(uploadUrl, status, detail, attempts) {
4560
4713
  const safeDetail = sanitizeUploadText(detail).slice(0, 500);
4561
4714
  return new Error(`uploadAsset: direct upload PUT failed for ${safeUrl} with status ${status}` + (attempts > 1 ? ` after ${attemptsLabel(attempts)}` : "") + (safeDetail ? `: ${safeDetail}` : ""));
4562
4715
  }
4716
+ function sanitizeUploadText(text) {
4717
+ return text.replace(/https?:\/\/[^\s<>"'`]+/g, (raw) => redactUrlPreservingTrailingPunctuation(raw)).replace(/\b(?:X-Amz-(?:Algorithm|Credential|Date|Expires|Security-Token|Signature|SignedHeaders)|AWSAccessKeyId|Signature|Credential|Security-Token|AccessKeyId|SecretAccessKey|SessionToken)=([^&\s<>"'`]+)/gi, "[redacted]").replace(/\bAKIA[0-9A-Z]{8,}\b/g, "[redacted]");
4718
+ }
4563
4719
  function attemptsLabel(attempts) {
4564
4720
  return attempts === 1 ? "1 attempt" : `${attempts} attempts`;
4565
4721
  }
@@ -4579,14 +4735,75 @@ function stringProperty2(value, key) {
4579
4735
  const prop = value[key];
4580
4736
  return typeof prop === "string" && prop.length > 0 ? prop : void 0;
4581
4737
  }
4582
- function sanitizeUploadText(text) {
4583
- return text.replace(/https?:\/\/[^\s<>"'`]+/g, (raw) => redactUrlPreservingTrailingPunctuation(raw)).replace(/\b(?:X-Amz-(?:Algorithm|Credential|Date|Expires|Security-Token|Signature|SignedHeaders)|AWSAccessKeyId|Signature|Credential|Security-Token|AccessKeyId|SecretAccessKey|SessionToken)=([^&\s<>"'`]+)/gi, "[redacted]").replace(/\bAKIA[0-9A-Z]{8,}\b/g, "[redacted]");
4584
- }
4585
4738
  function redactUrlPreservingTrailingPunctuation(raw) {
4586
4739
  const trailing = raw.match(/[),.;:!?]+$/)?.[0] ?? "";
4587
4740
  const candidate = trailing ? raw.slice(0, -trailing.length) : raw;
4588
4741
  return `${redactUrl(candidate)}${trailing}`;
4589
4742
  }
4743
+
4744
+ // ../contracts/dist/post-hook.js
4745
+ var DEFAULT_POST_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
4746
+
4747
+ // ../contracts/dist/internal.js
4748
+ async function uploadAsset(args) {
4749
+ const expected = args.hash.startsWith("sha256:") ? args.hash.slice("sha256:".length) : args.hash;
4750
+ const actual = await computeSha256Hex(args.bytes);
4751
+ if (actual !== expected) {
4752
+ throw new Error(`uploadAsset: client-side hash mismatch: computed sha256:${actual} but caller declared ${args.hash}. Aborting to avoid uploading corrupted data.`);
4753
+ }
4754
+ const contentHashHeader = `sha256:${actual}`;
4755
+ const presign = await args.http.request("/assets/presign", {
4756
+ method: "POST",
4757
+ headers: { "content-type": "application/json" },
4758
+ body: JSON.stringify({ hash: contentHashHeader, sizeBytes: args.bytes.byteLength })
4759
+ });
4760
+ if (presign.exists) {
4761
+ const contentHash2 = presign.contentHash ?? contentHashHeader;
4762
+ return {
4763
+ assetId: presign.assetId ?? assetIdFromContentHash(contentHash2),
4764
+ contentHash: contentHash2,
4765
+ sizeBytes: presign.sizeBytes ?? args.bytes.byteLength,
4766
+ exists: true
4767
+ };
4768
+ }
4769
+ if (!presign.uploadUrl) {
4770
+ throw new Error("uploadAsset: presign returned no uploadUrl and exists:false");
4771
+ }
4772
+ const doFetch = args.fetch ?? globalThis.fetch;
4773
+ const putHeaders = {
4774
+ "content-type": args.contentType ?? "application/zip",
4775
+ ...presign.requiredHeaders ?? {}
4776
+ };
4777
+ await putDirectUploadWithRetry(doFetch, presign.uploadUrl, {
4778
+ method: "PUT",
4779
+ headers: putHeaders,
4780
+ body: args.bytes
4781
+ }, args.retry);
4782
+ const fin = await args.http.request("/assets/finalize", {
4783
+ method: "POST",
4784
+ headers: { "content-type": "application/json" },
4785
+ body: JSON.stringify({ hash: contentHashHeader, sizeBytes: args.bytes.byteLength })
4786
+ });
4787
+ const contentHash = fin.contentHash ?? presign.contentHash ?? contentHashHeader;
4788
+ return {
4789
+ assetId: fin.assetId ?? presign.assetId ?? assetIdFromContentHash(contentHash),
4790
+ contentHash,
4791
+ sizeBytes: fin.sizeBytes ?? args.bytes.byteLength,
4792
+ exists: false
4793
+ };
4794
+ }
4795
+ async function computeSha256Hex(bytes) {
4796
+ const subtle = globalThis.crypto?.subtle;
4797
+ if (!subtle) {
4798
+ throw new Error("uploadAsset: globalThis.crypto.subtle is not available; Bun, Node 18+, or a Web-Crypto-capable runtime is required");
4799
+ }
4800
+ const digest = await subtle.digest("SHA-256", bytes);
4801
+ return bufferToHex(digest);
4802
+ }
4803
+ function assetIdFromContentHash(contentHash) {
4804
+ const hex = contentHash.startsWith("sha256:") ? contentHash.slice("sha256:".length) : contentHash;
4805
+ return `asset_${hex}`;
4806
+ }
4590
4807
  function bufferToHex(buffer) {
4591
4808
  const view = new Uint8Array(buffer);
4592
4809
  let out = "";
@@ -1 +1 @@
1
- a67b2cbba2818c579806f112ebfbfddb975b028d0950381985e0e6aa73d259ee cli.mjs
1
+ cdab07311ba092bc586f1d536208b0ca762c2014ddfb8f4219beb21a6302b59b cli.mjs
package/dist/client.d.ts CHANGED
@@ -555,6 +555,11 @@ export type OutputFileSelector = Output | OutputFileIdSelector | OutputFilePathS
555
555
  export type OutputLinkSelector = string | OutputFileSelector | OutputQuery;
556
556
  export interface OutputDownloadOptions {
557
557
  readonly to?: string;
558
+ /**
559
+ * Per-attempt timeout for fetching and reading the selected output body.
560
+ * Defaults to 15_000ms; idempotent output downloads retry once on timeout.
561
+ */
562
+ readonly timeoutMs?: number;
558
563
  }
559
564
  /**
560
565
  * Workspace AgentsMd admin operations exposed under `client.agentsMd`.
package/dist/client.js CHANGED
@@ -746,9 +746,10 @@ function isSessionEnvelopeTerminal(event) {
746
746
  async function downloadSessionOutput(http, id, selector, options) {
747
747
  // One selector-resolution path: the contracts `downloadOutput` lists-if-path
748
748
  // then downloads, throwing with PUBLIC verb names — no duplicated resolver.
749
+ const transferOptions = options?.timeoutMs === undefined ? undefined : { timeoutMs: options.timeoutMs };
749
750
  const bytes = selector === undefined
750
- ? await operations.downloadOutputs(http, id)
751
- : (await operations.downloadOutput(http, id, selector)).bytes;
751
+ ? await operations.downloadOutputs(http, id, transferOptions)
752
+ : (await operations.downloadOutput(http, id, selector, transferOptions)).bytes;
752
753
  return writeOptionalFile(bytes, options?.to);
753
754
  }
754
755
  /**
@@ -1766,11 +1767,9 @@ export class Aex {
1766
1767
  * Resolve the effective `baseUrl` from a self-describing API key (ZERO-network):
1767
1768
  * - key omitted-of-shape (opaque/legacy): return the caller's `baseUrl`
1768
1769
  * unchanged (HttpClient falls back to the prd default).
1769
- * - `baseUrl` omitted: DERIVE it from the key's plane; a plane with no default
1770
- * host (dev) requires an explicit `baseUrl` — throw with that guidance.
1770
+ * - `baseUrl` omitted: DERIVE it from the key's plane.
1771
1771
  * - `baseUrl` supplied but its plane DISAGREES with the key's plane: throw
1772
- * {@link CredentialValidationError} BEFORE any request (the exact `dev key
1773
- * against the prd default → bare token_invalid` trap).
1772
+ * {@link CredentialValidationError} BEFORE any request.
1774
1773
  */
1775
1774
  function resolveBaseUrlForKey(apiKey, baseUrl) {
1776
1775
  const parsed = parseApiKey(apiKey);
@@ -1778,15 +1777,11 @@ function resolveBaseUrlForKey(apiKey, baseUrl) {
1778
1777
  return baseUrl;
1779
1778
  const planeUrl = PLANE_BASE_URLS[parsed.plane];
1780
1779
  if (baseUrl === undefined) {
1781
- if (planeUrl === null) {
1782
- throw new CredentialValidationError(`Aex: this API key is for the ${parsed.plane} plane, which has no default host — pass baseUrl explicitly.`, { plane: parsed.plane });
1783
- }
1784
1780
  return planeUrl;
1785
1781
  }
1786
- // The only client-detectable mismatch: a non-prd key pointed at the canonical
1787
- // prd host (dev has no canonical host to compare a prd key against).
1788
- if (baseUrl === PLANE_BASE_URLS.prd && parsed.plane !== "prd") {
1789
- throw new CredentialValidationError(`Aex: this API key is for the ${parsed.plane} plane but baseUrl targets the prd plane (${PLANE_BASE_URLS.prd}) — ` +
1782
+ const canonicalPlane = baseUrl === PLANE_BASE_URLS.dev ? "dev" : baseUrl === PLANE_BASE_URLS.prd ? "prd" : undefined;
1783
+ if (canonicalPlane !== undefined && canonicalPlane !== parsed.plane) {
1784
+ throw new CredentialValidationError(`Aex: this API key is for the ${parsed.plane} plane but baseUrl targets the ${canonicalPlane} plane (${baseUrl}) — ` +
1790
1785
  `pass the ${parsed.plane} plane baseUrl, or omit baseUrl to auto-route.`, { plane: parsed.plane, baseUrl });
1791
1786
  }
1792
1787
  return baseUrl;