import type { ArtifactReference, FileReference } from './artifacts'import { MAX_ACP_MESSAGE_IMAGE_BYTES_PER_MESSAGE, MAX_ACP_MESSAGE_IMAGES_PER_MESSAGE, sanitizeAcpMessageImage, type AcpMessageImage} from './acp'import { createArtifactVersionLocator, parseArtifactVersionLocator } from './artifact-provenance'import { parseLiteratureAttachmentVersionReference } from './literature-attachment-reference'import { createUploadVersionReference, parseUploadVersionReference } from './uploads' export const ANNOTATION_LIMITS = Object.freeze({ count: 10, quote: 4_000, note: 2_000, payload: 10_000, messagePayload: 100_000}) export type SessionTextAnnotationItemType = 'tool-activity' | 'plan' | 'elicitation' | 'delegated-elicitation' | 'subagent-message' const SESSION_TEXT_ANNOTATION_ITEM_TYPES = new Set<SessionTextAnnotationItemType>([ 'tool-activity', 'plan', 'elicitation', 'delegated-elicitation', 'subagent-message']) export type TextAnnotationSource = | Readonly<{ kind: 'agent-message' sessionId: string messageId: string }> | Readonly<{ kind: 'project-file' projectId: string path: string name?: string fileSource?: 'artifact' | 'upload' sourceFileId?: string versionId?: string sessionId?: string }> | Readonly<{ kind: 'session-item' sessionId: string itemId: string itemType: SessionTextAnnotationItemType sectionId?: string }> type ProjectFileAnnotationSource = Extract<TextAnnotationSource, { kind: 'project-file' }> export type ManagedProjectFileAnnotationIdentity = Readonly<{ fileSource: 'artifact' | 'upload' fileId: string versionId: string}> // Managed annotations carry one logical file identity and one immutable Version. A null result is// fail-closed malformed input; undefined is a legacy raw-path annotation with no managed identity.export const resolveManagedProjectFileAnnotationIdentity = ( source: ProjectFileAnnotationSource): ManagedProjectFileAnnotationIdentity | null | undefined => { const artifact = parseArtifactVersionLocator(source.path) const upload = parseUploadVersionReference(source.path) const hasExplicitSource = source.fileSource !== undefined const hasExplicitFileId = source.sourceFileId !== undefined if (hasExplicitSource !== hasExplicitFileId) return null if ( artifact && (artifact.projectId !== source.projectId || (source.sessionId !== undefined && artifact.appSessionId !== source.sessionId) || (source.versionId !== undefined && artifact.versionId !== source.versionId)) ) { return null } if ( upload && ((upload.projectId !== undefined && upload.projectId !== source.projectId) || (source.sessionId !== undefined && upload.sessionId !== undefined && upload.sessionId !== source.sessionId) || (source.versionId !== undefined && upload.versionId !== source.versionId)) ) { return null } if (source.fileSource && source.sourceFileId) { if (!source.versionId) return null const locatorSource = artifact ? 'artifact' : upload ? 'upload' : undefined const locatorFileId = artifact?.artifactId ?? upload?.fileId if ( (locatorSource && locatorSource !== source.fileSource) || (locatorFileId && locatorFileId !== source.sourceFileId) ) { return null } return { fileSource: source.fileSource, fileId: source.sourceFileId, versionId: source.versionId } } if (artifact) { return { fileSource: 'artifact', fileId: artifact.artifactId, versionId: artifact.versionId } } if (upload?.fileId) { return { fileSource: 'upload', fileId: upload.fileId, versionId: upload.versionId } } return undefined} export type SessionTextAnnotationSource = Exclude< TextAnnotationSource, Readonly<{ kind: 'project-file'; projectId: string; path: string }>> export type TextAnnotationAnchor = Pick<PdfTextSelector, 'position' | 'prefix' | 'suffix'> export type TextAnnotation = Readonly<{ id: string kind: 'text' target: 'agent' quote: string anchor?: TextAnnotationAnchor note?: string source: TextAnnotationSource}> export type ImagePointAnnotation = Readonly<{ id: string kind: 'image-point' target: 'agent' note: string source: Readonly<{ kind: 'artifact-version' | 'upload-version' projectId: string sessionId: string versionId: string name: string path: string mimeType: string }> point: Readonly<{ x: number; y: number }> naturalSize: Readonly<{ width: number; height: number }>}> export type PdfNormalizedQuad = Readonly<{ x: number y: number width: number height: number}> export type PdfNormalizedRect = PdfNormalizedQuad export type PdfTextSelector = Readonly<{ kind: 'text' pageNumber: number exact: string prefix?: string suffix?: string position: Readonly<{ start: number; end: number }> quads: readonly PdfNormalizedQuad[] extractorVersion: string}> export type PdfRegionSelector = Readonly<{ kind: 'region' pageNumber: number rect: PdfNormalizedRect pageRotation: number text?: string}> & ( | Readonly<{ image: AcpMessageImage; imageOmissionReason?: never }> | Readonly<{ image?: never; imageOmissionReason: 'session-budget' }> ) export type PdfAnnotation = Readonly<{ id: string kind: 'pdf' target: 'agent' note?: string source: Readonly<{ kind: 'artifact-version' | 'upload-version' | 'literature-attachment-version' projectId: string sessionId?: string versionId: string name: string path: string checksum: string }> selector: PdfTextSelector | PdfRegionSelector}> export type Annotation = TextAnnotation | ImagePointAnnotation | PdfAnnotation export const annotationRequiresImageInput = (annotation: Annotation): boolean => annotation.kind === 'image-point' || (annotation.kind === 'pdf' && annotation.selector.kind === 'region' && !!annotation.selector.image) export type PreparedImagePoint = Readonly<{ annotationId: string number: number attachment: number note: string sourceKind: ImagePointAnnotation['source']['kind'] versionId: string name: string mimeType: string x: number y: number imageWidth: number imageHeight: number}> export type PreparedImagePointAnnotations = Readonly<{ attachments: ArtifactReference[] points: PreparedImagePoint[]}> export type PreparedAnnotationsForAgent = Readonly<{ promptText: string referencedArtifacts?: FileReference[] images?: AcpMessageImage[]}> export type SideChatAnnotationItem = | Readonly<{ type: 'quote' content: string source?: | TextAnnotationSource | Readonly<{ kind: 'pdf' versionId: string name: string checksum: string page: number selector: Omit<PdfTextSelector, 'kind' | 'pageNumber' | 'exact'> }> instruction?: string }> | Readonly<{ type: 'image-point' source: | Readonly<{ kind: 'artifact-version' artifactId: string versionId: string name: string }> | Readonly<{ kind: 'upload-version'; versionId: string; name: string }> x: number y: number instruction: string }> export type ParsedSideChatAnnotationText = Readonly<{ text: string items: readonly SideChatAnnotationItem[]}> export type AnnotationValidationError = | 'too-many' | 'quote-too-long' | 'note-too-long' | 'payload-too-large' | 'visual-model-required' | 'invalid' const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value) const trimmed = (value: unknown): string | undefined => { if (typeof value !== 'string') return undefined const result = value.trim() return result || undefined} const boundedString = (value: unknown, limit: number): string | undefined => typeof value === 'string' && value.length > 0 && value.length <= limit ? value : undefined const normalizedQuad = (value: unknown): PdfNormalizedQuad | undefined => { if (!isRecord(value)) return undefined const { x, y, width, height } = value if ( typeof x !== 'number' || typeof y !== 'number' || typeof width !== 'number' || typeof height !== 'number' || ![x, y, width, height].every(Number.isFinite) || x < 0 || y < 0 || width <= 0 || height <= 0 || x + width > 1.000_001 || y + height > 1.000_001 ) { return undefined } return { x, y, width, height }} const sanitizePdfAnnotation = ( value: Record<string, unknown>, id: string): PdfAnnotation | undefined => { if (!isRecord(value.source) || !isRecord(value.selector)) return undefined const source = value.source const selector = value.selector const sourceKind = source.kind const sourceSessionId = trimmed(source.sessionId) const sourceSessionIdRequired = sourceKind === 'artifact-version' || sourceKind === 'upload-version' const pageNumber = selector.pageNumber if ( (!sourceSessionIdRequired && sourceKind !== 'literature-attachment-version') || !trimmed(source.projectId) || (sourceSessionIdRequired && !sourceSessionId) || (!sourceSessionIdRequired && sourceSessionId !== undefined) || !trimmed(source.versionId) || !trimmed(source.name) || !trimmed(source.path) || typeof source.checksum !== 'string' || !/^[a-f0-9]{64}$/u.test(source.checksum) || typeof pageNumber !== 'number' || !Number.isInteger(pageNumber) || pageNumber < 1 ) { return undefined } const sanitizedSource: PdfAnnotation['source'] = { kind: sourceKind, projectId: trimmed(source.projectId)!, ...(sourceSessionId ? { sessionId: sourceSessionId } : {}), versionId: trimmed(source.versionId)!, name: trimmed(source.name)!, path: trimmed(source.path)!, checksum: source.checksum } const note = trimmed(value.note) if (selector.kind === 'region') { const rect = normalizedQuad(selector.rect) const text = selector.text === undefined ? undefined : boundedString(selector.text, ANNOTATION_LIMITS.quote) const image = sanitizeAcpMessageImage(selector.image) if ( !rect || typeof selector.pageRotation !== 'number' || ![0, 90, 180, 270].includes(selector.pageRotation) || (selector.text !== undefined && !text) || (!image && !(selector.image === undefined && selector.imageOmissionReason === 'session-budget')) || (image !== undefined && selector.imageOmissionReason !== undefined) ) { return undefined } return { id, kind: 'pdf', target: 'agent', ...(note ? { note } : {}), source: sanitizedSource, selector: { kind: 'region', pageNumber, rect, pageRotation: selector.pageRotation, ...(text ? { text } : {}), ...(image ? { image } : { imageOmissionReason: 'session-budget' as const }) } } } const exact = boundedString(selector.exact, ANNOTATION_LIMITS.quote) const prefix = selector.prefix === undefined ? undefined : boundedString(selector.prefix, 256) const suffix = selector.suffix === undefined ? undefined : boundedString(selector.suffix, 256) const position = selector.position const quads = Array.isArray(selector.quads) ? selector.quads.map(normalizedQuad) : undefined if ( selector.kind !== 'text' || !exact || (selector.prefix !== undefined && !prefix) || (selector.suffix !== undefined && !suffix) || !isRecord(position) || typeof position.start !== 'number' || typeof position.end !== 'number' || !Number.isInteger(position.start) || !Number.isInteger(position.end) || position.start < 0 || position.end !== position.start + exact.length || !quads || quads.length === 0 || quads.some((quad) => !quad) || !trimmed(selector.extractorVersion) ) { return undefined } return { id, kind: 'pdf', target: 'agent', ...(note ? { note } : {}), source: sanitizedSource, selector: { kind: 'text', pageNumber, exact, ...(prefix ? { prefix } : {}), ...(suffix ? { suffix } : {}), position: { start: position.start, end: position.end }, quads: quads as PdfNormalizedQuad[], extractorVersion: trimmed(selector.extractorVersion)! } }} const sanitizeTextSource = (value: unknown): TextAnnotationSource | undefined => { if (!isRecord(value)) return undefined const kind = value.kind if (kind === 'agent-message') { const sessionId = trimmed(value.sessionId) const messageId = trimmed(value.messageId) return sessionId && messageId ? { kind, sessionId, messageId } : undefined } if (kind === 'project-file') { const projectId = trimmed(value.projectId) const path = trimmed(value.path) if (!projectId || !path) return undefined const name = trimmed(value.name) const fileSource = value.fileSource === 'artifact' || value.fileSource === 'upload' ? value.fileSource : undefined const sourceFileId = trimmed(value.sourceFileId) if (Boolean(fileSource) !== Boolean(sourceFileId)) return undefined const versionId = trimmed(value.versionId) const source: ProjectFileAnnotationSource = { kind, projectId, path, ...(name ? { name } : {}), ...(fileSource && sourceFileId ? { fileSource, sourceFileId } : {}), ...(versionId ? { versionId } : {}), ...(trimmed(value.sessionId) ? { sessionId: trimmed(value.sessionId) } : {}) } return resolveManagedProjectFileAnnotationIdentity(source) === null ? undefined : source } if (kind === 'session-item') { const sessionId = trimmed(value.sessionId) const itemId = trimmed(value.itemId) const itemType = trimmed(value.itemType) if ( !sessionId || !itemId || !itemType || !SESSION_TEXT_ANNOTATION_ITEM_TYPES.has(itemType as SessionTextAnnotationItemType) ) { return undefined } return { kind, sessionId, itemId, itemType: itemType as SessionTextAnnotationItemType, ...(trimmed(value.sectionId) ? { sectionId: trimmed(value.sectionId) } : {}) } } return undefined} const sanitizeTextAnchor = (value: unknown, quote: string): TextAnnotationAnchor | undefined => { if (!isRecord(value) || !isRecord(value.position)) return undefined const { start, end } = value.position if ( typeof start !== 'number' || !Number.isSafeInteger(start) || start < 0 || typeof end !== 'number' || !Number.isSafeInteger(end) || end !== start + quote.length || (value.prefix !== undefined && !boundedString(value.prefix, 256)) || (value.suffix !== undefined && !boundedString(value.suffix, 256)) ) return undefined return { position: { start, end }, ...(typeof value.prefix === 'string' ? { prefix: value.prefix } : {}), ...(typeof value.suffix === 'string' ? { suffix: value.suffix } : {})Lines 1–500 of 1177. Load more
import type { ArtifactReference, FileReference } from './artifacts' import { MAX_ACP_MESSAGE_IMAGE_BYTES_PER_MESSAGE, MAX_ACP_MESSAGE_IMAGES_PER_MESSAGE, sanitizeAcpMessageImage, type AcpMessageImage } from './acp' import { createArtifactVersionLocator, parseArtifactVersionLocator } from './artifact-provenance' import { parseLiteratureAttachmentVersionReference } from './literature-attachment-reference' import { createUploadVersionReference, parseUploadVersionReference } from './uploads' export const ANNOTATION_LIMITS = Object.freeze({ count: 10, quote: 4_000, note: 2_000, payload: 10_000, messagePayload: 100_000 }) export type SessionTextAnnotationItemType = 'tool-activity' | 'plan' | 'elicitation' | 'delegated-elicitation' | 'subagent-message' const SESSION_TEXT_ANNOTATION_ITEM_TYPES = new Set<SessionTextAnnotationItemType>([ 'tool-activity', 'plan', 'elicitation', 'delegated-elicitation', 'subagent-message' ]) export type TextAnnotationSource = | Readonly<{ kind: 'agent-message' sessionId: string messageId: string }> | Readonly<{ kind: 'project-file' projectId: string path: string name?: string fileSource?: 'artifact' | 'upload' sourceFileId?: string versionId?: string sessionId?: string }> | Readonly<{ kind: 'session-item' sessionId: string itemId: string itemType: SessionTextAnnotationItemType sectionId?: string }> type ProjectFileAnnotationSource = Extract<TextAnnotationSource, { kind: 'project-file' }> export type ManagedProjectFileAnnotationIdentity = Readonly<{ fileSource: 'artifact' | 'upload' fileId: string versionId: string }> // Managed annotations carry one logical file identity and one immutable Version. A null result is // fail-closed malformed input; undefined is a legacy raw-path annotation with no managed identity. export const resolveManagedProjectFileAnnotationIdentity = ( source: ProjectFileAnnotationSource ): ManagedProjectFileAnnotationIdentity | null | undefined => { const artifact = parseArtifactVersionLocator(source.path) const upload = parseUploadVersionReference(source.path) const hasExplicitSource = source.fileSource !== undefined const hasExplicitFileId = source.sourceFileId !== undefined if (hasExplicitSource !== hasExplicitFileId) return null if ( artifact && (artifact.projectId !== source.projectId || (source.sessionId !== undefined && artifact.appSessionId !== source.sessionId) || (source.versionId !== undefined && artifact.versionId !== source.versionId)) ) { return null } if ( upload && ((upload.projectId !== undefined && upload.projectId !== source.projectId) || (source.sessionId !== undefined && upload.sessionId !== undefined && upload.sessionId !== source.sessionId) || (source.versionId !== undefined && upload.versionId !== source.versionId)) ) { return null } if (source.fileSource && source.sourceFileId) { if (!source.versionId) return null const locatorSource = artifact ? 'artifact' : upload ? 'upload' : undefined const locatorFileId = artifact?.artifactId ?? upload?.fileId if ( (locatorSource && locatorSource !== source.fileSource) || (locatorFileId && locatorFileId !== source.sourceFileId) ) { return null } return { fileSource: source.fileSource, fileId: source.sourceFileId, versionId: source.versionId } } if (artifact) { return { fileSource: 'artifact', fileId: artifact.artifactId, versionId: artifact.versionId } } if (upload?.fileId) { return { fileSource: 'upload', fileId: upload.fileId, versionId: upload.versionId } } return undefined } export type SessionTextAnnotationSource = Exclude< TextAnnotationSource, Readonly<{ kind: 'project-file'; projectId: string; path: string }> > export type TextAnnotationAnchor = Pick<PdfTextSelector, 'position' | 'prefix' | 'suffix'> export type TextAnnotation = Readonly<{ id: string kind: 'text' target: 'agent' quote: string anchor?: TextAnnotationAnchor note?: string source: TextAnnotationSource }> export type ImagePointAnnotation = Readonly<{ id: string kind: 'image-point' target: 'agent' note: string source: Readonly<{ kind: 'artifact-version' | 'upload-version' projectId: string sessionId: string versionId: string name: string path: string mimeType: string }> point: Readonly<{ x: number; y: number }> naturalSize: Readonly<{ width: number; height: number }> }> export type PdfNormalizedQuad = Readonly<{ x: number y: number width: number height: number }> export type PdfNormalizedRect = PdfNormalizedQuad export type PdfTextSelector = Readonly<{ kind: 'text' pageNumber: number exact: string prefix?: string suffix?: string position: Readonly<{ start: number; end: number }> quads: readonly PdfNormalizedQuad[] extractorVersion: string }> export type PdfRegionSelector = Readonly<{ kind: 'region' pageNumber: number rect: PdfNormalizedRect pageRotation: number text?: string }> & ( | Readonly<{ image: AcpMessageImage; imageOmissionReason?: never }> | Readonly<{ image?: never; imageOmissionReason: 'session-budget' }> ) export type PdfAnnotation = Readonly<{ id: string kind: 'pdf' target: 'agent' note?: string source: Readonly<{ kind: 'artifact-version' | 'upload-version' | 'literature-attachment-version' projectId: string sessionId?: string versionId: string name: string path: string checksum: string }> selector: PdfTextSelector | PdfRegionSelector }> export type Annotation = TextAnnotation | ImagePointAnnotation | PdfAnnotation export const annotationRequiresImageInput = (annotation: Annotation): boolean => annotation.kind === 'image-point' || (annotation.kind === 'pdf' && annotation.selector.kind === 'region' && !!annotation.selector.image) export type PreparedImagePoint = Readonly<{ annotationId: string number: number attachment: number note: string sourceKind: ImagePointAnnotation['source']['kind'] versionId: string name: string mimeType: string x: number y: number imageWidth: number imageHeight: number }> export type PreparedImagePointAnnotations = Readonly<{ attachments: ArtifactReference[] points: PreparedImagePoint[] }> export type PreparedAnnotationsForAgent = Readonly<{ promptText: string referencedArtifacts?: FileReference[] images?: AcpMessageImage[] }> export type SideChatAnnotationItem = | Readonly<{ type: 'quote' content: string source?: | TextAnnotationSource | Readonly<{ kind: 'pdf' versionId: string name: string checksum: string page: number selector: Omit<PdfTextSelector, 'kind' | 'pageNumber' | 'exact'> }> instruction?: string }> | Readonly<{ type: 'image-point' source: | Readonly<{ kind: 'artifact-version' artifactId: string versionId: string name: string }> | Readonly<{ kind: 'upload-version'; versionId: string; name: string }> x: number y: number instruction: string }> export type ParsedSideChatAnnotationText = Readonly<{ text: string items: readonly SideChatAnnotationItem[] }> export type AnnotationValidationError = | 'too-many' | 'quote-too-long' | 'note-too-long' | 'payload-too-large' | 'visual-model-required' | 'invalid' const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value) const trimmed = (value: unknown): string | undefined => { if (typeof value !== 'string') return undefined const result = value.trim() return result || undefined } const boundedString = (value: unknown, limit: number): string | undefined => typeof value === 'string' && value.length > 0 && value.length <= limit ? value : undefined const normalizedQuad = (value: unknown): PdfNormalizedQuad | undefined => { if (!isRecord(value)) return undefined const { x, y, width, height } = value if ( typeof x !== 'number' || typeof y !== 'number' || typeof width !== 'number' || typeof height !== 'number' || ![x, y, width, height].every(Number.isFinite) || x < 0 || y < 0 || width <= 0 || height <= 0 || x + width > 1.000_001 || y + height > 1.000_001 ) { return undefined } return { x, y, width, height } } const sanitizePdfAnnotation = ( value: Record<string, unknown>, id: string ): PdfAnnotation | undefined => { if (!isRecord(value.source) || !isRecord(value.selector)) return undefined const source = value.source const selector = value.selector const sourceKind = source.kind const sourceSessionId = trimmed(source.sessionId) const sourceSessionIdRequired = sourceKind === 'artifact-version' || sourceKind === 'upload-version' const pageNumber = selector.pageNumber if ( (!sourceSessionIdRequired && sourceKind !== 'literature-attachment-version') || !trimmed(source.projectId) || (sourceSessionIdRequired && !sourceSessionId) || (!sourceSessionIdRequired && sourceSessionId !== undefined) || !trimmed(source.versionId) || !trimmed(source.name) || !trimmed(source.path) || typeof source.checksum !== 'string' || !/^[a-f0-9]{64}$/u.test(source.checksum) || typeof pageNumber !== 'number' || !Number.isInteger(pageNumber) || pageNumber < 1 ) { return undefined } const sanitizedSource: PdfAnnotation['source'] = { kind: sourceKind, projectId: trimmed(source.projectId)!, ...(sourceSessionId ? { sessionId: sourceSessionId } : {}), versionId: trimmed(source.versionId)!, name: trimmed(source.name)!, path: trimmed(source.path)!, checksum: source.checksum } const note = trimmed(value.note) if (selector.kind === 'region') { const rect = normalizedQuad(selector.rect) const text = selector.text === undefined ? undefined : boundedString(selector.text, ANNOTATION_LIMITS.quote) const image = sanitizeAcpMessageImage(selector.image) if ( !rect || typeof selector.pageRotation !== 'number' || ![0, 90, 180, 270].includes(selector.pageRotation) || (selector.text !== undefined && !text) || (!image && !(selector.image === undefined && selector.imageOmissionReason === 'session-budget')) || (image !== undefined && selector.imageOmissionReason !== undefined) ) { return undefined } return { id, kind: 'pdf', target: 'agent', ...(note ? { note } : {}), source: sanitizedSource, selector: { kind: 'region', pageNumber, rect, pageRotation: selector.pageRotation, ...(text ? { text } : {}), ...(image ? { image } : { imageOmissionReason: 'session-budget' as const }) } } } const exact = boundedString(selector.exact, ANNOTATION_LIMITS.quote) const prefix = selector.prefix === undefined ? undefined : boundedString(selector.prefix, 256) const suffix = selector.suffix === undefined ? undefined : boundedString(selector.suffix, 256) const position = selector.position const quads = Array.isArray(selector.quads) ? selector.quads.map(normalizedQuad) : undefined if ( selector.kind !== 'text' || !exact || (selector.prefix !== undefined && !prefix) || (selector.suffix !== undefined && !suffix) || !isRecord(position) || typeof position.start !== 'number' || typeof position.end !== 'number' || !Number.isInteger(position.start) || !Number.isInteger(position.end) || position.start < 0 || position.end !== position.start + exact.length || !quads || quads.length === 0 || quads.some((quad) => !quad) || !trimmed(selector.extractorVersion) ) { return undefined } return { id, kind: 'pdf', target: 'agent', ...(note ? { note } : {}), source: sanitizedSource, selector: { kind: 'text', pageNumber, exact, ...(prefix ? { prefix } : {}), ...(suffix ? { suffix } : {}), position: { start: position.start, end: position.end }, quads: quads as PdfNormalizedQuad[], extractorVersion: trimmed(selector.extractorVersion)! } } } const sanitizeTextSource = (value: unknown): TextAnnotationSource | undefined => { if (!isRecord(value)) return undefined const kind = value.kind if (kind === 'agent-message') { const sessionId = trimmed(value.sessionId) const messageId = trimmed(value.messageId) return sessionId && messageId ? { kind, sessionId, messageId } : undefined } if (kind === 'project-file') { const projectId = trimmed(value.projectId) const path = trimmed(value.path) if (!projectId || !path) return undefined const name = trimmed(value.name) const fileSource = value.fileSource === 'artifact' || value.fileSource === 'upload' ? value.fileSource : undefined const sourceFileId = trimmed(value.sourceFileId) if (Boolean(fileSource) !== Boolean(sourceFileId)) return undefined const versionId = trimmed(value.versionId) const source: ProjectFileAnnotationSource = { kind, projectId, path, ...(name ? { name } : {}), ...(fileSource && sourceFileId ? { fileSource, sourceFileId } : {}), ...(versionId ? { versionId } : {}), ...(trimmed(value.sessionId) ? { sessionId: trimmed(value.sessionId) } : {}) } return resolveManagedProjectFileAnnotationIdentity(source) === null ? undefined : source } if (kind === 'session-item') { const sessionId = trimmed(value.sessionId) const itemId = trimmed(value.itemId) const itemType = trimmed(value.itemType) if ( !sessionId || !itemId || !itemType || !SESSION_TEXT_ANNOTATION_ITEM_TYPES.has(itemType as SessionTextAnnotationItemType) ) { return undefined } return { kind, sessionId, itemId, itemType: itemType as SessionTextAnnotationItemType, ...(trimmed(value.sectionId) ? { sectionId: trimmed(value.sectionId) } : {}) } } return undefined } const sanitizeTextAnchor = (value: unknown, quote: string): TextAnnotationAnchor | undefined => { if (!isRecord(value) || !isRecord(value.position)) return undefined const { start, end } = value.position if ( typeof start !== 'number' || !Number.isSafeInteger(start) || start < 0 || typeof end !== 'number' || !Number.isSafeInteger(end) || end !== start + quote.length || (value.prefix !== undefined && !boundedString(value.prefix, 256)) || (value.suffix !== undefined && !boundedString(value.suffix, 256)) ) return undefined return { position: { start, end }, ...(typeof value.prefix === 'string' ? { prefix: value.prefix } : {}), ...(typeof value.suffix === 'string' ? { suffix: value.suffix } : {})