/**
 * Cloudflare R2 asset backend
 *
 * S3-compatible object storage with zero egress fees
 */
import type { AssetBackend, AssetInfo, AssetRecord, R2Config } from './base.js';
export declare class R2AssetBackend implements AssetBackend {
    private client;
    private bucket;
    private publicUrl;
    constructor(config: R2Config);
    /**
     * Upload asset to R2
     */
    upload(key: string, buffer: Buffer, contentType: string): Promise<AssetInfo>;
    /**
     * Server-side copy via S3 CopyObjectCommand (R2 supports it) — no download.
     * CopySource must be URL-encoded and bucket-qualified. Overwrites the
     * destination, so migration re-runs are idempotent.
     */
    copy(srcKey: string, destKey: string): Promise<void>;
    /**
     * Download an object's bytes from R2 into a Buffer.
     */
    download(key: string): Promise<Buffer>;
    /**
     * List assets with a given prefix
     */
    list(prefix: string): Promise<AssetInfo[]>;
    /**
     * List every asset in the bucket via paginated ListObjectsV2. Parses
     * `{type}/{id}/{filename}` out of each key and guesses content type
     * from the extension — no per-object HEAD calls.
     */
    listAll(): Promise<AssetRecord[]>;
    /**
     * Delete a single asset
     */
    delete(key: string): Promise<void>;
    /**
     * Delete all assets with a given prefix
     */
    deleteAll(prefix: string): Promise<number>;
    /**
     * Generate a short-lived pre-signed URL for a stored object.
     *
     * Stage 2 of the asset architecture: rather than handing browsers a
     * permanent public URL, the GET /api/assets/* route authorizes per-repo
     * and 302-redirects to one of these. The bucket can then be flipped to
     * private without breaking the workflow.
     *
     * Default expiry is 4 hours (14400s). Long enough that Range-request
     * continuation on multi-hour media doesn't get a SignatureExpired mid-
     * stream; short enough that out-of-band URL leakage has a bounded
     * exposure window. The leak math doesn't meaningfully shift between
     * 15 min and 4 hr — anything that exfiltrates a URL fast enough to
     * matter does so within either window.
     *
     * `method` selects the HTTP verb the URL is signed against — pre-signed
     * URLs are method-bound, so HEAD requests need a HEAD-signed URL.
     * Without this, link-unfurlers and OpenGraph crawlers that issue HEAD
     * after following a redirect get 403 SignatureDoesNotMatch from R2.
     */
    getSignedUrl(key: string, expiresIn?: number, method?: 'GET' | 'HEAD'): Promise<string>;
    /**
     * Generate a short-lived pre-signed PUT URL the client uploads to directly.
     *
     * Counterpart to `getSignedUrl()` — same SDK machinery, different verb. The
     * `ContentType` is pinned into the signature: if the client sends a PUT
     * with any other `Content-Type` header, R2 rejects with
     * `SignatureDoesNotMatch`. This is the load-bearing piece of the MCP
     * presign flow's mime-type guarantee — the agent cannot upload an SVG
     * disguised as `image/png` because the bytes would be uploadable but
     * unmatched-CT requests fail at the wire.
     *
     * Default `expiresIn` is 900s (15 min) — short window matches the token's
     * `exp` so a leaked URL has the same blast radius as a leaked token.
     *
     * Returns the URL and the headers the client must send verbatim (notably
     * `Content-Type`). The SDK's `signableHeaders` defaults already cover
     * `Content-Type` and standard hosted headers; we don't add custom signed
     * headers to keep the agent's PUT trivial.
     */
    getSignedPutUrl(key: string, contentType: string, expiresIn?: number): Promise<{
        uploadUrl: string;
        headers: Record<string, string>;
    }>;
    /**
     * Fetch ContentType + ContentLength via HEAD. Returns `null` on 404 so
     * callers can distinguish "object not present" from a network/auth error.
     * Used by the MCP finalize flow to verify the pre-signed PUT landed AND
     * R2 stored the contract'd Content-Type before promoting pending/ →
     * canonical.
     */
    head(key: string): Promise<{
        contentType: string;
        size: number;
    } | null>;
    /**
     * Bucket lifecycle rules summarized for the MCP presign flow's boot-time
     * backstop (see docs/r2-lifecycle.md). The full S3 schema is much richer
     * than what we need — only the prefix + ExpirationDays per rule matters
     * for the "is `pending/` reaped within 24h?" check.
     *
     * `null` when the bucket has no lifecycle configuration at all (R2
     * returns `NoSuchLifecycleConfiguration`). Throws on other SDK errors so
     * the caller can decide whether to degrade or propagate.
     */
    getBucketLifecycle(): Promise<Array<{
        prefix: string | undefined;
        expirationDays: number | undefined;
        enabled: boolean;
    }> | null>;
    /**
     * Check if an asset exists
     */
    exists(key: string): Promise<boolean>;
    /**
     * Guess content type from file extension
     */
    private guessContentType;
}
