@agent-native/core 0.49.24 → 0.49.26

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 (64) hide show
  1. package/dist/agent/production-agent.d.ts.map +1 -1
  2. package/dist/agent/production-agent.js +8 -1
  3. package/dist/agent/production-agent.js.map +1 -1
  4. package/dist/cli/recap.d.ts.map +1 -1
  5. package/dist/cli/recap.js +43 -11
  6. package/dist/cli/recap.js.map +1 -1
  7. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  8. package/dist/client/agent-chat-adapter.js +2 -1
  9. package/dist/client/agent-chat-adapter.js.map +1 -1
  10. package/dist/client/blocks/library/AnnotatedCodeBlock.js +7 -7
  11. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  12. package/dist/client/blocks/library/DiffBlock.js +3 -3
  13. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  14. package/dist/client/blocks/library/annotation-rail.d.ts +4 -3
  15. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
  16. package/dist/client/blocks/library/annotation-rail.js +16 -9
  17. package/dist/client/blocks/library/annotation-rail.js.map +1 -1
  18. package/dist/client/blocks/types.d.ts +2 -2
  19. package/dist/client/blocks/types.js.map +1 -1
  20. package/dist/coding-tools/run-code.d.ts.map +1 -1
  21. package/dist/coding-tools/run-code.js +198 -15
  22. package/dist/coding-tools/run-code.js.map +1 -1
  23. package/dist/extensions/fetch-tool.js +1 -1
  24. package/dist/extensions/fetch-tool.js.map +1 -1
  25. package/dist/mcp/build-server.d.ts.map +1 -1
  26. package/dist/mcp/build-server.js +1 -0
  27. package/dist/mcp/build-server.js.map +1 -1
  28. package/dist/mcp/builtin-tools.d.ts +8 -4
  29. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  30. package/dist/mcp/builtin-tools.js +247 -13
  31. package/dist/mcp/builtin-tools.js.map +1 -1
  32. package/dist/provider-api/actions/query-staged-dataset.d.ts.map +1 -1
  33. package/dist/provider-api/actions/query-staged-dataset.js +1 -0
  34. package/dist/provider-api/actions/query-staged-dataset.js.map +1 -1
  35. package/dist/provider-api/index.d.ts +15 -4
  36. package/dist/provider-api/index.d.ts.map +1 -1
  37. package/dist/provider-api/index.js +191 -43
  38. package/dist/provider-api/index.js.map +1 -1
  39. package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
  40. package/dist/provider-api/staged-datasets-store.js +29 -6
  41. package/dist/provider-api/staged-datasets-store.js.map +1 -1
  42. package/dist/provider-api/staging.d.ts +6 -1
  43. package/dist/provider-api/staging.d.ts.map +1 -1
  44. package/dist/provider-api/staging.js +35 -6
  45. package/dist/provider-api/staging.js.map +1 -1
  46. package/dist/server/agent-chat-plugin.d.ts +1 -1
  47. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  48. package/dist/server/agent-chat-plugin.js +157 -80
  49. package/dist/server/agent-chat-plugin.js.map +1 -1
  50. package/dist/server/prompts/shared-rules.d.ts +1 -1
  51. package/dist/server/prompts/shared-rules.d.ts.map +1 -1
  52. package/dist/server/prompts/shared-rules.js +5 -7
  53. package/dist/server/prompts/shared-rules.js.map +1 -1
  54. package/dist/server/schema-prompt.js +1 -1
  55. package/dist/server/schema-prompt.js.map +1 -1
  56. package/dist/templates/default/.agents/skills/actions/SKILL.md +37 -9
  57. package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +7 -1
  58. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +37 -9
  59. package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +7 -1
  60. package/package.json +1 -1
  61. package/src/templates/default/.agents/skills/actions/SKILL.md +37 -9
  62. package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +7 -1
  63. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +37 -9
  64. package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +7 -1
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { resolveCredential, } from "../credentials/index.js";
3
3
  import { createSsrfSafeDispatcher, isBlockedExtensionUrlWithDns, } from "../extensions/url-safety.js";
4
- import { listOAuthAccountsByOwner, saveOAuthTokens, } from "../oauth-tokens/index.js";
4
+ import { deleteOAuthTokens, listOAuthAccountsByOwner, saveOAuthTokens, } from "../oauth-tokens/index.js";
5
5
  import { getCredentialContext } from "../server/request-context.js";
6
6
  import { resolveWorkspaceConnectionCredentialForApp } from "../workspace-connections/credentials.js";
7
7
  export { upsertCustomProvider, deleteCustomProvider, listCustomProviders, getCustomProvider, validateCustomBaseUrl, } from "./custom-registry.js";
@@ -32,6 +32,11 @@ export const PROVIDER_API_IDS = [
32
32
  "stripe",
33
33
  "twitter",
34
34
  ];
35
+ const PERMANENT_GOOGLE_OAUTH_REFRESH_ERRORS = new Set([
36
+ "invalid_grant",
37
+ "unauthorized_client",
38
+ "invalid_client",
39
+ ]);
35
40
  const DEFAULT_TIMEOUT_MS = 30_000;
36
41
  const MAX_TIMEOUT_MS = 120_000;
37
42
  const DEFAULT_MAX_BYTES = 1024 * 1024;
@@ -94,7 +99,7 @@ const PROVIDER_CONFIGS = {
94
99
  },
95
100
  credentialKeys: ["APOLLO_API_KEY"],
96
101
  docsUrls: ["https://docs.apollo.io/reference/api-reference"],
97
- templateUses: ["analytics"],
102
+ templateUses: ["analytics", "calendar"],
98
103
  examples: [
99
104
  {
100
105
  label: "Search people",
@@ -270,7 +275,7 @@ const PROVIDER_CONFIGS = {
270
275
  Accept: "application/vnd.github+json",
271
276
  "X-GitHub-Api-Version": "2022-11-28",
272
277
  },
273
- templateUses: ["analytics", "brain", "dispatch"],
278
+ templateUses: ["analytics", "brain", "design", "dispatch"],
274
279
  examples: [
275
280
  { label: "Authenticated user", method: "GET", path: "/user" },
276
281
  { label: "Search issues", method: "GET", path: "/search/issues" },
@@ -319,7 +324,7 @@ const PROVIDER_CONFIGS = {
319
324
  },
320
325
  credentialKeys: ["GONG_ACCESS_KEY", "GONG_ACCESS_SECRET", "GONG_API_BASE"],
321
326
  docsUrls: ["https://gong.app.gong.io/settings/api/documentation"],
322
- templateUses: ["analytics"],
327
+ templateUses: ["analytics", "calendar"],
323
328
  examples: [
324
329
  { label: "List calls", method: "GET", path: "/calls" },
325
330
  {
@@ -328,6 +333,24 @@ const PROVIDER_CONFIGS = {
328
333
  path: "/calls/transcript",
329
334
  body: { filter: { callIds: ["<call-id>"] } },
330
335
  },
336
+ {
337
+ label: "Calls with parties/content",
338
+ method: "POST",
339
+ path: "/calls/extensive",
340
+ body: {
341
+ filter: { fromDateTime: "<iso-date-time>" },
342
+ contentSelector: {
343
+ exposedFields: {
344
+ parties: true,
345
+ content: { brief: true, keyPoints: true },
346
+ },
347
+ },
348
+ },
349
+ },
350
+ ],
351
+ notes: [
352
+ "For broad corpus work, call /calls/extensive with provider-api-request and stageAs/saveToFile. Gong returns the next cursor at records.cursor and expects the next cursor in the POST body at cursor, so use pagination { nextCursorPath: 'records.cursor', cursorBodyPath: 'cursor' } for stageAs or fetchAllPages { cursorPath: 'records.cursor', cursorBodyPath: 'cursor' } for saveToFile.",
353
+ "Batch transcripts with POST /calls/transcript and body { filter: { callIds: [...] } } after narrowing or staging call ids.",
331
354
  ],
332
355
  },
333
356
  google_calendar: {
@@ -343,7 +366,7 @@ const PROVIDER_CONFIGS = {
343
366
  docsUrls: ["https://developers.google.com/calendar/api/v3/reference"],
344
367
  specUrls: ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"],
345
368
  allowedHostSuffixes: ["googleapis.com"],
346
- templateUses: ["brain", "calendar", "dispatch"],
369
+ templateUses: ["brain", "calendar", "dispatch", "mail"],
347
370
  examples: [
348
371
  {
349
372
  label: "List calendars",
@@ -426,7 +449,7 @@ const PROVIDER_CONFIGS = {
426
449
  },
427
450
  credentialKeys: ["HUBSPOT_PRIVATE_APP_TOKEN", "HUBSPOT_ACCESS_TOKEN"],
428
451
  docsUrls: ["https://developers.hubspot.com/docs/api/overview"],
429
- templateUses: ["analytics", "brain", "mail", "dispatch"],
452
+ templateUses: ["analytics", "brain", "calendar", "mail", "dispatch"],
430
453
  examples: [
431
454
  {
432
455
  label: "Search deals with any HubSpot CRM filter",
@@ -585,7 +608,7 @@ const PROVIDER_CONFIGS = {
585
608
  },
586
609
  credentialKeys: ["PYLON_API_KEY"],
587
610
  docsUrls: ["https://docs.usepylon.com/pylon-docs/developer/api-reference"],
588
- templateUses: ["analytics"],
611
+ templateUses: ["analytics", "calendar"],
589
612
  examples: [{ label: "List issues", method: "GET", path: "/issues" }],
590
613
  },
591
614
  sentry: {
@@ -822,9 +845,9 @@ export async function executeProviderApiRequest(args, runtime) {
822
845
  // --- fetchAllPages mode ---
823
846
  if (args.fetchAllPages) {
824
847
  const pageCfg = args.fetchAllPages;
825
- const { items, pageCount, lastStatus, lastContentType } = await fetchAllPages(pageCfg, async (extraQuery) => {
826
- const queryWithCursor = extraQuery
827
- ? mergeQueryObjects(substituteUnknown(args.query, placeholders), extraQuery)
848
+ const { items, pageCount, lastStatus, lastContentType } = await fetchAllPages(pageCfg, async (extra) => {
849
+ const queryWithCursor = extra?.query
850
+ ? mergeQueryObjects(substituteUnknown(args.query, placeholders), extra.query)
828
851
  : substituteUnknown(args.query, placeholders);
829
852
  const pageUrl = buildProviderUrl({
830
853
  config,
@@ -832,7 +855,10 @@ export async function executeProviderApiRequest(args, runtime) {
832
855
  rawPath: substituteString(args.path, placeholders),
833
856
  query: queryWithCursor,
834
857
  });
835
- const pageBody = prepareBody(substituteUnknown(args.body, placeholders), { ...headers });
858
+ const bodyWithCursor = extra?.bodyCursor
859
+ ? setValueAtPath(substituteUnknown(args.body, placeholders), extra.bodyCursor.path, extra.bodyCursor.value)
860
+ : substituteUnknown(args.body, placeholders);
861
+ const pageBody = prepareBody(bodyWithCursor, { ...headers });
836
862
  const resp = await fetchWithTimeout(pageUrl.href, {
837
863
  method,
838
864
  headers,
@@ -955,9 +981,9 @@ async function executeCustomProviderApiRequest(args, customConfig, runtime) {
955
981
  // --- fetchAllPages mode (same cursor pagination as built-in providers) ---
956
982
  if (args.fetchAllPages) {
957
983
  const pageCfg = args.fetchAllPages;
958
- const { items, pageCount, lastStatus, lastContentType } = await fetchAllPages(pageCfg, async (extraQuery) => {
959
- const queryWithCursor = extraQuery
960
- ? mergeQueryObjects(args.query, extraQuery)
984
+ const { items, pageCount, lastStatus, lastContentType } = await fetchAllPages(pageCfg, async (extra) => {
985
+ const queryWithCursor = extra?.query
986
+ ? mergeQueryObjects(args.query, extra.query)
961
987
  : args.query;
962
988
  const pageUrl = buildProviderUrl({
963
989
  config: syntheticConfig,
@@ -965,7 +991,10 @@ async function executeCustomProviderApiRequest(args, customConfig, runtime) {
965
991
  rawPath: args.path,
966
992
  query: queryWithCursor,
967
993
  });
968
- const pageBody = prepareBody(args.body, { ...headers });
994
+ const bodyWithCursor = extra?.bodyCursor
995
+ ? setValueAtPath(args.body, extra.bodyCursor.path, extra.bodyCursor.value)
996
+ : args.body;
997
+ const pageBody = prepareBody(bodyWithCursor, { ...headers });
969
998
  const resp = await fetchWithTimeout(pageUrl.href, {
970
999
  method,
971
1000
  headers,
@@ -1394,7 +1423,7 @@ function buildProviderUrl(options) {
1394
1423
  const rawPath = options.rawPath.trim();
1395
1424
  const url = /^https?:\/\//i.test(rawPath)
1396
1425
  ? new URL(rawPath)
1397
- : new URL(rawPath.startsWith("/") ? rawPath : `/${rawPath}`, base);
1426
+ : new URL(joinProviderUrlPath(base, rawPath), base.origin);
1398
1427
  if (!isAllowedProviderUrl(url, base, options.config)) {
1399
1428
  throw new Error(`${options.config.label} API requests must stay on the configured provider host or registered provider host suffix.`);
1400
1429
  }
@@ -1403,6 +1432,20 @@ function buildProviderUrl(options) {
1403
1432
  }
1404
1433
  return url;
1405
1434
  }
1435
+ function joinProviderUrlPath(base, rawPath) {
1436
+ const basePath = base.pathname.replace(/\/+$/, "");
1437
+ const providerPath = rawPath.replace(/^\/+/, "");
1438
+ if (!providerPath)
1439
+ return basePath || "/";
1440
+ const baseSegments = basePath.split("/").filter(Boolean);
1441
+ const providerSegments = providerPath.split("/").filter(Boolean);
1442
+ if (baseSegments.length > 0 &&
1443
+ providerSegments.length >= baseSegments.length &&
1444
+ baseSegments.every((segment, index) => segment === providerSegments[index])) {
1445
+ return `/${providerPath}`;
1446
+ }
1447
+ return `${basePath}/${providerPath}`;
1448
+ }
1406
1449
  function isAllowedProviderUrl(url, base, config) {
1407
1450
  if (url.protocol !== "https:" && url.protocol !== "http:")
1408
1451
  return false;
@@ -1533,8 +1576,9 @@ async function resolveAuth(config, runtime, ctx, args) {
1533
1576
  };
1534
1577
  }
1535
1578
  if (auth.type === "oauth-bearer") {
1579
+ const oauthProvider = runtime.oauthProviderOverrides?.[config.id] ?? auth.oauthProvider;
1536
1580
  const credential = await resolveOAuthBearerToken({
1537
- auth,
1581
+ auth: { ...auth, oauthProvider },
1538
1582
  ctx,
1539
1583
  accountId: args.accountId,
1540
1584
  });
@@ -1733,7 +1777,8 @@ async function getValidOAuthAccessToken(options) {
1733
1777
  const refreshToken = options.tokens.refresh_token ?? options.tokens.refreshToken;
1734
1778
  if (!refreshToken)
1735
1779
  return accessToken;
1736
- if (options.oauthProvider === "google") {
1780
+ if (options.oauthProvider === "google" ||
1781
+ options.oauthProvider === "google-docs") {
1737
1782
  return refreshGoogleOAuthToken(options, refreshToken);
1738
1783
  }
1739
1784
  throw new Error(`${options.oauthProvider} OAuth token is expired and automatic refresh is not configured for provider-api.`);
@@ -1756,6 +1801,9 @@ async function refreshGoogleOAuthToken(options, refreshToken) {
1756
1801
  });
1757
1802
  const data = (await res.json());
1758
1803
  if (!res.ok || !data.access_token) {
1804
+ if (data.error && PERMANENT_GOOGLE_OAUTH_REFRESH_ERRORS.has(data.error)) {
1805
+ await deleteOAuthTokens(options.oauthProvider, options.accountId);
1806
+ }
1759
1807
  const detail = data.error_description ?? data.error ?? res.statusText;
1760
1808
  throw new Error(`Google OAuth refresh failed: ${detail}`);
1761
1809
  }
@@ -1811,11 +1859,14 @@ function prepareBody(body, headers) {
1811
1859
  }
1812
1860
  async function fetchWithTimeout(optionsUrl, options) {
1813
1861
  const controller = new AbortController();
1814
- const timeout = setTimeout(() => controller.abort(), clampTimeout(options.timeoutMs));
1862
+ const timeoutMs = clampTimeout(options.timeoutMs);
1863
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1864
+ const method = options.method ?? "GET";
1865
+ const secretValues = options.secretValues ?? [];
1815
1866
  try {
1816
1867
  const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;
1817
1868
  const fetchOptions = {
1818
- method: options.method ?? "GET",
1869
+ method,
1819
1870
  headers: options.headers,
1820
1871
  body: options.body,
1821
1872
  signal: controller.signal,
@@ -1823,30 +1874,93 @@ async function fetchWithTimeout(optionsUrl, options) {
1823
1874
  };
1824
1875
  if (dispatcher)
1825
1876
  fetchOptions.dispatcher = dispatcher;
1826
- const startedAt = Date.now();
1827
- const res = await fetch(optionsUrl, fetchOptions);
1828
- const elapsedMs = Date.now() - startedAt;
1829
- const rawText = await readResponseTextWithLimit(res, clampMaxBytes(options.maxBytes));
1830
- const secretValues = options.secretValues ?? [];
1831
- const redactedText = redactString(rawText.text, secretValues);
1832
- const parsed = tryParseJson(redactedText);
1833
- return {
1834
- status: res.status,
1835
- statusText: res.statusText,
1836
- ok: res.ok,
1837
- elapsedMs,
1838
- headers: redactSecrets(headersToObject(res.headers), secretValues),
1839
- contentType: res.headers.get("content-type") ?? null,
1840
- size: rawText.size,
1841
- truncated: rawText.truncated,
1842
- text: parsed === undefined ? redactedText : undefined,
1843
- json: parsed,
1844
- };
1877
+ try {
1878
+ return await fetchProviderResponse(optionsUrl, fetchOptions, {
1879
+ maxBytes: options.maxBytes,
1880
+ secretValues,
1881
+ });
1882
+ }
1883
+ catch (error) {
1884
+ if (dispatcher && isDispatcherCompatibilityError(error)) {
1885
+ const fallbackOptions = { ...fetchOptions };
1886
+ delete fallbackOptions.dispatcher;
1887
+ return await fetchProviderResponse(optionsUrl, fallbackOptions, {
1888
+ maxBytes: options.maxBytes,
1889
+ secretValues,
1890
+ });
1891
+ }
1892
+ throw error;
1893
+ }
1894
+ }
1895
+ catch (error) {
1896
+ throw normalizeFetchError(error, {
1897
+ method,
1898
+ url: optionsUrl,
1899
+ timeoutMs,
1900
+ secretValues,
1901
+ });
1845
1902
  }
1846
1903
  finally {
1847
1904
  clearTimeout(timeout);
1848
1905
  }
1849
1906
  }
1907
+ async function fetchProviderResponse(url, fetchOptions, options) {
1908
+ const startedAt = Date.now();
1909
+ const res = await fetch(url, fetchOptions);
1910
+ const elapsedMs = Date.now() - startedAt;
1911
+ const rawText = await readResponseTextWithLimit(res, clampMaxBytes(options.maxBytes));
1912
+ const redactedText = redactString(rawText.text, options.secretValues);
1913
+ const parsed = tryParseJson(redactedText);
1914
+ return {
1915
+ status: res.status,
1916
+ statusText: res.statusText,
1917
+ ok: res.ok,
1918
+ elapsedMs,
1919
+ headers: redactSecrets(headersToObject(res.headers), options.secretValues),
1920
+ contentType: res.headers.get("content-type") ?? null,
1921
+ size: rawText.size,
1922
+ truncated: rawText.truncated,
1923
+ text: parsed === undefined ? redactedText : undefined,
1924
+ json: parsed,
1925
+ };
1926
+ }
1927
+ function isDispatcherCompatibilityError(error) {
1928
+ const err = error;
1929
+ const code = typeof err?.cause?.code === "string" ? err.cause.code : "";
1930
+ const detail = [
1931
+ typeof err?.message === "string" ? err.message : "",
1932
+ typeof err?.cause?.message === "string" ? err.cause.message : "",
1933
+ ]
1934
+ .join(" ")
1935
+ .toLowerCase();
1936
+ return (code === "UND_ERR_INVALID_ARG" &&
1937
+ detail.includes("invalid onrequeststart method"));
1938
+ }
1939
+ function normalizeFetchError(error, options) {
1940
+ const target = describeProviderRequestTarget(options.url, options.secretValues);
1941
+ const err = error;
1942
+ if (err?.name === "AbortError") {
1943
+ return new Error(`Provider API request timed out after ${options.timeoutMs}ms: ${options.method} ${target}`, { cause: error });
1944
+ }
1945
+ const causeCode = typeof err?.cause?.code === "string" && err.cause.code
1946
+ ? ` (${err.cause.code})`
1947
+ : "";
1948
+ const detail = typeof err?.cause?.message === "string" && err.cause.message
1949
+ ? err.cause.message
1950
+ : typeof err?.message === "string" && err.message
1951
+ ? err.message
1952
+ : String(error);
1953
+ return new Error(`Provider API request failed${causeCode}: ${options.method} ${target}: ${redactString(detail, options.secretValues)}`, { cause: error });
1954
+ }
1955
+ function describeProviderRequestTarget(rawUrl, secretValues) {
1956
+ try {
1957
+ const url = new URL(rawUrl);
1958
+ return redactString(`${url.host}${url.pathname}${url.search}`, secretValues);
1959
+ }
1960
+ catch {
1961
+ return redactString(rawUrl, secretValues);
1962
+ }
1963
+ }
1850
1964
  async function readResponseTextWithLimit(response, maxBytes) {
1851
1965
  const contentLength = response.headers.get("content-length");
1852
1966
  if (contentLength && Number(contentLength) > maxBytes) {
@@ -1928,6 +2042,25 @@ function mergeQueryObjects(base, extra) {
1928
2042
  }
1929
2043
  return extra;
1930
2044
  }
2045
+ function setValueAtPath(base, path, value) {
2046
+ const root = base && typeof base === "object" && !Array.isArray(base)
2047
+ ? { ...base }
2048
+ : {};
2049
+ const parts = path.split(".").filter(Boolean);
2050
+ if (!parts.length)
2051
+ return root;
2052
+ let current = root;
2053
+ for (const part of parts.slice(0, -1)) {
2054
+ const existing = current[part];
2055
+ const next = existing && typeof existing === "object" && !Array.isArray(existing)
2056
+ ? { ...existing }
2057
+ : {};
2058
+ current[part] = next;
2059
+ current = next;
2060
+ }
2061
+ current[parts[parts.length - 1]] = value;
2062
+ return root;
2063
+ }
1931
2064
  function clampTimeout(timeoutMs) {
1932
2065
  if (!Number.isFinite(timeoutMs))
1933
2066
  return DEFAULT_TIMEOUT_MS;
@@ -1995,14 +2128,29 @@ async function fetchAllPages(config, executeOnePage) {
1995
2128
  let pageCount = 0;
1996
2129
  let lastStatus = 0;
1997
2130
  let lastContentType = null;
2131
+ if (!config.cursorParam && !config.cursorBodyPath) {
2132
+ throw new Error("fetchAllPages requires cursorParam or cursorBodyPath to send the next cursor.");
2133
+ }
2134
+ if (config.cursorParam && config.cursorBodyPath) {
2135
+ throw new Error("fetchAllPages accepts exactly one cursor method: cursorParam or cursorBodyPath.");
2136
+ }
1998
2137
  while (pageCount < maxPages) {
1999
- const extraQuery = {};
2000
- if (cursor)
2001
- extraQuery[config.cursorParam] = cursor;
2002
- const page = await executeOnePage(pageCount > 0 ? extraQuery : undefined);
2138
+ const extra = {};
2139
+ if (cursor) {
2140
+ if (config.cursorParam)
2141
+ extra.query = { [config.cursorParam]: cursor };
2142
+ if (config.cursorBodyPath) {
2143
+ extra.bodyCursor = { path: config.cursorBodyPath, value: cursor };
2144
+ }
2145
+ }
2146
+ const page = await executeOnePage(pageCount > 0 ? extra : undefined);
2003
2147
  lastStatus = page.status;
2004
2148
  lastContentType = page.contentType;
2005
2149
  pageCount++;
2150
+ if (!page.ok) {
2151
+ const preview = page.text.replace(/\s+/g, " ").trim().slice(0, 500);
2152
+ throw new Error(`fetchAllPages request failed with HTTP ${page.status}${preview ? `: ${preview}` : ""}`);
2153
+ }
2006
2154
  let body;
2007
2155
  try {
2008
2156
  body = JSON.parse(page.text);