diff --git a/src/libs/AssemblyAI/Generated/AssemblyAI.AutoSDKPager.g.cs b/src/libs/AssemblyAI/Generated/AssemblyAI.AutoSDKPager.g.cs
index 5f364d5..00bb518 100644
--- a/src/libs/AssemblyAI/Generated/AssemblyAI.AutoSDKPager.g.cs
+++ b/src/libs/AssemblyAI/Generated/AssemblyAI.AutoSDKPager.g.cs
@@ -10,6 +10,85 @@ namespace AssemblyAI
///
public static class AutoSDKPager
{
+ /// Copies request options for one generated next-URL page request.
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ public static global::AssemblyAI.AutoSDKRequestOptions CreatePageRequestOptions(
+ global::AssemblyAI.AutoSDKRequestOptions? source,
+ string? nextUrl)
+ {
+ var clone = new global::AssemblyAI.AutoSDKRequestOptions();
+ if (source is not null)
+ {
+ foreach (var header in source.Headers)
+ {
+ clone.Headers[header.Key] = header.Value;
+ }
+ foreach (var query in source.QueryParameters)
+ {
+ clone.QueryParameters[query.Key] = query.Value;
+ }
+ clone.Timeout = source.Timeout;
+ clone.Retry = source.Retry;
+ clone.ReadResponseAsString = source.ReadResponseAsString;
+ clone.Authorizations = source.Authorizations;
+ }
+ clone.PaginationUrl = nextUrl;
+ return clone;
+ }
+
+ /// Returns a validated next-page URL, or null for an ordinary request.
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ public static string? ResolvePageUrl(
+ string path,
+ global::AssemblyAI.AutoSDKRequestOptions? requestOptions,
+ global::System.Uri? baseAddress)
+ {
+ var nextUrl = requestOptions?.PaginationUrl;
+ if (string.IsNullOrWhiteSpace(nextUrl))
+ {
+ return null;
+ }
+
+ if (baseAddress is null &&
+ global::System.Uri.TryCreate(path, global::System.UriKind.Absolute, out var pathUri) &&
+ (pathUri.Scheme == global::System.Uri.UriSchemeHttp || pathUri.Scheme == global::System.Uri.UriSchemeHttps))
+ {
+ baseAddress = pathUri;
+ }
+ if (baseAddress is not null)
+ {
+ var resolved = new global::System.Uri(baseAddress, nextUrl!);
+ EnsureSameOrigin(resolved.AbsoluteUri, baseAddress);
+ return resolved.AbsoluteUri;
+ }
+ if (global::System.Uri.TryCreate(nextUrl, global::System.UriKind.Absolute, out _))
+ {
+ throw new global::System.InvalidOperationException(
+ "Cannot follow an absolute next-page URL without a known base address.");
+ }
+
+ return nextUrl;
+ }
+
+ /// Uses the HTTP client's origin, or the operation server when it has one.
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ public static global::System.Uri? GetPaginationBaseAddress(
+ global::System.Uri? clientBaseAddress,
+ string operationBaseUrl)
+ {
+ if (clientBaseAddress is not null)
+ {
+ return clientBaseAddress;
+ }
+
+ return global::System.Uri.TryCreate(
+ operationBaseUrl,
+ global::System.UriKind.Absolute,
+ out var serverBaseAddress)
+ ? serverBaseAddress
+ : null;
+ }
+
///
/// Paginates through offset/page-number-style endpoints. Calls
/// repeatedly with an incrementing page number until the page returns no items, or the
@@ -74,7 +153,7 @@ public static class AutoSDKPager
///
/// Paginates through cursor/next-page-token-style endpoints. Calls
/// with the cursor returned by the previous page until
- /// returns null or whitespace.
+ /// returns null or whitespace, or a cursor repeats.
///
///
///
@@ -96,9 +175,14 @@ public static class AutoSDKPager
extractNextCursor = extractNextCursor ?? throw new global::System.ArgumentNullException(nameof(extractNextCursor));
var cursor = initialCursor;
+ var seenCursors = new global::System.Collections.Generic.HashSet(global::System.StringComparer.Ordinal);
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
+ if (!string.IsNullOrWhiteSpace(cursor) && !seenCursors.Add(cursor!))
+ {
+ yield break;
+ }
var response = await fetchPage(cursor, cancellationToken).ConfigureAwait(false);
if (response is null)
@@ -156,9 +240,14 @@ public static class AutoSDKPager
}
var url = initialUrl;
+ var seenUrls = new global::System.Collections.Generic.HashSet(global::System.StringComparer.Ordinal);
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
+ if (!string.IsNullOrWhiteSpace(url) && !seenUrls.Add(url!))
+ {
+ yield break;
+ }
var response = await fetchPage(url, cancellationToken).ConfigureAwait(false);
if (response is null)
@@ -224,7 +313,7 @@ public static class AutoSDKPager
/// Paginates through APIs that embed an absolute next URL in the response body
/// (Firecrawl, Slack, Linear, Notion, HubSpot, etc.). Calls
/// with the URL returned by from the previous page
- /// until the extractor returns null/whitespace.
+ /// until the extractor returns null/whitespace or a URL repeats.
///
/// Security: when is provided, every next-URL
/// origin must match it before the next fetch runs. An unchecked walker would let a
@@ -254,12 +343,17 @@ public static class AutoSDKPager
extractNextUrl = extractNextUrl ?? throw new global::System.ArgumentNullException(nameof(extractNextUrl));
var url = initialUrl;
+ var seenUrls = new global::System.Collections.Generic.HashSet(global::System.StringComparer.Ordinal);
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
if (!string.IsNullOrWhiteSpace(url))
{
+ if (!seenUrls.Add(url!))
+ {
+ yield break;
+ }
EnsureSameOrigin(url!, baseAddress);
}
@@ -291,7 +385,8 @@ public static class AutoSDKPager
///
/// Validates that shares an origin with .
/// Throws when they differ so an embedded foreign URL can't redirect the SDK (and the
- /// caller's Authorization header) to a hostile host. When
+ /// caller's Authorization header) to a hostile host. Relative URLs are resolved
+ /// before comparison, including protocol-relative URLs. When
/// is null, validation is skipped — callers should pass HttpClient.BaseAddress.
///
public static void EnsureSameOrigin(string nextUrl, global::System.Uri? baseAddress)
@@ -301,11 +396,7 @@ public static void EnsureSameOrigin(string nextUrl, global::System.Uri? baseAddr
return;
}
- if (!global::System.Uri.TryCreate(nextUrl, global::System.UriKind.Absolute, out var absolute))
- {
- // Relative URLs are safe — they're resolved against the client's BaseAddress on send.
- return;
- }
+ var absolute = new global::System.Uri(baseAddress, nextUrl);
if (!string.Equals(absolute.Scheme, baseAddress.Scheme, global::System.StringComparison.OrdinalIgnoreCase) ||
!string.Equals(absolute.Host, baseAddress.Host, global::System.StringComparison.OrdinalIgnoreCase) ||
diff --git a/src/libs/AssemblyAI/Generated/AssemblyAI.OptionsSupport.g.cs b/src/libs/AssemblyAI/Generated/AssemblyAI.OptionsSupport.g.cs
index 3261d49..1fd16c4 100644
--- a/src/libs/AssemblyAI/Generated/AssemblyAI.OptionsSupport.g.cs
+++ b/src/libs/AssemblyAI/Generated/AssemblyAI.OptionsSupport.g.cs
@@ -308,6 +308,9 @@ private static void ApplyAuthorization(
///
public sealed class AutoSDKRequestOptions
{
+ // Set only by AutoSDKPager on a generated next-URL paging call.
+ internal string? PaginationUrl { get; set; }
+
///
/// Additional headers applied after generated and client-level headers.
///