1+ import { db } from '@sim/db'
2+ import { knowledgeConnector } from '@sim/db/schema'
13import { createLogger } from '@sim/logger'
4+ import { and , eq , isNull } from 'drizzle-orm'
5+ import { generateInternalToken } from '@/lib/auth/internal'
26import type { BaseServerTool } from '@/lib/copilot/tools/server/base-tool'
37import type { KnowledgeBaseArgs , KnowledgeBaseResult } from '@/lib/copilot/tools/shared/schemas'
8+ import { getInternalApiBaseUrl } from '@/lib/core/utils/urls'
49import { createSingleDocument , processDocumentAsync } from '@/lib/knowledge/documents/service'
510import { generateSearchEmbedding } from '@/lib/knowledge/embeddings'
611import {
@@ -543,10 +548,179 @@ export const knowledgeBaseServerTool: BaseServerTool<KnowledgeBaseArgs, Knowledg
543548 }
544549 }
545550
551+ case 'add_connector' : {
552+ if ( ! args . knowledgeBaseId ) {
553+ return { success : false , message : 'Knowledge base ID is required for add_connector' }
554+ }
555+ if ( ! args . connectorType ) {
556+ return { success : false , message : 'connectorType is required for add_connector' }
557+ }
558+ if ( ! args . credentialId ) {
559+ return {
560+ success : false ,
561+ message :
562+ 'credentialId is required for add_connector. Read environment/credentials.json to find credential IDs.' ,
563+ }
564+ }
565+
566+ const createBody : Record < string , unknown > = {
567+ connectorType : args . connectorType ,
568+ credentialId : args . credentialId ,
569+ sourceConfig : args . sourceConfig ?? { } ,
570+ syncIntervalMinutes : args . syncIntervalMinutes ?? 1440 ,
571+ }
572+
573+ if ( args . disabledTagIds ?. length ) {
574+ ; ( createBody . sourceConfig as Record < string , unknown > ) . disabledTagIds =
575+ args . disabledTagIds
576+ }
577+
578+ const createRes = await connectorApiCall (
579+ context . userId ,
580+ `/api/knowledge/${ args . knowledgeBaseId } /connectors` ,
581+ 'POST' ,
582+ createBody
583+ )
584+
585+ if ( ! createRes . success ) {
586+ return { success : false , message : createRes . error }
587+ }
588+
589+ const connector = createRes . data
590+ logger . info ( 'Connector created via copilot' , {
591+ connectorId : connector . id ,
592+ connectorType : args . connectorType ,
593+ knowledgeBaseId : args . knowledgeBaseId ,
594+ userId : context . userId ,
595+ } )
596+
597+ return {
598+ success : true ,
599+ message : `Connector "${ args . connectorType } " added to knowledge base. Initial sync started.` ,
600+ data : {
601+ id : connector . id ,
602+ connectorType : connector . connectorType ?? connector . connector_type ,
603+ status : connector . status ,
604+ knowledgeBaseId : args . knowledgeBaseId ,
605+ } ,
606+ }
607+ }
608+
609+ case 'update_connector' : {
610+ if ( ! args . connectorId ) {
611+ return { success : false , message : 'connectorId is required for update_connector' }
612+ }
613+
614+ const kbId = await resolveKnowledgeBaseId ( args . connectorId )
615+ if ( ! kbId ) {
616+ return { success : false , message : `Connector "${ args . connectorId } " not found` }
617+ }
618+
619+ const updateBody : Record < string , unknown > = { }
620+ if ( args . sourceConfig !== undefined ) updateBody . sourceConfig = args . sourceConfig
621+ if ( args . syncIntervalMinutes !== undefined )
622+ updateBody . syncIntervalMinutes = args . syncIntervalMinutes
623+ if ( args . connectorStatus !== undefined ) updateBody . status = args . connectorStatus
624+
625+ if ( Object . keys ( updateBody ) . length === 0 ) {
626+ return {
627+ success : false ,
628+ message :
629+ 'At least one of sourceConfig, syncIntervalMinutes, or connectorStatus is required' ,
630+ }
631+ }
632+
633+ const updateRes = await connectorApiCall (
634+ context . userId ,
635+ `/api/knowledge/${ kbId } /connectors/${ args . connectorId } ` ,
636+ 'PATCH' ,
637+ updateBody
638+ )
639+
640+ if ( ! updateRes . success ) {
641+ return { success : false , message : updateRes . error }
642+ }
643+
644+ logger . info ( 'Connector updated via copilot' , {
645+ connectorId : args . connectorId ,
646+ userId : context . userId ,
647+ } )
648+
649+ return {
650+ success : true ,
651+ message : 'Connector updated successfully' ,
652+ data : { id : args . connectorId , ...updateBody } ,
653+ }
654+ }
655+
656+ case 'delete_connector' : {
657+ if ( ! args . connectorId ) {
658+ return { success : false , message : 'connectorId is required for delete_connector' }
659+ }
660+
661+ const deleteKbId = await resolveKnowledgeBaseId ( args . connectorId )
662+ if ( ! deleteKbId ) {
663+ return { success : false , message : `Connector "${ args . connectorId } " not found` }
664+ }
665+
666+ const deleteRes = await connectorApiCall (
667+ context . userId ,
668+ `/api/knowledge/${ deleteKbId } /connectors/${ args . connectorId } ` ,
669+ 'DELETE'
670+ )
671+
672+ if ( ! deleteRes . success ) {
673+ return { success : false , message : deleteRes . error }
674+ }
675+
676+ logger . info ( 'Connector deleted via copilot' , {
677+ connectorId : args . connectorId ,
678+ userId : context . userId ,
679+ } )
680+
681+ return {
682+ success : true ,
683+ message : 'Connector deleted successfully. Associated documents have been removed.' ,
684+ data : { id : args . connectorId } ,
685+ }
686+ }
687+
688+ case 'sync_connector' : {
689+ if ( ! args . connectorId ) {
690+ return { success : false , message : 'connectorId is required for sync_connector' }
691+ }
692+
693+ const syncKbId = await resolveKnowledgeBaseId ( args . connectorId )
694+ if ( ! syncKbId ) {
695+ return { success : false , message : `Connector "${ args . connectorId } " not found` }
696+ }
697+
698+ const syncRes = await connectorApiCall (
699+ context . userId ,
700+ `/api/knowledge/${ syncKbId } /connectors/${ args . connectorId } /sync` ,
701+ 'POST'
702+ )
703+
704+ if ( ! syncRes . success ) {
705+ return { success : false , message : syncRes . error }
706+ }
707+
708+ logger . info ( 'Connector sync triggered via copilot' , {
709+ connectorId : args . connectorId ,
710+ userId : context . userId ,
711+ } )
712+
713+ return {
714+ success : true ,
715+ message : 'Sync triggered. Documents will be updated in the background.' ,
716+ data : { id : args . connectorId } ,
717+ }
718+ }
719+
546720 default :
547721 return {
548722 success : false ,
549- message : `Unknown operation: ${ operation } . Supported operations: create, get, query, add_file, update, delete, list_tags, create_tag, update_tag, delete_tag, get_tag_usage` ,
723+ message : `Unknown operation: ${ operation } . Supported operations: create, get, query, add_file, update, delete, list_tags, create_tag, update_tag, delete_tag, get_tag_usage, add_connector, update_connector, delete_connector, sync_connector ` ,
550724 }
551725 }
552726 } catch ( error ) {
@@ -564,3 +738,43 @@ export const knowledgeBaseServerTool: BaseServerTool<KnowledgeBaseArgs, Knowledg
564738 }
565739 } ,
566740}
741+
742+ async function connectorApiCall (
743+ userId : string ,
744+ path : string ,
745+ method : string ,
746+ body ?: Record < string , unknown >
747+ ) : Promise < { success : boolean ; data ?: any ; error ?: string } > {
748+ const token = await generateInternalToken ( userId )
749+ const baseUrl = getInternalApiBaseUrl ( )
750+
751+ const res = await fetch ( `${ baseUrl } ${ path } ` , {
752+ method,
753+ headers : {
754+ 'Content-Type' : 'application/json' ,
755+ Authorization : `Bearer ${ token } ` ,
756+ } ,
757+ ...( body ? { body : JSON . stringify ( body ) } : { } ) ,
758+ } )
759+
760+ const json = await res . json ( ) . catch ( ( ) => ( { } ) )
761+
762+ if ( ! res . ok ) {
763+ return {
764+ success : false ,
765+ error : json . error || `API returned ${ res . status } ` ,
766+ }
767+ }
768+
769+ return { success : true , data : json . data }
770+ }
771+
772+ async function resolveKnowledgeBaseId ( connectorId : string ) : Promise < string | null > {
773+ const rows = await db
774+ . select ( { knowledgeBaseId : knowledgeConnector . knowledgeBaseId } )
775+ . from ( knowledgeConnector )
776+ . where ( and ( eq ( knowledgeConnector . id , connectorId ) , isNull ( knowledgeConnector . deletedAt ) ) )
777+ . limit ( 1 )
778+
779+ return rows [ 0 ] ?. knowledgeBaseId ?? null
780+ }
0 commit comments