@@ -3,6 +3,7 @@ import { document, embedding, knowledgeBase, knowledgeConnector } from '@sim/db/
33import { createLogger } from '@sim/logger'
44import { chunkArray } from '@sim/utils/helpers'
55import { generateId } from '@sim/utils/id'
6+ import { truncateAtCodePoint } from '@sim/utils/string'
67import { and , eq , exists , inArray , isNull , lt , or , sql } from 'drizzle-orm'
78import { getInternalApiBaseUrl } from '@/lib/core/utils/urls'
89import type { DbOrTx } from '@/lib/db/types'
@@ -18,6 +19,7 @@ import type { ConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/conn
1819import { resolveSourceModifiedAt } from '@/lib/knowledge/connectors/source-modified-at'
1920import { SOURCE_CONTENT_ERROR } from '@/lib/knowledge/connectors/sync-limits'
2021import { assertSyncLeaseHeldInTx , type SyncWriteLease } from '@/lib/knowledge/connectors/sync-lock'
22+ import { MAX_DOCUMENT_INDEXED_TEXT_LENGTH } from '@/lib/knowledge/constants'
2123import type { DocumentData } from '@/lib/knowledge/documents/service'
2224import { enqueueKnowledgeStorageCleanup } from '@/lib/knowledge/documents/storage-cleanup'
2325import {
@@ -26,7 +28,6 @@ import {
2628} from '@/lib/knowledge/documents/storage-upload'
2729import { buildStorageKeySegment } from '@/lib/uploads/core/storage-key'
2830import { getFileMetadataByKeys } from '@/lib/uploads/server/metadata'
29- import { MAX_DOCUMENT_INDEXED_TEXT_LENGTH } from '@/lib/knowledge/constants'
3031import { CONNECTOR_REGISTRY } from '@/connectors/registry.server'
3132import type { DocumentTags , ExternalDocument } from '@/connectors/types'
3233
@@ -174,16 +175,20 @@ export async function persistDocumentAcls(
174175
175176const MAX_SAFE_TITLE_LENGTH = 200
176177
178+ /** The suffix a cut value carries, counted inside {@link MAX_DOCUMENT_INDEXED_TEXT_LENGTH}. */
179+ const INDEXED_TEXT_CUT_SUFFIX = '...'
180+
177181/**
178182 * Source titles and mapped tag values are untrusted machine input with no caller to refuse them,
179- * so they are cut to {@link MAX_DOCUMENT_INDEXED_TEXT_LENGTH} by code point, never inside a
180- * surrogate pair.
183+ * so they are cut to {@link MAX_DOCUMENT_INDEXED_TEXT_LENGTH} code units, suffix included, and
184+ * never inside a surrogate pair. The result always passes the document APIs' own bound .
181185 */
182186function boundIndexedText ( value : string ) : string {
183- if ( value . length <= MAX_DOCUMENT_INDEXED_TEXT_LENGTH ) return value
184- const points = Array . from ( value )
185- if ( points . length <= MAX_DOCUMENT_INDEXED_TEXT_LENGTH ) return value
186- return `${ points . slice ( 0 , MAX_DOCUMENT_INDEXED_TEXT_LENGTH ) . join ( '' ) } ...`
187+ return truncateAtCodePoint (
188+ value ,
189+ MAX_DOCUMENT_INDEXED_TEXT_LENGTH - INDEXED_TEXT_CUT_SUFFIX . length ,
190+ INDEXED_TEXT_CUT_SUFFIX
191+ )
187192}
188193
189194function sanitizeStorageTitle ( title : string ) : string {
0 commit comments