Skip to content
Open
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
4 changes: 3 additions & 1 deletion python/packages/devui/agent_framework_devui/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ def extract_agent_metadata(entity_object: Any) -> dict[str, Any]:
metadata["chat_client_type"] = entity_object.client.__class__.__name__

# Try to get context providers
if (
if context_providers := getattr(entity_object, "context_providers", None):
metadata["context_provider"] = [provider.__class__.__name__ for provider in context_providers] # type: ignore
Comment thread
dhairyajangir marked this conversation as resolved.
elif (
hasattr(entity_object, "context_provider")
and entity_object.context_provider
and hasattr(entity_object.context_provider, "__class__")
Expand Down

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions python/packages/devui/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"test:agent-details": "node --test test/agent-details.test.mjs",
"test:tracing": "node --test test/trace-polling.test.mjs",
"preview": "vite preview"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export function AgentDetailsModal({
className={!agent.middleware || agent.middleware.length === 0 ? "md:col-start-2" : ""}
>
<div className="font-mono text-xs text-foreground">
{agent.context_provider}
{agent.context_provider.join(", ")}
</div>
</DetailCard>
)}
Expand Down
2 changes: 1 addition & 1 deletion python/packages/devui/frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface AgentInfo {
instructions?: string;
model_id?: string;
chat_client_type?: string;
context_provider?: string | undefined;
context_provider?: string[] | undefined;
middleware?: string[] | undefined;
}

Expand Down
75 changes: 75 additions & 0 deletions python/packages/devui/frontend/test/agent-details.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft. All rights reserved.

import assert from "node:assert/strict";
import { after, before, test } from "node:test";
import { fileURLToPath } from "node:url";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { createServer } from "vite";

let server;
let ApiClient;
let AgentDetailsModal;
const originalStorage = globalThis.localStorage;

before(async () => {
globalThis.localStorage = { getItem: () => null, removeItem: () => {} };
server = await createServer({
configFile: false,
root: fileURLToPath(new URL("..", import.meta.url)),
resolve: { alias: { "@": fileURLToPath(new URL("../src", import.meta.url)) } },
server: { middlewareMode: true, hmr: false, ws: false, watch: null },
optimizeDeps: { noDiscovery: true, include: [] },
});
({ ApiClient } = await server.ssrLoadModule("/src/services/api.ts"));
({ AgentDetailsModal } = await server.ssrLoadModule(
"/src/components/features/agent/agent-details-modal.tsx"
));
});

after(async () => {
await server?.close();
globalThis.localStorage = originalStorage;
});

async function renderDiscoveredAgent(t, contextProvider) {
t.mock.method(globalThis, "fetch", async () => ({
ok: true,
json: async () => ({
entities: [{
id: "test-agent",
type: "agent",
source: "in_memory",
context_provider: contextProvider,
}],
}),
}));
const { agents } = await new ApiClient("http://localhost").getEntities();
return renderToStaticMarkup(createElement(AgentDetailsModal, {
agent: agents[0],
open: true,
onOpenChange: () => {},
}));
}

test("separates multiple context provider names in discovery order", async (t) => {
const html = await renderDiscoveredAgent(t, [
"InMemoryHistoryProvider",
"ContextProvider",
]);

assert.match(html, />InMemoryHistoryProvider, ContextProvider<\/div>/);
assert.doesNotMatch(html, /InMemoryHistoryProviderContextProvider/);
});

test("renders a single context provider without an added separator", async (t) => {
const html = await renderDiscoveredAgent(t, ["InMemoryHistoryProvider"]);

assert.match(html, />InMemoryHistoryProvider<\/div>/);
});

test("omits the context provider card when discovery has no providers", async (t) => {
const html = await renderDiscoveredAgent(t, undefined);

assert.doesNotMatch(html, /Context Provider/);
});
32 changes: 32 additions & 0 deletions python/packages/devui/tests/devui/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,44 @@
import asyncio
import tempfile
from pathlib import Path
from types import SimpleNamespace

import pytest
from agent_framework import Agent, ContextProvider, InMemoryHistoryProvider

from agent_framework_devui._discovery import EntityDiscovery
from agent_framework_devui._utils import extract_agent_metadata

# Note: test_entities_dir fixture is provided by conftest.py


@pytest.mark.parametrize(
"context_providers, expected",
[
([], None),
([InMemoryHistoryProvider()], ["InMemoryHistoryProvider"]),
(
[InMemoryHistoryProvider(), ContextProvider("additional")],
["InMemoryHistoryProvider", "ContextProvider"],
),
],
)
async def test_context_providers_in_agent_metadata(mock_base_chat_client, context_providers, expected):
"""Discovery includes all configured providers in their configured order."""
agent = Agent(client=mock_base_chat_client, name="Example", context_providers=context_providers)

entity_info = await EntityDiscovery().create_entity_info_from_object(agent)

assert entity_info.context_provider == expected


def test_legacy_context_provider_in_agent_metadata():
"""Agent-like objects using the singular attribute retain their metadata."""
agent = SimpleNamespace(context_provider=InMemoryHistoryProvider())

assert extract_agent_metadata(agent)["context_provider"] == ["InMemoryHistoryProvider"]


async def test_discover_agents(test_entities_dir):
"""Test that agent discovery works and returns valid agent entities."""
discovery = EntityDiscovery(test_entities_dir)
Expand Down
Loading