@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
224 lines • 9.96 kB
TypeScript
import type { AttachmentStagingDescription, AttachmentStagingStore, ListStagedAttachmentsInput, ResolveAttachmentInput, StageAttachmentInput, StagedAttachment } from '../spi/attachment-staging.js';
import type { Actor, MessageAttachment } from '../types.js';
/** A media row — `@adonis-agora/media`'s `MediaRecord`. */
export interface MediaRecordLike {
id: string;
ownerType: string;
ownerId: string;
collection: string;
name: string;
fileName: string;
mimeType: string;
size: number;
disk: string;
path: string;
order: number;
customProperties: Record<string, unknown>;
conversions: Record<string, unknown>;
createdAt: Date;
updatedAt: Date;
}
/** `@adonis-agora/media`'s `MediaStore`, the part used here. */
export interface MediaStoreLike {
save(record: MediaRecordLike): Promise<MediaRecordLike>;
find(id: string): Promise<MediaRecordLike | null>;
delete(id: string): Promise<void>;
nextOrder(ownerType: string, ownerId: string, collection: string): Promise<number>;
/** Every media row of one owner (in one collection) — what the staged-attachment inventory reads. */
listByOwner?(ownerType: string, ownerId: string, collection?: string): Promise<MediaRecordLike[]>;
}
/** A Drive disk as `@adonis-agora/media` hands it out, the part used here. */
export interface MediaDiskLike {
put(key: string, contents: Uint8Array, options?: {
contentType?: string;
}): Promise<void>;
getBytes(key: string): Promise<Uint8Array>;
getUrl(key: string): Promise<string>;
getSignedUrl(key: string, options?: {
expiresIn?: string | number;
contentType?: string;
}): Promise<string>;
delete(key: string): Promise<void>;
/** Needed for resumable uploads only — whether the assembled object has landed. */
exists?(key: string): Promise<boolean>;
/** Needed for resumable uploads only — the assembled object's size. */
getMetaData?(key: string): Promise<{
contentLength: number;
}>;
}
/**
* `@adonis-agora/media`'s `ResumableUploadManager` (`media.resumable`), the part used here: open a
* tus session the bytes then stream to over the media library's own tus routes, or abort one.
*/
export interface MediaResumableLike {
createUpload(input: {
disk: string;
key: string;
size?: number;
contentType?: string;
metadata?: Record<string, string>;
}): Promise<{
id: string;
}>;
abort(id: string): Promise<void>;
}
/** What `beginUpload` takes: the file as the client describes it, before any byte moves. */
export interface BeginMediaUploadInput {
actor: Actor;
filename: string;
contentType: string;
/** Exact byte length — tus needs it up front, and completion is checked against it. */
size: number;
}
/** A tus session opened for one attachment. Stream the bytes to `location`, then complete. */
export interface BeginMediaUploadResult {
/** The attachment's id — what the chat turn sends as `{ mediaId }`. */
mediaId: string;
/** The tus upload id. */
uploadId: string;
/** The tus resource to `PATCH` the bytes to (relative to the app's origin). */
location: string;
}
/** A refusal of an upload request, carrying the HTTP status the route answers with. */
export declare class MediaUploadRefusedError extends Error {
readonly status: 400 | 409 | 413 | 415 | 422 | 501;
constructor(status: 400 | 409 | 413 | 415 | 422 | 501, message: string);
}
/** The agent store, the part used here — which media a live message of this actor carries. */
export interface ReferencedMediaLike {
referencedMediaIds?(actorRef: string, mediaIds: readonly string[]): Promise<string[]>;
}
/** What {@link MediaAttachmentStaging} is built from. */
export interface MediaAttachmentStagingDeps {
storage: MediaStorageLike;
store: MediaStoreLike;
/** Lets media referenced from the actor's own threads resolve (see `canAccess`). */
agentStore?: ReferencedMediaLike;
/**
* `media.resumable` — resumable (tus) uploads. Absent or `null` (`config/media.ts` has no
* `uploads.resumable`) → uploads are multipart only.
*/
uploads?: MediaResumableLike | null;
}
/** `@adonis-agora/media`'s `StorageManager`, the part used here. */
export interface MediaStorageLike {
readonly defaultDisk: string;
disk(name?: string): MediaDiskLike;
}
/** Options for `attachmentStores.media({ … })`. All optional. */
export interface MediaAttachmentsOptions {
/** Media collection the uploads go in. Default `agent-attachments`. */
collection?: string;
/** Owner type the rows carry (the owner id is the actor id). Default `agent-actor`. */
ownerType?: string;
/** Drive disk. Default the media library's default disk. */
disk?: string;
/** Per-file size cap. Default 20 MiB. */
maxBytes?: number;
/** Content types accepted. Default images (png/jpeg/gif/webp), PDF, plain text, CSV. */
allowedContentTypes?: readonly string[];
/**
* `private` (default): urls are signed and short-lived, re-minted on every read. `public`: the
* disk's plain url.
*/
visibility?: 'private' | 'public';
/** Lifetime of a signed url, in seconds. Default 7 days. */
urlExpiresInSeconds?: number;
/** Mint the url yourself (a CDN, a proxy route). Wins over `visibility`. */
resolveUrl?: (record: MediaRecordLike, context: {
disk: MediaDiskLike;
}) => string | Promise<string>;
/**
* Who may attach a file. By default (`allowed`): the actor that uploaded it, or one a message in
* one of the actor's OWN threads already carries (a fork, a regenerate) — the latter needs an
* agent store with `referencedMediaIds`, which both shipped stores have. Widen or narrow it here —
* e.g. let a support agent attach a customer's upload.
*/
canAccess?: (input: {
record: MediaRecordLike;
actor: Actor;
allowed: boolean;
}) => boolean | Promise<boolean>;
/**
* Where `@adonis-agora/media` mounts its tus routes (`uploads.resumable.routes.prefix` in
* `config/media.ts`). Default `/media/uploads/tus`. The `location` a resumable upload answers is
* `<tusBasePath>/<uploadId>`.
*/
tusBasePath?: string;
/** Clock and id seams, for tests. */
clock?: () => Date;
idGenerator?: () => string;
}
export declare const DEFAULT_AGENT_MEDIA_COLLECTION = "agent-attachments";
export declare const DEFAULT_AGENT_MEDIA_OWNER_TYPE = "agent-actor";
export declare const DEFAULT_AGENT_MEDIA_TUS_BASE_PATH = "/media/uploads/tus";
/**
* Chat attachments stored as media rows in their own collection, one owner per actor, on any Drive
* disk — S3, GCS, R2, the local filesystem. Uploads land at
* `<ownerType>/<actor>/<collection>/<id>/<file>`; a send names them by id and {@link resolve} mints
* the url the model fetches: the caller's own `resolveUrl`, a public url, a signed url, or — on a
* disk that cannot sign — the bytes inline as a `data:` url (logged once: fine for dev, not for
* production).
*/
export declare class MediaAttachmentStaging implements AttachmentStagingStore {
private readonly deps;
private readonly options;
private readonly collection;
private readonly ownerType;
private readonly maxBytes;
private readonly allowed;
private readonly now;
private readonly newId;
private warnedInline;
constructor(deps: MediaAttachmentStagingDeps, options?: MediaAttachmentsOptions);
describe(): AttachmentStagingDescription;
/**
* Open a tus session for one file, owned by `actor`. Everything is validated here, before any
* byte moves. The record exists from now on, `pending` until {@link completeUpload} sees the bytes.
*/
beginUpload(input: BeginMediaUploadInput): Promise<BeginMediaUploadResult>;
/**
* Confirm the bytes of a {@link beginUpload} landed, and whole. `null` for an unknown id or one
* that is not `actor`'s; `409` while the bytes are still missing; `422` (and the record dropped)
* when what arrived is not the size that was declared. Idempotent on a ready record.
*/
completeUpload(input: {
actor: Actor;
mediaId: string;
}): Promise<MessageAttachment | null>;
/** The actor drops one of their own attachments — aborting its upload if still in flight. */
discard(input: {
actor: Actor;
mediaId: string;
}): Promise<boolean>;
private get diskName();
stage(input: StageAttachmentInput): Promise<MessageAttachment>;
/**
* The attachment for `mediaId`, or `null` when it is unknown or this actor may not use it. The
* actor may use media they own, and media a message in one of THEIR threads already carries (a
* fork, a regenerate) — never anything else, unless `canAccess` says so.
*/
resolve(input: ResolveAttachmentInput): Promise<MessageAttachment | null>;
/**
* The actor's staged attachments, newest first: their own media rows in this store's collection —
* sent or not, and a resumable upload still in flight too (it is theirs, and it is staged). Metadata
* only; a url is only ever minted by {@link resolve}.
*/
list(input: ListStagedAttachmentsInput): Promise<StagedAttachment[]>;
private canUse;
private defaultAccess;
/** Delete an upload and its row. */
remove(mediaId: string): Promise<void>;
private validate;
private pathFor;
private saveRecord;
private ownRecord;
/**
* A ready record as-is; a pending one promoted to ready once its bytes are on the disk at exactly
* the declared size. `'missing'` while they are not; `'mismatch'` when they arrived at another size.
*/
private ensureReady;
private isOurs;
private toAttachment;
}
//# sourceMappingURL=media-attachment-staging.d.ts.map