1177 lines · 37.5 KB · TypeScript
RawDownload
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.