/**
 * Asset service for Studiograph
 *
 * Manages all media assets (images, videos, audio, documents)
 * with pluggable storage backends (local filesystem or R2)
 */
import type { AssetInfo, AssetRecord, AssetConfig } from './base.js';
import { encodeAssetFilename } from '../../core/artifact-asset-urls.js';
export * from './base.js';
/**
 * Result of `graph.uploadAsset`. Extends AssetInfo with the canonical public
 * URL the caller should embed/store (`publicUrl`) and, on the immutable-key
 * path, the generated `assetId`. Centralizing the URL here keeps callers from
 * re-deriving `/api/assets/...` (and getting the scheme wrong).
 */
export interface AssetUploadResult extends AssetInfo {
    assetId?: string;
    publicUrl: string;
}
export declare class AssetService {
    private backend;
    private storage;
    constructor(config: AssetConfig);
    /** Which backend this service is bound to. Routes that need different
     *  serving strategies per backend (e.g. stream local files vs. 302 to
     *  pre-signed URL for R2) branch on this. */
    getStorage(): 'local' | 'r2';
    /** Whether the backend can mint short-lived pre-signed download URLs.
     *  True for R2; false for local (where the HTTP server streams files
     *  directly). */
    supportsSignedUrls(): boolean;
    /** Whether the backend supports the full pre-signed PUT + HEAD + copy
     *  lifecycle needed for the MCP presign flow. Tied to all three because a
     *  backend that signs PUT but can't HEAD-verify the upload would break
     *  the finalize contract. R2 supports all three; local supports none. */
    supportsPresignedUploads(): boolean;
    /** Generate a short-lived pre-signed download URL for an asset.
     *  Throws if the backend doesn't support pre-signed URLs (local).
     *  Default expiry is 4 hours (14400s) — see R2AssetBackend.getSignedUrl
     *  for the rationale. `method` selects the HTTP verb the URL is signed
     *  against — HEAD requests (link unfurls, OpenGraph) need a HEAD-signed
     *  URL because pre-signed URLs are method-bound. */
    getSignedUrl(entityType: string, entityId: string, filename: string, keyPrefix?: string, expiresIn?: number, method?: 'GET' | 'HEAD'): Promise<string>;
    /** Reject key segments that could escape the prefix. */
    private assertSafeSegment;
    /** Upload bytes under an immutable assetId key. Caller records the index row. */
    uploadByAssetId(assetId: string, filename: string, buffer: Buffer): Promise<AssetInfo>;
    getSignedUrlByAssetId(assetId: string, filename: string, expiresIn?: number, method?: 'GET' | 'HEAD'): Promise<string>;
    deleteByAssetId(assetId: string, filename: string): Promise<void>;
    existsByAssetId(assetId: string, filename: string): Promise<boolean>;
    private pendingKey;
    private canonicalKey;
    signPendingPutUrl(assetId: string, filename: string, contentType: string, expiresIn?: number): Promise<{
        uploadUrl: string;
        headers: Record<string, string>;
    }>;
    /** HEAD the pending blob. Returns null when the agent never PUT (or the
     *  TTL already expired). */
    headPending(assetId: string, filename: string): Promise<{
        contentType: string;
        size: number;
    } | null>;
    /** Server-side copy pending → canonical (R2's CopyObject — no download). */
    copyPendingToCanonical(assetId: string, filename: string): Promise<void>;
    deletePending(assetId: string, filename: string): Promise<void>;
    /** Bucket lifecycle rules summary for the MCP presign flow's boot-time
     *  backstop. Returns `null` when the backend has no lifecycle support
     *  (local) or when the bucket has no rules configured at all. Throws on
     *  permission / network errors so the caller can decide whether to
     *  degrade or propagate. */
    getBucketLifecycle(): Promise<Array<{
        prefix: string | undefined;
        expirationDays: number | undefined;
        enabled: boolean;
    }> | null>;
    /** Download an object's bytes by raw key. Validates every path segment —
     *  the key is derived from a stored `asset_url`, so a `..` segment must
     *  never reach the backend's path join (arbitrary-file read). */
    download(key: string): Promise<Buffer>;
    /** Upload an object's bytes by raw key (non-catalog keyspaces — e.g. the chat
     *  conversation-scratch images, alongside the existing `pending` keyspace).
     *  Validates every path segment. Does NOT create an asset-index row. */
    uploadByKey(key: string, buffer: Buffer, contentType: string): Promise<AssetInfo>;
    /** Delete an object's bytes by raw key (non-catalog keyspaces). Validates
     *  every path segment. No-op-safe at the backend level. */
    deleteByKey(key: string): Promise<void>;
    /** Pre-signed download URL for a raw key (R2 only; local throws). Validates
     *  every path segment. Used to 302-redirect non-catalog objects (scratch
     *  images) the same way catalog assets are served. */
    getSignedUrlForKey(key: string, method?: 'GET' | 'HEAD', expiresIn?: number): Promise<string>;
    /** Server-side copy between raw keys (migration + transfer-copy). Validates
     *  every path segment of both keys — the migration derives srcKey from
     *  user-editable body URLs, so a `..` in a key segment must never reach the
     *  backend's path join (arbitrary-file read/write). */
    copy(srcKey: string, destKey: string): Promise<void>;
    /**
     * Upload asset for an entity
     */
    upload(entityType: string, entityId: string, filename: string, buffer: Buffer, keyPrefix?: string): Promise<AssetInfo>;
    /**
     * List all assets for an entity
     */
    list(entityType: string, entityId: string, keyPrefix?: string): Promise<AssetInfo[]>;
    /**
     * List every asset in the backend in one round-trip, with their
     * (entityType, entityId) origins parsed from the storage key.
     */
    listAll(): Promise<AssetRecord[]>;
    /**
     * Delete a single asset
     */
    delete(entityType: string, entityId: string, filename: string, keyPrefix?: string): Promise<void>;
    /**
     * Delete all assets for an entity (when entity is deleted)
     */
    deleteAll(entityType: string, entityId: string, keyPrefix?: string): Promise<number>;
    /**
     * Check if an asset exists
     */
    exists(entityType: string, entityId: string, filename: string, keyPrefix?: string): Promise<boolean>;
    /**
     * Get asset info without downloading
     */
    getInfo(entityType: string, entityId: string, filename: string, keyPrefix?: string): Promise<AssetInfo | null>;
}
/**
 * Convert an `AssetInfo` returned by a backend into the URL stored in
 * an entity's `asset_url` (or used as the `src` of an inline embed).
 *
 * Stage 2 of the asset architecture (see docs/asset-architecture.md):
 * always emit the proxied `/api/assets/{type}/{id}/{file}` path, regardless
 * of backend. The route is the single chokepoint that enforces per-repo
 * authz — local files are streamed directly, R2-backed assets get a 302
 * to a short-lived pre-signed URL. New uploads never persist absolute
 * R2 URLs in frontmatter; the migration CLI rewrites legacy ones.
 *
 * Centralised here because three different call sites used to inline this
 * ternary, and getting it wrong silently breaks image rendering.
 */
export declare function publicUrlFor(_asset: {
    url: string;
    path: string;
    filename: string;
}, entityType: string, entityId: string): string;
/**
 * Immutable-key public URL: `/api/assets/{assetId}/{filename}`. Two path
 * segments after `/api/assets/` (vs three for the legacy form), so the new
 * and legacy serving routes disambiguate by arity during cutover.
 */
export declare function publicUrlForAssetId(assetId: string, filename: string): string;
export { encodeAssetFilename };
