import { inferDocumentFileInfo, type KnowledgeUpsertDocumentParams, type KnowledgeUpsertDocumentResponse, } from '@/tools/knowledge/types' import { enrichKBTagsSchema } from '@/tools/schema-enrichers' import { formatDocumentTagsForAPI, parseDocumentTags } from '@/tools/shared/tags' import type { ToolConfig } from '@/tools/types' export const knowledgeUpsertDocumentTool: ToolConfig< KnowledgeUpsertDocumentParams, KnowledgeUpsertDocumentResponse > = { id: 'knowledge_upsert_document', name: 'Knowledge Upsert Document', description: 'Create or update a document in a knowledge base. If a document with the given ID or filename already exists, it will be replaced with the new content.', version: '1.0.0', params: { knowledgeBaseId: { type: 'string', required: true, visibility: 'user-or-llm', description: 'ID of the knowledge base containing the document', }, documentId: { type: 'string', required: false, visibility: 'user-or-llm', description: 'Optional ID of an existing document to update. If not provided, lookup is done by filename.', }, name: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Name of the document', }, content: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Content of the document', }, documentTags: { type: 'json', required: false, visibility: 'user-or-llm', description: 'Document tags', }, }, schemaEnrichment: { documentTags: { dependsOn: 'knowledgeBaseId', enrichSchema: enrichKBTagsSchema, }, }, request: { url: (params) => `/api/knowledge/${params.knowledgeBaseId}/documents/upsert`, method: 'POST', headers: () => ({ 'Content-Type': 'application/json', }), body: (params) => { const workflowId = params._context?.workflowId const textContent = params.content?.trim() const documentName = params.name?.trim() if (!documentName || documentName.length === 0) { throw new Error('Document name is required') } if (documentName.length > 255) { throw new Error('Document name must be 255 characters or less') } if (!textContent || textContent.length < 1) { throw new Error('Document content cannot be empty') } const utf8Bytes = new TextEncoder().encode(textContent) const contentBytes = utf8Bytes.length if (contentBytes > 1_000_000) { throw new Error('Document content exceeds maximum size of 1MB') } let base64Content: string if (typeof Buffer !== 'undefined') { base64Content = Buffer.from(textContent, 'utf8').toString('base64') } else { let binary = '' for (let i = 0; i < utf8Bytes.length; i++) { binary += String.fromCharCode(utf8Bytes[i]) } base64Content = btoa(binary) } const { filename, mimeType } = inferDocumentFileInfo(documentName) const dataUri = `data:${mimeType};base64,${base64Content}` const parsedTags = parseDocumentTags(params.documentTags) const tagData = formatDocumentTagsForAPI(parsedTags) const requestBody: Record = { filename, fileUrl: dataUri, fileSize: contentBytes, mimeType, ...tagData, processingOptions: { recipe: 'default', lang: 'en', }, ...(workflowId && { workflowId }), } if (params.documentId && String(params.documentId).trim().length > 0) { requestBody.documentId = String(params.documentId).trim() } return requestBody }, }, transformResponse: async (response): Promise => { const result = await response.json() const data = result.data ?? result const documentsCreated = data.documentsCreated ?? [] const firstDocument = documentsCreated[0] const isUpdate = data.isUpdate ?? false const previousDocumentId = data.previousDocumentId ?? null const documentId = firstDocument?.documentId ?? firstDocument?.id ?? '' return { success: true, output: { message: isUpdate ? 'Successfully updated document in knowledge base' : 'Successfully created document in knowledge base', documentId, data: { documentId, documentName: firstDocument?.filename ?? 'Unknown', type: 'document', enabled: true, isUpdate, previousDocumentId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, }, } }, outputs: { data: { type: 'object', description: 'Information about the upserted document', properties: { documentId: { type: 'string', description: 'Document ID' }, documentName: { type: 'string', description: 'Document name' }, type: { type: 'string', description: 'Document type' }, enabled: { type: 'boolean', description: 'Whether the document is enabled' }, isUpdate: { type: 'boolean', description: 'Whether an existing document was replaced', }, previousDocumentId: { type: 'string', description: 'ID of the document that was replaced, if any', optional: true, }, createdAt: { type: 'string', description: 'Creation timestamp' }, updatedAt: { type: 'string', description: 'Last update timestamp' }, }, }, message: { type: 'string', description: 'Success or error message describing the operation result', }, documentId: { type: 'string', description: 'ID of the upserted document', }, }, }