Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fresh-otters-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@inkeep/agents-run-api": patch
"@inkeep/agents-manage-ui": patch
"@inkeep/agents-core": patch
---

fix blue dot appears on inverted delegation on top left corner, refactor retrieving relationshipId in agents-core
31 changes: 20 additions & 11 deletions agents-manage-ui/src/components/agent/edges/default-edge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,41 @@ import { cn } from '@/lib/utils';
import type { AnimatedEdge } from '../configuration/edge-types';

export const AnimatedCircle: FC<{ edgePath: string } & AnimatedEdge> = ({ edgePath, status }) => {
const ref = useRef<SVGAnimateElement>(null);
const motionRef = useRef<SVGAnimateMotionElement>(null);
const opacityRef = useRef<SVGAnimateElement>(null);

// Without this useEffect, the animation won't start when this component is rendered dynamically.
// biome-ignore lint/correctness/useExhaustiveDependencies: We need restart animation when invert is changed
useEffect(() => {
ref.current?.beginElement();
motionRef.current?.beginElement();
opacityRef.current?.beginElement();
}, [status]);

if (!status) {
return;
}

const isInvertedDelegating = status === 'inverted-delegating';
const isInverted = status === 'inverted-delegating';
const dur = '2s';

return (
<circle fill="var(--primary)" r="6">
<circle fill="var(--primary)" r="6" opacity={isInverted ? 0 : 100}>
<animateMotion
ref={ref}
dur="2s"
ref={motionRef}
dur={dur}
path={edgePath}
fill={isInvertedDelegating ? 'remove' : 'freeze'}
{...(isInvertedDelegating && {
keyPoints: '1;0',
keyTimes: '0;1',
})}
fill={isInverted ? 'remove' : 'freeze'}
{...(isInverted && { keyPoints: '1;0', keyTimes: '0;1' })}
/>
{isInverted && (
<animate
ref={opacityRef}
dur={dur}
attributeName="opacity"
values="1;1;0"
keyTimes="0;0.95;1"
/>
)}
</circle>
);
};
Expand Down
56 changes: 41 additions & 15 deletions agents-run-api/src/agents/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export type AgentConfig = {
tenantId: string;
projectId: string;
agentId: string;
relationId?: string;
baseUrl: string;
apiKey?: string;
apiKeyId?: string;
Expand Down Expand Up @@ -291,6 +292,40 @@ export class Agent {
return sanitizedTools;
}

#createRelationToolName(prefix: string, targetId: string): string {
return `${prefix}_to_${targetId.toLowerCase().replace(/\s+/g, '_')}`;
}

#getRelationshipIdForTool(toolName: string, toolType?: ToolType): string | undefined {
if (toolType === 'mcp') {
const matchingTool = this.config.tools?.find((tool) => {
if (tool.config?.type !== 'mcp') {
return false;
}

if (tool.availableTools?.some((available) => available.name === toolName)) {
return true;
}

if (tool.config.mcp.activeTools?.includes(toolName)) {
return true;
}

return tool.name === toolName;
});

return matchingTool?.relationshipId;
}

if (toolType === 'delegation') {
const relation = this.config.delegateRelations.find(
(relation) => this.#createRelationToolName('delegate', relation.config.id) === toolName
);

return relation?.config.relationId;
}
}

/**
* Get the primary model settings for text generation and thinking
* Requires model to be configured at project level
Expand Down Expand Up @@ -373,12 +408,12 @@ export class Agent {
toolDefinition: any,
streamRequestId?: string,
toolType?: ToolType,
relationshipId?: string,
options?: { needsApproval?: boolean }
) {
if (!toolDefinition || typeof toolDefinition !== 'object' || !('execute' in toolDefinition)) {
return toolDefinition;
}
const relationshipId = this.#getRelationshipIdForTool(toolName, toolType);

const originalExecute = toolDefinition.execute;
return {
Expand Down Expand Up @@ -520,11 +555,9 @@ export class Agent {
sessionId?: string
) {
const { transferRelations = [], delegateRelations = [] } = this.config;
const createToolName = (prefix: string, subAgentId: string) =>
`${prefix}_to_${subAgentId.toLowerCase().replace(/\s+/g, '_')}`;
return Object.fromEntries([
...transferRelations.map((agentConfig) => {
const toolName = createToolName('transfer', agentConfig.id);
const toolName = this.#createRelationToolName('transfer', agentConfig.id);
return [
toolName,
this.wrapToolWithStreaming(
Expand All @@ -541,7 +574,7 @@ export class Agent {
];
}),
...delegateRelations.map((relation) => {
const toolName = createToolName('delegate', relation.config.id);
const toolName = this.#createRelationToolName('delegate', relation.config.id);

return [
toolName,
Expand Down Expand Up @@ -579,13 +612,8 @@ export class Agent {
}) || [];
const tools = (await Promise.all(mcpTools.map((tool) => this.getMcpTool(tool)) || [])) || [];
if (!sessionId) {
// TODO check if we need reduce
// const combinedTools = tools.reduce((acc, toolResult) => {
// return Object.assign(acc, toolResult.tools) as ToolSet;
// }, {} as ToolSet);
const wrappedTools: ToolSet = {};
for (const [index, toolSet] of tools.entries()) {
const relationshipId = mcpTools[index]?.relationshipId;
for (const toolSet of tools) {
for (const [toolName, toolDef] of Object.entries(toolSet.tools)) {
// Find toolPolicies for this tool
const needsApproval = toolSet.toolPolicies?.[toolName]?.needsApproval || false;
Expand All @@ -600,7 +628,6 @@ export class Agent {
enhancedTool,
streamRequestId,
'mcp',
relationshipId,
{ needsApproval }
);
}
Expand All @@ -609,8 +636,7 @@ export class Agent {
}

const wrappedTools: ToolSet = {};
for (const [index, toolResult] of tools.entries()) {
const relationshipId = mcpTools[index]?.relationshipId;
for (const toolResult of tools) {
for (const [toolName, originalTool] of Object.entries(toolResult.tools)) {
if (!isValidTool(originalTool)) {
logger.error({ toolName }, 'Invalid MCP tool structure - missing required properties');
Expand Down Expand Up @@ -743,6 +769,7 @@ export class Agent {
});

if (streamRequestId) {
const relationshipId = this.#getRelationshipIdForTool(toolName, 'mcp');
agentSessionManager.recordEvent(streamRequestId, 'error', this.config.id, {
message: `MCP tool "${toolName}" failed: ${errorMessage}`,
code: 'mcp_tool_error',
Expand Down Expand Up @@ -798,7 +825,6 @@ export class Agent {
sessionWrappedTool,
streamRequestId,
'mcp',
relationshipId,
{ needsApproval }
);
}
Expand Down
1 change: 1 addition & 0 deletions agents-run-api/src/agents/generateTaskHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ export const createTaskHandler = (
type: 'internal' as const,
config: {
id: relation.id,
relationId: relation.relationId,
tenantId: config.tenantId,
projectId: config.projectId,
agentId: config.agentId,
Expand Down
1 change: 1 addition & 0 deletions packages/agents-core/src/data-access/subAgentRelations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export const getRelatedAgentsForAgent =
name: subAgents.name,
description: subAgents.description,
relationType: subAgentRelations.relationType,
relationId: subAgentRelations.id,
})
.from(subAgentRelations)
.innerJoin(
Expand Down
Loading