blob: 2ddcda73185cb031fe5a4caa521d5c572237339b [file]
export const ARTIFACT_KINDS = ['file', 'diff', 'html', 'image', 'pdf'] as const;
export type ArtifactKind = (typeof ARTIFACT_KINDS)[number];
export const ARTIFACT_SOURCES = [
'tool_result',
'tool_result_archive',
'synthesis_cache_block',
'history_compact_block',
'history_compact_source',
'provider_request_capture',
'subagent_writeback',
'deep_research',
'user_upload',
'export',
'snapshot',
'session_effect',
'fixture',
] as const;
export type ArtifactSource = (typeof ARTIFACT_SOURCES)[number];
export const ARTIFACT_STATUSES = ['live', 'deleted'] as const;
export type ArtifactStatus = (typeof ARTIFACT_STATUSES)[number];
export const ARTIFACT_ENTITY_ID_MAX_CHARS = 128;
export const ARTIFACT_TURN_KEY_MAX_CHARS = 512;
const ARTIFACT_ENTITY_ID_PATTERN = new RegExp(`^[A-Za-z0-9_-]{1,${ARTIFACT_ENTITY_ID_MAX_CHARS}}$`);
const ARTIFACT_TURN_KEY_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
export function isCanonicalArtifactEntityId(value: unknown): value is string {
return typeof value === 'string' && ARTIFACT_ENTITY_ID_PATTERN.test(value);
}
export function isArtifactTurnKey(value: unknown): value is string {
return (
typeof value === 'string' &&
value.length > 0 &&
value.length <= ARTIFACT_TURN_KEY_MAX_CHARS &&
!ARTIFACT_TURN_KEY_CONTROL_CHARACTERS.test(value)
);
}
export interface ArtifactDescriptor {
id: string;
sessionId: string;
/** Opaque, bounded Runtime turn key. It is a reference, not a filesystem path component. */
turnId: string;
createdAt: number;
name: string;
kind: ArtifactKind;
sizeBytes: number;
mimeType?: string;
source?: ArtifactSource;
summary?: string;
status: ArtifactStatus;
}
export interface ArtifactRecord extends ArtifactDescriptor {
/**
* Artifact-root-relative path. Never absolute and never exposed as a
* filesystem path to renderer code.
*/
relativePath: string;
/** Durable role for artifacts owned by a Deep Research workspace. */
deepResearchRole?: import('./deep-research-run.js').DeepResearchArtifactRole;
}
const ARTIFACT_USER_DELETE_ALLOWED_BY_SOURCE = {
tool_result: true,
tool_result_archive: false,
synthesis_cache_block: true,
history_compact_block: true,
history_compact_source: true,
provider_request_capture: true,
subagent_writeback: false,
deep_research: false,
user_upload: true,
export: true,
snapshot: true,
session_effect: false,
fixture: true,
} as const satisfies Record<ArtifactSource, boolean>;
export function canUserDeleteArtifact(record: Pick<ArtifactRecord, 'source'>): boolean {
return record.source === undefined || ARTIFACT_USER_DELETE_ALLOWED_BY_SOURCE[record.source];
}
export type ArtifactChangedReason = 'created' | 'deleted' | 'purged';
export interface ArtifactChangedEvent {
reason: ArtifactChangedReason;
artifactId: string;
sessionId: string;
ts: number;
}
export type ArtifactReadFailureReason =
| 'not_found'
| 'too_large'
| 'read_failed'
| 'not_allowed'
| 'deleted';
export type ArtifactBinaryReadFailureReason = ArtifactReadFailureReason | 'unsupported_mime';
export type ArtifactTextReadResult =
| { ok: true; text: string }
| { ok: false; reason: ArtifactReadFailureReason };
export type ArtifactBinaryReadResult =
| { ok: true; base64: string; mimeType: string }
| { ok: false; reason: ArtifactBinaryReadFailureReason };
export type ArtifactSaveFailureReason =
| 'canceled'
| 'not_found'
| 'not_allowed'
| 'deleted'
| 'write_failed';
export type ArtifactSaveResult =
| { ok: true; saved: string }
| { ok: false; reason: ArtifactSaveFailureReason };