@@ -16,6 +16,12 @@ import {
1616} from '@/lib/knowledge/application/workspace-search'
1717import { sourceAuthor } from '@/lib/knowledge/search/author'
1818import { createKnowledgeDocumentCitation } from '@/lib/knowledge/search/citation'
19+ import {
20+ annotateSearchDiagnostics ,
21+ measureSearchStage ,
22+ recordSearchStageDuration ,
23+ withSearchDiagnostics ,
24+ } from '@/lib/knowledge/search/diagnostics'
1925import { intersectWorkspaceSearchFilters } from '@/lib/knowledge/search/filters'
2026import { connectorDisplayName } from '@/lib/sim-search/connectors'
2127import { projectResolvedSecretModelContent } from '@/executor/utils/resolved-secret-content-projection'
@@ -37,77 +43,99 @@ const CITATION_INSTRUCTION =
3743export const searchWorkspaceServerTool : BaseServerTool = {
3844 name : 'search_workspace' ,
3945 async execute ( raw , context ?: ServerToolContext ) {
40- try {
41- const scope = requireCopilotKnowledgeScope ( context )
42- const { query, topK, ...requestedFilters } = searchInputSchema . parse ( raw )
43- const registry = context ?. resolvedSecretTraceRegistry
44- if ( ! registry ) throw new Error ( 'Knowledge result provenance is unavailable' )
45- const projected = projectResolvedSecretModelContent ( query , registry )
46- if ( ! projected . safe || typeof projected . value !== 'string' ) {
47- return {
48- success : false ,
49- message : 'Search query contains protected content. Rephrase the query.' ,
50- }
51- }
52- const input = {
53- query : projected . value ,
54- topK,
55- filters : intersectWorkspaceSearchFilters ( requestedFilters , context ?. assistantSearch ) ,
46+ return withSearchDiagnostics (
47+ {
5648 surface : context ?. searchSurface ?? 'copilot' ,
57- resultSecretRegistry : registry ,
58- signal : context ?. abortSignal ,
59- } as const
60- const result =
61- scope . kind === 'organization'
62- ? await executeCopilotOrganizationKnowledgeUseCase ( context , searchOrganizationKnowledge , {
63- ...input ,
64- organizationId : scope . organizationId ,
65- } )
66- : await executeCopilotKnowledgeUseCase ( context , searchWorkspaceKnowledge , {
67- ...input ,
68- workspaceId : scope . workspaceId ,
49+ toolCallId : context ?. toolCallId ,
50+ executionId : context ?. executionId ,
51+ } ,
52+ async ( ) => {
53+ try {
54+ const inputStarted = performance . now ( )
55+ const scope = requireCopilotKnowledgeScope ( context )
56+ const { query, topK, ...requestedFilters } = searchInputSchema . parse ( raw )
57+ const registry = context ?. resolvedSecretTraceRegistry
58+ if ( ! registry ) throw new Error ( 'Knowledge result provenance is unavailable' )
59+ const projected = projectResolvedSecretModelContent ( query , registry )
60+ if ( ! projected . safe || typeof projected . value !== 'string' ) {
61+ return {
62+ success : false ,
63+ message : 'Search query contains protected content. Rephrase the query.' ,
64+ }
65+ }
66+ const input = {
67+ query : projected . value ,
68+ topK,
69+ filters : intersectWorkspaceSearchFilters ( requestedFilters , context ?. assistantSearch ) ,
70+ surface : context ?. searchSurface ?? 'copilot' ,
71+ resultSecretRegistry : registry ,
72+ signal : context ?. abortSignal ,
73+ } as const
74+ recordSearchStageDuration ( 'tool_input' , performance . now ( ) - inputStarted )
75+ const result = await measureSearchStage ( 'tool_application' , ( ) =>
76+ scope . kind === 'organization'
77+ ? executeCopilotOrganizationKnowledgeUseCase ( context , searchOrganizationKnowledge , {
78+ ...input ,
79+ organizationId : scope . organizationId ,
80+ } )
81+ : executeCopilotKnowledgeUseCase ( context , searchWorkspaceKnowledge , {
82+ ...input ,
83+ workspaceId : scope . workspaceId ,
84+ } )
85+ )
86+ return await measureSearchStage ( 'tool_presentation' , ( ) => {
87+ const names = new Map ( result . knowledgeBases . map ( ( base ) => [ base . id , base . name ] ) )
88+ const output = {
89+ success : true ,
90+ message : `Found ${ result . results . length } passages. ${ CITATION_INSTRUCTION } ` ,
91+ data : {
92+ query,
93+ results : result . results . map ( ( item ) => ( {
94+ documentId : item . documentId ,
95+ knowledgeBaseId : item . knowledgeBaseId ,
96+ knowledgeBaseName : names . get ( item . knowledgeBaseId ) ?? '' ,
97+ siteName : item . connectorType
98+ ? connectorDisplayName ( item . connectorType )
99+ : names . get ( item . knowledgeBaseId ) ,
100+ documentName : item . documentName ,
101+ sourceUrl : item . sourceUrl ,
102+ connectorType : item . connectorType ,
103+ sourceModifiedAt : item . sourceModifiedAt ?. toISOString ( ) ?? null ,
104+ author : sourceAuthor ( item . metadata ) ,
105+ content : item . content ,
106+ chunkIndex : item . chunkIndex ,
107+ similarity : item . similarity ,
108+ ...createKnowledgeDocumentCitation ( {
109+ scope,
110+ knowledgeBaseId : item . knowledgeBaseId ,
111+ documentId : item . documentId ,
112+ sourceUrl : item . sourceUrl ,
113+ baseUrl : getBaseUrl ( ) ,
114+ } ) ,
115+ } ) ) ,
116+ } ,
117+ }
118+ const passageBytes = output . data . results . map ( ( item ) => Buffer . byteLength ( item . content ) )
119+ annotateSearchDiagnostics ( {
120+ toolResultBytes : Buffer . byteLength ( JSON . stringify ( output ) ) ,
121+ passageBytes : passageBytes . reduce ( ( total , bytes ) => total + bytes , 0 ) ,
122+ maxPassageBytes : Math . max ( 0 , ...passageBytes ) ,
123+ uniqueDocumentCount : new Set ( output . data . results . map ( ( item ) => item . documentId ) ) . size ,
69124 } )
70- const names = new Map ( result . knowledgeBases . map ( ( base ) => [ base . id , base . name ] ) )
71- return {
72- success : true ,
73- message : `Found ${ result . results . length } passages. ${ CITATION_INSTRUCTION } ` ,
74- data : {
75- query,
76- results : result . results . map ( ( item ) => ( {
77- documentId : item . documentId ,
78- knowledgeBaseId : item . knowledgeBaseId ,
79- knowledgeBaseName : names . get ( item . knowledgeBaseId ) ?? '' ,
80- siteName : item . connectorType
81- ? connectorDisplayName ( item . connectorType )
82- : names . get ( item . knowledgeBaseId ) ,
83- documentName : item . documentName ,
84- sourceUrl : item . sourceUrl ,
85- connectorType : item . connectorType ,
86- sourceModifiedAt : item . sourceModifiedAt ?. toISOString ( ) ?? null ,
87- author : sourceAuthor ( item . metadata ) ,
88- content : item . content ,
89- chunkIndex : item . chunkIndex ,
90- similarity : item . similarity ,
91- ...createKnowledgeDocumentCitation ( {
92- scope,
93- knowledgeBaseId : item . knowledgeBaseId ,
94- documentId : item . documentId ,
95- sourceUrl : item . sourceUrl ,
96- baseUrl : getBaseUrl ( ) ,
97- } ) ,
98- } ) ) ,
99- } ,
100- }
101- } catch ( error ) {
102- logger . error ( 'Workspace search failed' , { error } )
103- return {
104- success : false ,
105- message :
106- error instanceof z . ZodError
107- ? 'Invalid search arguments'
108- : messageForCopilotKnowledgeError ( error ) ,
125+ return output
126+ } )
127+ } catch ( error ) {
128+ logger . error ( 'Workspace search failed' , { error } )
129+ return {
130+ success : false ,
131+ message :
132+ error instanceof z . ZodError
133+ ? 'Invalid search arguments'
134+ : messageForCopilotKnowledgeError ( error ) ,
135+ }
136+ }
109137 }
110- }
138+ )
111139 } ,
112140}
113141
0 commit comments