Skip to content

Commit a617100

Browse files
Copilotphrocker
andauthored
Fix endpoints_like parsing failure on VerbRegistry-wrapped arguments (#101)
* Initial plan * Fix endpoints_like parsing to handle nested array format - Handle VerbRegistry wrapping which creates {"endpoints_like": {"endpoints_like": [...]}} - Extract array values when found in nested object structure - Add comprehensive tests for nested, simple array, and string formats - All 53 tests pass successfully Co-authored-by: phrocker <[email protected]> * fixup --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: phrocker <[email protected]> Co-authored-by: Marc Parisi <[email protected]>
1 parent cf9d9ab commit a617100

File tree

7 files changed

+192
-5
lines changed

7 files changed

+192
-5
lines changed

core/src/main/java/io/sentrius/sso/core/dto/agents/AgentExecutionContextDTO.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public void addToMemory(JsonNode node) {
5151
}
5252

5353
public void addToMemory(String key, JsonNode value) {
54+
log.info("Adding to memory key: {}", key);
5455
putStructuredToMemory(key, value);
5556
}
5657

core/src/main/java/io/sentrius/sso/core/model/verbs/VerbResponse.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212
public class VerbResponse {
1313
private List<Message> messages;
1414
private Class<?> returnType;
15+
private String returnName;
1516
}

enterprise-agent/src/main/java/io/sentrius/agent/analysis/agents/agents/ChatAgent.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,27 @@ public void onApplicationEvent(final ApplicationReadyEvent event) {
229229
var planResponse =
230230
responses.isEmpty() ? "" :
231231
responses.get(responses.size() - 1).toString();
232+
if (planResponse.isEmpty()) {
233+
var respName =
234+
agentExecutionContext.getAgentShortTermMemory().get( executionResponse.getReturnName() );
235+
if (respName != null) {
236+
planResponse = respName.toString();
237+
}
238+
}
239+
log.info("Plan response: {} from {}", planResponse, responses);
232240
nextResponse = chatVerbs.interpret_plan_response(
233241
agentExecution,
234242
agentExecutionContext,
235243
verbRegistry.getVerbs().get(response.getNextOperation()),
236244
planResponse
237245
);
246+
agentExecutionContext.addToPersistentMemory(
247+
"agent_response_" + System.currentTimeMillis(),
248+
nextResponse.getResponseForUser(),
249+
"PRIVATE",
250+
new String[]{"CONVERSATION"}
251+
);
252+
238253

239254
var memory = agentExecutionContext.flushPersistentMemory();
240255
if (memory != null && !memory.isEmpty()) {

enterprise-agent/src/main/java/io/sentrius/agent/analysis/agents/agents/VerbRegistry.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ public class VerbRegistry {
5555

5656
private static final String [] AGENT_MARKINGS = new String[] {"SENTRIUS_INTERNAL"};
5757

58-
private final AgentEndpointDiscoveryService agentEndpointDiscoveryService;
5958

6059
private List<EndpointDescriptor> endpoints = new ArrayList<>();
6160

@@ -166,6 +165,7 @@ public VerbResponse execute(AgentExecution agentExecution,
166165

167166
log.info("Executing verb: {}", verb);
168167
var returnType = agentVerb.getReturnType();
168+
var returnName = agentVerb.getReturnName();
169169
if (null != priorResponse ) {
170170
log.info("Interpreting prior response for verb: {}", verb);
171171
log.info("Interpreting prior response: {}", priorResponse.getReturnType());
@@ -197,8 +197,10 @@ public VerbResponse execute(AgentExecution agentExecution,
197197
}
198198

199199

200+
200201
return VerbResponse.builder()
201202
.returnType(returnType)
203+
.returnName(returnName)
202204
.build();
203205

204206
} catch (InvocationTargetException e) {

enterprise-agent/src/main/java/io/sentrius/agent/analysis/agents/verbs/AgentVerbs.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -986,10 +986,32 @@ public ObjectNode getEndpointsLike(AgentExecution execution,
986986
}
987987
}
988988
} else if (parsedQuery.isObject()) {
989-
// Handle nested object format: {"arg1": {"field": "text"}} or {"arg1": "text"}
990-
String queryText = extractQueryString(parsedQuery);
991-
if (queryText != null && !queryText.isEmpty()) {
992-
queryStrings.add(queryText);
989+
// Handle nested object format: {"endpoints_like": ["text1", "text2"]} or {"arg1": "text"}
990+
// First check if this object contains an array (common when VerbRegistry wraps arguments)
991+
Iterator<Map.Entry<String, JsonNode>> fields = parsedQuery.fields();
992+
boolean foundArray = false;
993+
while (fields.hasNext()) {
994+
Map.Entry<String, JsonNode> entry = fields.next();
995+
JsonNode value = entry.getValue();
996+
if (value.isArray()) {
997+
// Extract strings from the array
998+
for (JsonNode arrayElement : value) {
999+
String queryText = extractQueryString(arrayElement);
1000+
if (queryText != null && !queryText.isEmpty()) {
1001+
queryStrings.add(queryText);
1002+
}
1003+
}
1004+
foundArray = true;
1005+
break;
1006+
}
1007+
}
1008+
1009+
// If no array found, try to extract a single string
1010+
if (!foundArray) {
1011+
String queryText = extractQueryString(parsedQuery);
1012+
if (queryText != null && !queryText.isEmpty()) {
1013+
queryStrings.add(queryText);
1014+
}
9931015
}
9941016
} else if (parsedQuery.isTextual()) {
9951017
// Simple string format

enterprise-agent/src/main/java/io/sentrius/agent/analysis/api/websocket/ChatWSHandler.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,14 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message)
373373
var planResponse =
374374
responses.isEmpty() ? "" :
375375
responses.get( responses.size() -1 ).asText();
376+
if (planResponse.isEmpty()) {
377+
var respName =
378+
websocketCommunication.getAgentExecutionContextDTO().getAgentShortTermMemory().get( executionResponse.getReturnName() );
379+
if (respName != null) {
380+
planResponse = respName.toString();
381+
}
382+
}
383+
log.info("Plan response: {} from {}", planResponse, responses);
376384
nextResponse = chatVerbs.interpret_plan_response(
377385
chatAgent.getAgentExecution(),
378386
websocketCommunication.getAgentExecutionContextDTO(),

enterprise-agent/src/test/java/io/sentrius/sentrius/analysis/agents/verbs/AgentVerbsTest.java

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import static org.mockito.ArgumentMatchers.any;
1212
import static org.mockito.ArgumentMatchers.eq;
1313
import static org.mockito.Mockito.when;
14+
import com.fasterxml.jackson.databind.node.ArrayNode;
1415
import com.fasterxml.jackson.databind.node.ObjectNode;
1516
import io.sentrius.agent.analysis.agents.agents.VerbRegistry;
1617
import io.sentrius.agent.analysis.agents.verbs.AgentVerbs;
@@ -19,6 +20,7 @@
1920
import io.sentrius.sso.core.dto.agents.AgentContextDTO;
2021
import io.sentrius.sso.core.dto.agents.AgentExecution;
2122
import io.sentrius.sso.core.dto.agents.AgentExecutionContextDTO;
23+
import io.sentrius.sso.core.dto.capabilities.EndpointDescriptor;
2224
import io.sentrius.sso.core.exceptions.ZtatException;
2325
import io.sentrius.sso.core.services.agents.AgentClientService;
2426
import io.sentrius.sso.core.services.agents.AgentExecutionService;
@@ -480,4 +482,140 @@ void searchAgentMemorySemanticWithoutAgentId() throws Exception, ZtatException {
480482
assertEquals(1, result.get("count").asInt());
481483
assertEquals("global_config", result.get("memories").get(0).get("memoryKey").asText());
482484
}
485+
486+
@Test
487+
void getEndpointsLikeHandlesNestedArrayFormat() throws Exception, ZtatException {
488+
// Given - This is the problematic format from the error log
489+
String executionId = UUID.randomUUID().toString();
490+
AgentExecution execution = AgentExecution.builder()
491+
.executionId(executionId)
492+
.build();
493+
494+
AgentExecutionContextDTO requestContext = AgentExecutionContextDTO.builder().build();
495+
ObjectNode queryArgs = JsonUtil.MAPPER.createObjectNode();
496+
497+
// Simulate VerbRegistry wrapping: {"endpoints_like": {"endpoints_like": ["github issues", "mcp server"]}}
498+
ObjectNode nestedObject = JsonUtil.MAPPER.createObjectNode();
499+
ArrayNode searchArray = JsonUtil.MAPPER.createArrayNode();
500+
searchArray.add("github issues");
501+
searchArray.add("mcp server");
502+
nestedObject.set("endpoints_like", searchArray);
503+
queryArgs.set("endpoints_like", nestedObject);
504+
505+
requestContext.setExecutionArgs(queryArgs);
506+
507+
// Mock endpoint searcher to return results
508+
List<EndpointDescriptor> mockEndpoints = new ArrayList<>();
509+
mockEndpoints.add(EndpointDescriptor.builder()
510+
.name("list_issues")
511+
.description("List GitHub issues")
512+
.httpMethod("GET")
513+
.path("/api/github/issues")
514+
.build());
515+
516+
when(endpointSearcher.getEndpointsLike(eq(execution), eq("github issues")))
517+
.thenReturn(mockEndpoints);
518+
when(endpointSearcher.getEndpointsLike(eq(execution), eq("mcp server")))
519+
.thenReturn(new ArrayList<>());
520+
521+
// When
522+
ObjectNode result = agentVerbs.getEndpointsLike(execution, requestContext);
523+
524+
// Then
525+
assertNotNull(result);
526+
assertTrue(result.has("endpoints"));
527+
assertEquals(1, result.get("endpoints").size());
528+
assertEquals("list_issues", result.get("endpoints").get(0).get("name").asText());
529+
assertEquals("github issues", result.get("endpoints").get(0).get("searchQuery").asText());
530+
}
531+
532+
@Test
533+
void getEndpointsLikeHandlesSimpleArrayFormat() throws Exception, ZtatException {
534+
// Given
535+
String executionId = UUID.randomUUID().toString();
536+
AgentExecution execution = AgentExecution.builder()
537+
.executionId(executionId)
538+
.build();
539+
540+
AgentExecutionContextDTO requestContext = AgentExecutionContextDTO.builder().build();
541+
ObjectNode queryArgs = JsonUtil.MAPPER.createObjectNode();
542+
543+
// Simple array format: {"endpoints_like": ["list users", "delete users"]}
544+
ArrayNode searchArray = JsonUtil.MAPPER.createArrayNode();
545+
searchArray.add("list users");
546+
searchArray.add("delete users");
547+
queryArgs.set("endpoints_like", searchArray);
548+
549+
requestContext.setExecutionArgs(queryArgs);
550+
551+
// Mock endpoint searcher
552+
List<EndpointDescriptor> listEndpoints = new ArrayList<>();
553+
listEndpoints.add(EndpointDescriptor.builder()
554+
.name("list_users")
555+
.description("List all users")
556+
.httpMethod("GET")
557+
.path("/api/users")
558+
.build());
559+
560+
List<EndpointDescriptor> deleteEndpoints = new ArrayList<>();
561+
deleteEndpoints.add(EndpointDescriptor.builder()
562+
.name("delete_user")
563+
.description("Delete a user")
564+
.httpMethod("DELETE")
565+
.path("/api/users/{id}")
566+
.build());
567+
568+
when(endpointSearcher.getEndpointsLike(eq(execution), eq("list users")))
569+
.thenReturn(listEndpoints);
570+
when(endpointSearcher.getEndpointsLike(eq(execution), eq("delete users")))
571+
.thenReturn(deleteEndpoints);
572+
573+
// When
574+
ObjectNode result = agentVerbs.getEndpointsLike(execution, requestContext);
575+
576+
// Then
577+
assertNotNull(result);
578+
assertTrue(result.has("endpoints"));
579+
assertEquals(2, result.get("endpoints").size());
580+
assertEquals("list_users", result.get("endpoints").get(0).get("name").asText());
581+
assertEquals("delete_user", result.get("endpoints").get(1).get("name").asText());
582+
}
583+
584+
@Test
585+
void getEndpointsLikeHandlesStringFormat() throws Exception, ZtatException {
586+
// Given
587+
String executionId = UUID.randomUUID().toString();
588+
AgentExecution execution = AgentExecution.builder()
589+
.executionId(executionId)
590+
.build();
591+
592+
AgentExecutionContextDTO requestContext = AgentExecutionContextDTO.builder().build();
593+
ObjectNode queryArgs = JsonUtil.MAPPER.createObjectNode();
594+
595+
// String format: {"endpoints_like": "authentication"}
596+
queryArgs.put("endpoints_like", "authentication");
597+
598+
requestContext.setExecutionArgs(queryArgs);
599+
600+
// Mock endpoint searcher
601+
List<EndpointDescriptor> authEndpoints = new ArrayList<>();
602+
authEndpoints.add(EndpointDescriptor.builder()
603+
.name("login")
604+
.description("User login endpoint")
605+
.httpMethod("POST")
606+
.path("/api/auth/login")
607+
.build());
608+
609+
when(endpointSearcher.getEndpointsLike(eq(execution), eq("authentication")))
610+
.thenReturn(authEndpoints);
611+
612+
// When
613+
ObjectNode result = agentVerbs.getEndpointsLike(execution, requestContext);
614+
615+
// Then
616+
assertNotNull(result);
617+
assertTrue(result.has("endpoints"));
618+
assertEquals(1, result.get("endpoints").size());
619+
assertEquals("login", result.get("endpoints").get(0).get("name").asText());
620+
}
483621
}

0 commit comments

Comments
 (0)