Merge branch 'main' into feature-python-foundry-agents

This commit is contained in:
Dmytro Struk
2025-11-05 19:32:00 -08:00
Unverified
252 changed files with 15107 additions and 2669 deletions
+18
View File
@@ -7,11 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.0.0b251106] - 2025-11-06
### Changed
- **agent-framework-ag-ui**: export sample ag-ui agents ([#1927](https://github.com/microsoft/agent-framework/pull/1927))
## [1.0.0b251105] - 2025-11-05
### Added
- **agent-framework-ag-ui**: Initial release of AG-UI protocol integration for Agent Framework ([#1826](https://github.com/microsoft/agent-framework/pull/1826))
- **agent-framework-chatkit**: ChatKit integration with a sample application ([#1273](https://github.com/microsoft/agent-framework/pull/1273))
- Added parameter to disable agent cleanup in AzureAIAgentClient ([#1882](https://github.com/microsoft/agent-framework/pull/1882))
- Add support for Python 3.14 ([#1904](https://github.com/microsoft/agent-framework/pull/1904))
### Changed
- [BREAKING] Replaced AIProjectClient with AgentsClient in Foundry ([#1936](https://github.com/microsoft/agent-framework/pull/1936))
- Updates to Tools ([#1835](https://github.com/microsoft/agent-framework/pull/1835))
### Fixed
- Fix missing packaging dependency ([#1929](https://github.com/microsoft/agent-framework/pull/1929))
## [1.0.0b251104] - 2025-11-04
+16 -2
View File
@@ -4,6 +4,7 @@
import argparse
from enum import Enum
import glob
import logging
import tempfile
import subprocess # nosec
@@ -32,6 +33,16 @@ def with_color(text: str, color: Colors) -> str:
return f"{color.value}{text}{Colors.CEND.value}"
def expand_file_patterns(patterns: list[str]) -> list[str]:
"""Expand glob patterns to actual file paths."""
all_files: list[str] = []
for pattern in patterns:
# Handle both relative and absolute paths
matches = glob.glob(pattern, recursive=True)
all_files.extend(matches)
return sorted(set(all_files)) # Remove duplicates and sort
def extract_python_code_blocks(markdown_file_path: str) -> list[tuple[str, int]]:
"""Extract Python code blocks from a Markdown file."""
with open(markdown_file_path, encoding="utf-8") as file:
@@ -113,7 +124,10 @@ def check_code_blocks(markdown_file_paths: list[str], exclude_patterns: list[str
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Check code blocks in Markdown files for syntax errors.")
# Argument is a list of markdown files containing glob patterns
parser.add_argument("markdown_files", nargs="+", help="Markdown files to check.")
parser.add_argument("markdown_files", nargs="+", help="Markdown files to check (supports glob patterns).")
parser.add_argument("--exclude", action="append", help="Exclude files containing this pattern.")
args = parser.parse_args()
check_code_blocks(args.markdown_files, args.exclude)
# Expand glob patterns to actual file paths
expanded_files = expand_file_patterns(args.markdown_files)
check_code_blocks(expanded_files, args.exclude)
+1 -1
View File
@@ -4,7 +4,7 @@ description = "A2A integration for Microsoft Agent Framework."
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
@@ -1 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
"""Example agents for AG-UI demonstration."""
from . import agents
__all__ = ["agents"]
@@ -1,3 +1,25 @@
# Copyright (c) Microsoft. All rights reserved.
"""Example agents for AG-UI demonstration."""
from .document_writer_agent import document_writer_agent
from .human_in_the_loop_agent import human_in_the_loop_agent
from .recipe_agent import recipe_agent
from .research_assistant_agent import research_assistant_agent
from .simple_agent import agent as simple_agent
from .task_planner_agent import task_planner_agent
from .task_steps_agent import task_steps_agent_wrapped
from .ui_generator_agent import ui_generator_agent
from .weather_agent import weather_agent
__all__ = [
"document_writer_agent",
"human_in_the_loop_agent",
"recipe_agent",
"research_assistant_agent",
"simple_agent",
"task_planner_agent",
"task_steps_agent_wrapped",
"ui_generator_agent",
"weather_agent",
]
+2 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "agent-framework-ag-ui"
version = "1.0.0b251105"
version = "1.0.0b251106"
description = "AG-UI protocol integration for Agent Framework"
readme = "README.md"
license-files = ["LICENSE"]
@@ -41,6 +41,7 @@ build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["agent_framework_ag_ui"]
force-include = { "examples" = "agent_framework_ag_ui_examples" }
[tool.pytest.ini_options]
asyncio_mode = "auto"
+1 -1
View File
@@ -4,7 +4,7 @@ description = "Anthropic integration for Microsoft Agent Framework."
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
@@ -2,8 +2,9 @@
import importlib.metadata
from ._chat_client import AzureAIAgentClient, AzureAISettings
from ._chat_client import AzureAIAgentClient
from ._client import AzureAIClient
from ._shared import AzureAISettings
try:
__version__ = importlib.metadata.version(__name__)
@@ -859,7 +859,7 @@ class AzureAIAgentClient(BaseChatClient):
config_args["market"] = market
if set_lang := additional_props.get("set_lang"):
config_args["set_lang"] = set_lang
# Bing Grounding (support both connection_id and connection_name)
# Bing Grounding
connection_id = additional_props.get("connection_id") or os.getenv("BING_CONNECTION_ID")
# Custom Bing Search
custom_connection_id = additional_props.get("custom_connection_id") or os.getenv(
@@ -873,7 +873,7 @@ class AzureAIAgentClient(BaseChatClient):
if connection_id:
conn_id = connection_id
else:
raise ServiceInitializationError("Neither connection_id nor connection_name provided.")
raise ServiceInitializationError("Parameter connection_id is not provided.")
bing_search = BingGroundingTool(connection_id=conn_id, **config_args)
if custom_connection_id and custom_instance_name:
bing_search = BingCustomSearchTool(
+2 -1
View File
@@ -4,7 +4,7 @@ description = "Azure AI Foundry integration for Microsoft Agent Framework."
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
@@ -26,6 +26,7 @@ dependencies = [
"agent-framework-core",
"azure-ai-projects >= 2.0.0a20251103001",
"azure-ai-agents == 1.2.0b5",
"aiohttp",
]
[tool.uv]
+1 -1
View File
@@ -4,7 +4,7 @@ description = "OpenAI ChatKit integration for Microsoft Agent Framework."
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251001"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
+1 -1
View File
@@ -4,7 +4,7 @@ description = "Copilot Studio integration for Microsoft Agent Framework."
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
@@ -17,7 +17,7 @@ from openai.types.chat import ChatCompletion, ChatCompletionChunk
from openai.types.images_response import ImagesResponse
from openai.types.responses.response import Response
from openai.types.responses.response_stream_event import ResponseStreamEvent
from packaging import version
from packaging.version import parse
from pydantic import SecretStr
from .._logging import get_logger
@@ -58,8 +58,8 @@ def _check_openai_version_for_callable_api_key() -> None:
If the version is too old, raise a ServiceInitializationError with helpful message.
"""
try:
current_version = version.parse(openai.__version__)
min_required_version = version.parse("1.106.0")
current_version = parse(openai.__version__)
min_required_version = parse("1.106.0")
if current_version < min_required_version:
raise ServiceInitializationError(
+2 -1
View File
@@ -4,7 +4,7 @@ description = "Microsoft Agent Framework for building AI Agents with Python. Thi
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
@@ -36,6 +36,7 @@ dependencies = [
"openai>=1.99.0",
"azure-identity>=1,<2",
"mcp[ws]>=1.13",
"packaging>=24.1",
]
[project.optional-dependencies]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,11 +2,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/agentframework.svg" />
<link rel="icon" type="image/svg+xml" href="agentframework.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Agent Framework Dev UI</title>
<script type="module" crossorigin src="/assets/index-D_Y1oSGu.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CE4pGoXh.css">
<script type="module" crossorigin src="./assets/index.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index.css">
</head>
<body>
<div id="root"></div>
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

+2 -2
View File
@@ -2,12 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/agentframework.svg" />
<link rel="icon" type="image/svg+xml" href="agentframework.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Agent Framework Dev UI</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script type="module" src="src/main.tsx"></script>
</body>
</html>
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

@@ -45,6 +45,7 @@ import type {
ExtendedResponseStreamEvent,
} from "@/types";
import { useDevUIStore } from "@/stores";
import { loadStreamingState } from "@/services/streaming-state";
type DebugEventHandler = (event: ExtendedResponseStreamEvent | "clear") => void;
@@ -229,7 +230,6 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
const scrollAreaRef = useRef<HTMLDivElement>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const accumulatedText = useRef<string>("");
const textareaRef = useRef<HTMLTextAreaElement>(null);
const currentMessageUsage = useRef<{
total_tokens: number;
@@ -237,6 +237,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
output_tokens: number;
} | null>(null);
const userJustSentMessage = useRef<boolean>(false);
const accumulatedTextRef = useRef<string>("");
// Auto-scroll to bottom when new items arrive
useEffect(() => {
@@ -281,33 +282,270 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
// Load conversations when agent changes
useEffect(() => {
// Resume streaming after page refresh
const resumeStreaming = async (
assistantMessage: import("@/types/openai").ConversationMessage,
conversation: Conversation,
agent: AgentInfo
) => {
// Load the stored state to get the response ID
const storedState = loadStreamingState(conversation.id);
if (!storedState || !storedState.responseId) {
setIsStreaming(false);
return;
}
try {
// Use the stored responseId to resume the stream via GET /v1/responses/{responseId}
const openAIRequest: import("@/types/agent-framework").AgentFrameworkRequest = {
model: agent.id,
input: [], // Not needed for resume (using GET)
stream: true,
conversation: conversation.id,
};
// Pass the response ID explicitly to trigger GET request
const streamGenerator = apiClient.streamAgentExecutionOpenAIDirect(
agent.id,
openAIRequest,
conversation.id,
storedState.responseId // Pass response ID for resume
);
for await (const openAIEvent of streamGenerator) {
// Pass all events to debug panel
onDebugEvent(openAIEvent);
// Handle response.completed event
if (openAIEvent.type === "response.completed") {
const completedEvent = openAIEvent as import("@/types/openai").ResponseCompletedEvent;
const usage = completedEvent.response?.usage;
if (usage) {
currentMessageUsage.current = {
input_tokens: usage.input_tokens,
output_tokens: usage.output_tokens,
total_tokens: usage.total_tokens,
};
}
continue;
}
// Handle response.failed event
if (openAIEvent.type === "response.failed") {
const failedEvent = openAIEvent as import("@/types/openai").ResponseFailedEvent;
const error = failedEvent.response?.error;
const errorMessage = error
? typeof error === "object" && "message" in error
? (error as any).message
: JSON.stringify(error)
: "Request failed";
const currentItems = useDevUIStore.getState().chatItems;
setChatItems(currentItems.map((item) =>
item.id === assistantMessage.id && item.type === "message"
? {
...item,
content: [
{
type: "text",
text: accumulatedTextRef.current || errorMessage,
} as import("@/types/openai").MessageTextContent,
],
status: "incomplete" as const,
}
: item
));
setIsStreaming(false);
return;
}
// Handle function approval request events
if (openAIEvent.type === "response.function_approval.requested") {
const approvalEvent = openAIEvent as import("@/types/openai").ResponseFunctionApprovalRequestedEvent;
setPendingApprovals([
...useDevUIStore.getState().pendingApprovals,
{
request_id: approvalEvent.request_id,
function_call: approvalEvent.function_call,
},
]);
continue;
}
// Handle function approval response events
if (openAIEvent.type === "response.function_approval.responded") {
const responseEvent = openAIEvent as import("@/types/openai").ResponseFunctionApprovalRespondedEvent;
setPendingApprovals(
useDevUIStore.getState().pendingApprovals.filter((a) => a.request_id !== responseEvent.request_id)
);
continue;
}
// Handle error events
if (openAIEvent.type === "error") {
const errorEvent = openAIEvent as ExtendedResponseStreamEvent & { message?: string };
const errorMessage = errorEvent.message || "An error occurred";
const currentItems = useDevUIStore.getState().chatItems;
setChatItems(currentItems.map((item) =>
item.id === assistantMessage.id && item.type === "message"
? {
...item,
content: [
{
type: "text",
text: accumulatedTextRef.current || errorMessage,
} as import("@/types/openai").MessageTextContent,
],
status: "incomplete" as const,
}
: item
));
setIsStreaming(false);
return;
}
// Handle text delta events
if (
openAIEvent.type === "response.output_text.delta" &&
"delta" in openAIEvent &&
openAIEvent.delta
) {
accumulatedTextRef.current += openAIEvent.delta;
const currentItems = useDevUIStore.getState().chatItems;
setChatItems(currentItems.map((item) =>
item.id === assistantMessage.id && item.type === "message"
? {
...item,
content: [
{
type: "text",
text: accumulatedTextRef.current,
} as import("@/types/openai").MessageTextContent,
],
status: "in_progress" as const,
}
: item
));
}
}
// Stream ended - mark as complete
const finalUsage = currentMessageUsage.current;
const currentItems = useDevUIStore.getState().chatItems;
setChatItems(currentItems.map((item) =>
item.id === assistantMessage.id && item.type === "message"
? {
...item,
status: "completed" as const,
usage: finalUsage || undefined,
}
: item
));
setIsStreaming(false);
if (finalUsage) {
updateConversationUsage(finalUsage.total_tokens);
}
currentMessageUsage.current = null;
} catch (error) {
const currentItems = useDevUIStore.getState().chatItems;
setChatItems(currentItems.map((item) =>
item.id === assistantMessage.id && item.type === "message"
? {
...item,
content: [
{
type: "text",
text: `Error resuming stream: ${
error instanceof Error ? error.message : "Unknown error"
}`,
} as import("@/types/openai").MessageTextContent,
],
status: "incomplete" as const,
}
: item
));
setIsStreaming(false);
}
};
const loadConversations = async () => {
if (!selectedAgent) return;
setLoadingConversations(true);
try {
// Step 1: Try to list conversations from backend (DevUI extension)
// This works with DevUI backend but fails with OpenAI/Azure (they don't have list endpoint)
// Step 1: Always try to list conversations from backend first
// This ensures we get the latest data from the server
try {
const { data: conversations } = await apiClient.listConversations(
selectedAgent.id
);
// Backend successfully returned conversations list
setAvailableConversations(conversations);
if (conversations.length > 0) {
// Found conversations on backend - use most recent
const mostRecent = conversations[0];
setAvailableConversations(conversations);
setCurrentConversation(mostRecent);
// Load conversation items from backend
try {
const { data: items } = await apiClient.listConversationItems(
mostRecent.id
);
// Load all conversation items with pagination
let allItems: unknown[] = [];
let hasMore = true;
let after: string | undefined = undefined;
while (hasMore) {
const result = await apiClient.listConversationItems(
mostRecent.id,
{ order: "asc", after } // Load in chronological order (oldest first)
);
allItems = allItems.concat(result.data);
hasMore = result.has_more;
// Get the last item's ID for pagination
if (hasMore && result.data.length > 0) {
const lastItem = result.data[result.data.length - 1] as { id?: string };
after = lastItem.id;
}
}
// Use OpenAI ConversationItems directly (no conversion!)
setChatItems(items as import("@/types/openai").ConversationItem[]);
setChatItems(allItems as import("@/types/openai").ConversationItem[]);
setIsStreaming(false);
// Check for incomplete stream and resume if needed
const state = loadStreamingState(mostRecent.id);
if (state && !state.completed) {
accumulatedTextRef.current = state.accumulatedText || "";
// Add assistant message with resumed text
const assistantMsg: import("@/types/openai").ConversationMessage = {
id: state.lastMessageId || `assistant-${Date.now()}`,
type: "message",
role: "assistant",
content: state.accumulatedText ? [{ type: "text", text: state.accumulatedText }] : [],
status: "in_progress",
};
setChatItems([...allItems as import("@/types/openai").ConversationItem[], assistantMsg]);
setIsStreaming(true);
// Resume streaming from where we left off
setTimeout(() => {
resumeStreaming(assistantMsg, mostRecent, selectedAgent);
}, 100);
}
// Scroll to bottom after loading conversation
setTimeout(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, 100);
} catch {
// 404 means conversation exists but has no items yet (newly created)
// This is normal - just start with empty chat
@@ -316,11 +554,6 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
setIsStreaming(false);
}
// Cache to localStorage for faster future loads
localStorage.setItem(
`devui_convs_${selectedAgent.id}`,
JSON.stringify(conversations)
);
return;
}
} catch {
@@ -371,9 +604,6 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
setAvailableConversations([newConversation]);
setChatItems([]);
setIsStreaming(false);
// Save to localStorage
localStorage.setItem(cachedKey, JSON.stringify([newConversation]));
} catch {
setAvailableConversations([]);
setChatItems([]);
@@ -387,10 +617,12 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
setChatItems([]);
setIsStreaming(false);
setCurrentConversation(undefined);
accumulatedText.current = "";
accumulatedTextRef.current = "";
loadConversations();
}, [selectedAgent, setLoadingConversations, setAvailableConversations, setCurrentConversation, setChatItems, setIsStreaming]);
// currentConversation is intentionally excluded - this effect should only run when agent changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedAgent, onDebugEvent, setChatItems, setIsStreaming, setLoadingConversations, setAvailableConversations, setCurrentConversation, setPendingApprovals, updateConversationUsage]);
// Handle file uploads
const handleFilesSelected = async (files: File[]) => {
@@ -626,16 +858,11 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
setIsStreaming(false);
// Reset conversation usage by setting it to initial state
useDevUIStore.setState({ conversationUsage: { total_tokens: 0, message_count: 0 } });
accumulatedText.current = "";
// Update localStorage cache with new conversation
const cachedKey = `devui_convs_${selectedAgent.id}`;
const updated = [newConversation, ...availableConversations];
localStorage.setItem(cachedKey, JSON.stringify(updated));
accumulatedTextRef.current = "";
} catch {
// Failed to create conversation
}
}, [selectedAgent, availableConversations, setCurrentConversation, setAvailableConversations, setChatItems, setIsStreaming]);
}, [selectedAgent, setCurrentConversation, setAvailableConversations, setChatItems, setIsStreaming]);
// Handle conversation deletion
const handleDeleteConversation = useCallback(
@@ -660,15 +887,6 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
);
setAvailableConversations(updatedConversations);
// Update localStorage cache
if (selectedAgent) {
const cachedKey = `devui_convs_${selectedAgent.id}`;
localStorage.setItem(
cachedKey,
JSON.stringify(updatedConversations)
);
}
// If deleted conversation was selected, switch to another conversation or clear chat
if (currentConversation?.id === conversationId) {
if (updatedConversations.length > 0) {
@@ -683,7 +901,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
setChatItems([]);
setIsStreaming(false);
useDevUIStore.setState({ conversationUsage: { total_tokens: 0, message_count: 0 } });
accumulatedText.current = "";
accumulatedTextRef.current = "";
}
}
@@ -694,7 +912,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
alert("Failed to delete conversation. Please try again.");
}
},
[availableConversations, currentConversation, selectedAgent, onDebugEvent, setAvailableConversations, setCurrentConversation, setChatItems, setIsStreaming]
[availableConversations, currentConversation, onDebugEvent, setAvailableConversations, setCurrentConversation, setChatItems, setIsStreaming]
);
// Handle conversation selection
@@ -711,11 +929,28 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
onDebugEvent("clear");
try {
// Load conversation history from backend
const result = await apiClient.listConversationItems(conversationId);
// Load conversation history from backend with pagination
let allItems: unknown[] = [];
let hasMore = true;
let after: string | undefined = undefined;
while (hasMore) {
const result = await apiClient.listConversationItems(conversationId, {
order: "asc", // Load in chronological order (oldest first)
after,
});
allItems = allItems.concat(result.data);
hasMore = result.has_more;
// Get the last item's ID for pagination
if (hasMore && result.data.length > 0) {
const lastItem = result.data[result.data.length - 1] as { id?: string };
after = lastItem.id;
}
}
// Use OpenAI ConversationItems directly (no conversion!)
const items = result.data as import("@/types/openai").ConversationItem[];
const items = allItems as import("@/types/openai").ConversationItem[];
setChatItems(items);
setIsStreaming(false);
@@ -727,6 +962,27 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
message_count: items.length,
}
});
// Check for incomplete stream and restore accumulated text
const state = loadStreamingState(conversationId);
if (state?.accumulatedText) {
accumulatedTextRef.current = state.accumulatedText;
// Add assistant message with resumed text - streaming will continue automatically
const assistantMsg: import("@/types/openai").ConversationMessage = {
id: `assistant-${Date.now()}`,
type: "message",
role: "assistant",
content: [{ type: "output_text", text: state.accumulatedText }],
status: "in_progress",
};
setChatItems([...items, assistantMsg]);
setIsStreaming(true);
}
// Scroll to bottom after loading conversation
setTimeout(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, 100);
} catch {
// 404 means conversation doesn't exist or has no items yet
// This can happen if server restarted (in-memory store cleared)
@@ -736,7 +992,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
useDevUIStore.setState({ conversationUsage: { total_tokens: 0, message_count: 0 } });
}
accumulatedText.current = "";
accumulatedTextRef.current = "";
},
[availableConversations, onDebugEvent, setCurrentConversation, setChatItems, setIsStreaming]
);
@@ -851,13 +1107,18 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
}
}
// Clear any previous streaming state for this conversation before starting new message
if (conversationToUse?.id) {
apiClient.clearStreamingState(conversationToUse.id);
}
const apiRequest = {
input: request.input,
conversation_id: conversationToUse?.id,
};
// Clear text accumulator for new response
accumulatedText.current = "";
accumulatedTextRef.current = "";
// Use OpenAI-compatible API streaming - direct event handling
const streamGenerator = apiClient.streamAgentExecutionOpenAI(
@@ -884,6 +1145,35 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
continue; // Continue processing other events
}
// Handle response.failed event
if (openAIEvent.type === "response.failed") {
const failedEvent = openAIEvent as import("@/types/openai").ResponseFailedEvent;
const error = failedEvent.response?.error;
const errorMessage = error
? typeof error === "object" && "message" in error
? (error as any).message
: JSON.stringify(error)
: "Request failed";
const currentItems = useDevUIStore.getState().chatItems;
setChatItems(currentItems.map((item) =>
item.id === assistantMessage.id && item.type === "message"
? {
...item,
content: [
{
type: "text",
text: accumulatedTextRef.current || errorMessage,
} as import("@/types/openai").MessageTextContent,
],
status: "incomplete" as const,
}
: item
));
setIsStreaming(false);
return;
}
// Handle function approval request events
if (openAIEvent.type === "response.function_approval.requested") {
const approvalEvent = openAIEvent as import("@/types/openai").ResponseFunctionApprovalRequestedEvent;
@@ -943,7 +1233,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
"delta" in openAIEvent &&
openAIEvent.delta
) {
accumulatedText.current += openAIEvent.delta;
accumulatedTextRef.current += openAIEvent.delta;
// Update assistant message with accumulated content
const currentItems = useDevUIStore.getState().chatItems;
@@ -954,7 +1244,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
content: [
{
type: "text",
text: accumulatedText.current,
text: accumulatedTextRef.current,
} as import("@/types/openai").MessageTextContent,
],
status: "in_progress" as const,
@@ -23,7 +23,7 @@ interface ContentRendererProps {
// Text content renderer
function TextContentRenderer({ content, className, isStreaming }: ContentRendererProps) {
if (content.type !== "text") return null;
if (content.type !== "text" && content.type !== "input_text" && content.type !== "output_text") return null;
const text = content.text;
@@ -160,6 +160,8 @@ function FileContentRenderer({ content, className }: ContentRendererProps) {
export function OpenAIContentRenderer({ content, className, isStreaming }: ContentRendererProps) {
switch (content.type) {
case "text":
case "input_text":
case "output_text":
return <TextContentRenderer content={content} className={className} isStreaming={isStreaming} />;
case "input_image":
return <ImageContentRenderer content={content} className={className} />;
@@ -554,6 +554,10 @@ export function WorkflowView({
try {
const request = { input_data: inputData };
// Clear any previous streaming state before starting new workflow execution
// Note: Workflows don't use conversation IDs, so we use workflow ID as the key
apiClient.clearStreamingState(selectedWorkflow.id);
// Use OpenAI-compatible API streaming - direct event handling
const streamGenerator = apiClient.streamWorkflowExecutionOpenAI(
selectedWorkflow.id,
@@ -3,6 +3,10 @@ import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { ThemeProvider } from "./components/theme-provider"
import { initStreamingState } from "./services/api"
// Initialize streaming state management (clears expired states)
initStreamingState();
createRoot(document.getElementById('root')!).render(
<StrictMode>
+263 -144
View File
@@ -14,6 +14,12 @@ import type {
} from "@/types";
import type { AgentFrameworkRequest } from "@/types/agent-framework";
import type { ExtendedResponseStreamEvent } from "@/types/openai";
import {
loadStreamingState,
updateStreamingState,
markStreamingCompleted,
clearStreamingState,
} from "./streaming-state";
// Backend API response type - polymorphic entity that can be agent or workflow
// This matches the Python Pydantic EntityInfo model which has all fields optional
@@ -57,9 +63,27 @@ const DEFAULT_API_BASE_URL =
? import.meta.env.VITE_API_BASE_URL
: "http://localhost:8080";
// Retry configuration for streaming
const RETRY_INTERVAL_MS = 1000; // Retry every second
const MAX_RETRY_ATTEMPTS = 600; // Max 600 retries (10 minutes total)
// Get backend URL from localStorage or default
function getBackendUrl(): string {
return localStorage.getItem("devui_backend_url") || DEFAULT_API_BASE_URL;
const stored = localStorage.getItem("devui_backend_url");
if (stored) return stored;
// If VITE_API_BASE_URL is explicitly set to empty string, use relative path
// This allows the frontend to call the same host it's served from
if (import.meta.env.VITE_API_BASE_URL === "") {
return "";
}
return DEFAULT_API_BASE_URL;
}
// Helper to sleep for a given duration
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
class ApiClient {
@@ -272,6 +296,8 @@ class ApiClient {
await this.request(`/v1/conversations/${conversationId}`, {
method: "DELETE",
});
// Clear streaming state when conversation is deleted
clearStreamingState(conversationId);
return true;
} catch {
return false;
@@ -297,10 +323,230 @@ class ApiClient {
// OpenAI-compatible streaming methods using /v1/responses endpoint
// Private helper method that handles the actual streaming with retry logic
private async *streamOpenAIResponse(
openAIRequest: AgentFrameworkRequest,
conversationId?: string,
resumeResponseId?: string
): AsyncGenerator<ExtendedResponseStreamEvent, void, unknown> {
let lastSequenceNumber = -1;
let retryCount = 0;
let hasYieldedAnyEvent = false;
let currentResponseId: string | undefined = resumeResponseId;
let lastMessageId: string | undefined = undefined;
// Try to resume from stored state if conversation ID is provided
if (conversationId) {
const storedState = loadStreamingState(conversationId);
if (storedState) {
// Use stored response ID if no explicit one provided
if (!resumeResponseId) {
currentResponseId = storedState.responseId;
}
lastSequenceNumber = storedState.lastSequenceNumber;
lastMessageId = storedState.lastMessageId;
// Replay stored events only if we're not explicitly resuming
// (explicit resume means the caller already has the events)
if (!resumeResponseId) {
for (const event of storedState.events) {
hasYieldedAnyEvent = true;
yield event;
}
} else {
// Mark that we've already seen events up to this sequence number
hasYieldedAnyEvent = storedState.events.length > 0;
}
}
}
while (retryCount <= MAX_RETRY_ATTEMPTS) {
try {
// If we have a response_id from a previous attempt, use GET endpoint to resume
// Otherwise, use POST to create a new response
let response: Response;
if (currentResponseId) {
const params = new URLSearchParams();
params.set("stream", "true");
if (lastSequenceNumber >= 0) {
params.set("starting_after", lastSequenceNumber.toString());
}
const url = `${this.baseUrl}/v1/responses/${currentResponseId}?${params.toString()}`;
response = await fetch(url, {
method: "GET",
headers: {
Accept: "text/event-stream",
},
});
} else {
const url = `${this.baseUrl}/v1/responses`;
response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify(openAIRequest),
});
}
if (!response.ok) {
// Try to extract detailed error message from response body
let errorMessage = `Request failed with status ${response.status}`;
try {
const errorBody = await response.json();
if (errorBody.error && errorBody.error.message) {
errorMessage = errorBody.error.message;
} else if (errorBody.detail) {
errorMessage = errorBody.detail;
}
} catch {
// Fallback to generic message if parsing fails
}
throw new Error(errorMessage);
}
const reader = response.body?.getReader();
if (!reader) {
throw new Error("Response body is not readable");
}
const decoder = new TextDecoder();
let buffer = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
// Stream completed successfully
if (conversationId) {
markStreamingCompleted(conversationId);
}
return;
}
buffer += decoder.decode(value, { stream: true });
// Parse SSE events
const lines = buffer.split("\n");
buffer = lines.pop() || ""; // Keep incomplete line in buffer
for (const line of lines) {
if (line.startsWith("data: ")) {
const dataStr = line.slice(6);
// Handle [DONE] signal
if (dataStr === "[DONE]") {
if (conversationId) {
markStreamingCompleted(conversationId);
}
return;
}
try {
const openAIEvent: ExtendedResponseStreamEvent =
JSON.parse(dataStr);
// Capture response_id if present in the event for use in retries
if ("response" in openAIEvent && openAIEvent.response && typeof openAIEvent.response === "object" && "id" in openAIEvent.response) {
const newResponseId = openAIEvent.response.id as string;
if (!currentResponseId || currentResponseId !== newResponseId) {
currentResponseId = newResponseId;
}
} else if ("id" in openAIEvent && typeof openAIEvent.id === "string" && openAIEvent.id.startsWith("resp_")) {
const newResponseId = openAIEvent.id;
if (!currentResponseId || currentResponseId !== newResponseId) {
currentResponseId = newResponseId;
}
}
// Track last message ID if present (for user/assistant messages)
if ("item_id" in openAIEvent && openAIEvent.item_id) {
lastMessageId = openAIEvent.item_id;
}
// Check for sequence number restart (server restarted response)
const eventSeq = "sequence_number" in openAIEvent ? openAIEvent.sequence_number : undefined;
if (eventSeq !== undefined) {
// If we've received events before and sequence restarted from 0/1
if (hasYieldedAnyEvent && eventSeq <= 1 && lastSequenceNumber > 1) {
// Server restarted the response - clear old state and start fresh
if (conversationId) {
clearStreamingState(conversationId);
}
yield {
type: "error",
message: "Connection lost - previous response failed. Starting new response.",
} as ExtendedResponseStreamEvent;
lastSequenceNumber = eventSeq;
hasYieldedAnyEvent = true;
// Save new event to storage
if (conversationId && currentResponseId) {
updateStreamingState(conversationId, openAIEvent, currentResponseId, lastMessageId);
}
yield openAIEvent;
}
// Skip events we've already seen (resume from last position)
else if (eventSeq <= lastSequenceNumber) {
continue; // Skip duplicate event
} else {
lastSequenceNumber = eventSeq;
hasYieldedAnyEvent = true;
// Save event to storage before yielding
if (conversationId && currentResponseId) {
updateStreamingState(conversationId, openAIEvent, currentResponseId, lastMessageId);
}
yield openAIEvent;
}
} else {
// No sequence number - just yield the event
hasYieldedAnyEvent = true;
// Still save to storage if we have conversation context
if (conversationId && currentResponseId) {
updateStreamingState(conversationId, openAIEvent, currentResponseId, lastMessageId);
}
yield openAIEvent;
}
} catch (e) {
console.error("Failed to parse OpenAI SSE event:", e);
}
}
}
}
} finally {
reader.releaseLock();
}
} catch (error) {
// Network error occurred - prepare to retry
retryCount++;
if (retryCount > MAX_RETRY_ATTEMPTS) {
// Max retries exceeded - give up
throw new Error(
`Connection failed after ${MAX_RETRY_ATTEMPTS} retry attempts: ${error instanceof Error ? error.message : String(error)}`
);
}
// Wait before retrying
await sleep(RETRY_INTERVAL_MS);
// Loop will retry with GET if we have response_id, otherwise POST
}
}
}
// Stream agent execution using OpenAI format with simplified routing
async *streamAgentExecutionOpenAI(
agentId: string,
request: RunAgentRequest
request: RunAgentRequest,
resumeResponseId?: string
): AsyncGenerator<ExtendedResponseStreamEvent, void, unknown> {
const openAIRequest: AgentFrameworkRequest = {
model: agentId, // Model IS the entity_id (simplified routing!)
@@ -309,87 +555,20 @@ class ApiClient {
conversation: request.conversation_id, // OpenAI standard conversation param
};
return yield* this.streamAgentExecutionOpenAIDirect(agentId, openAIRequest);
return yield* this.streamAgentExecutionOpenAIDirect(agentId, openAIRequest, request.conversation_id, resumeResponseId);
}
// Stream agent execution using direct OpenAI format
async *streamAgentExecutionOpenAIDirect(
_agentId: string,
openAIRequest: AgentFrameworkRequest
openAIRequest: AgentFrameworkRequest,
conversationId?: string,
resumeResponseId?: string
): AsyncGenerator<ExtendedResponseStreamEvent, void, unknown> {
const response = await fetch(`${this.baseUrl}/v1/responses`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify(openAIRequest),
});
if (!response.ok) {
// Try to extract detailed error message from response body
let errorMessage = `Request failed with status ${response.status}`;
try {
const errorBody = await response.json();
if (errorBody.error && errorBody.error.message) {
errorMessage = errorBody.error.message;
} else if (errorBody.detail) {
errorMessage = errorBody.detail;
}
} catch {
// Fallback to generic message if parsing fails
}
throw new Error(errorMessage);
}
const reader = response.body?.getReader();
if (!reader) {
throw new Error("Response body is not readable");
}
const decoder = new TextDecoder();
let buffer = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
buffer += decoder.decode(value, { stream: true });
// Parse SSE events
const lines = buffer.split("\n");
buffer = lines.pop() || ""; // Keep incomplete line in buffer
for (const line of lines) {
if (line.startsWith("data: ")) {
const dataStr = line.slice(6);
// Handle [DONE] signal
if (dataStr === "[DONE]") {
return;
}
try {
const openAIEvent: ExtendedResponseStreamEvent =
JSON.parse(dataStr);
yield openAIEvent; // Direct pass-through - no conversion!
} catch (e) {
console.error("Failed to parse OpenAI SSE event:", e);
}
}
}
}
} finally {
reader.releaseLock();
}
yield* this.streamOpenAIResponse(openAIRequest, conversationId, resumeResponseId);
}
// Stream workflow execution using OpenAI format - direct event pass-through
// Stream workflow execution using OpenAI format
async *streamWorkflowExecutionOpenAI(
workflowId: string,
request: RunWorkflowRequest
@@ -402,75 +581,7 @@ class ApiClient {
conversation: request.conversation_id, // Include conversation if present
};
const response = await fetch(`${this.baseUrl}/v1/responses`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify(openAIRequest),
});
if (!response.ok) {
// Try to extract detailed error message from response body
let errorMessage = `Request failed with status ${response.status}`;
try {
const errorBody = await response.json();
if (errorBody.error && errorBody.error.message) {
errorMessage = errorBody.error.message;
} else if (errorBody.detail) {
errorMessage = errorBody.detail;
}
} catch {
// Fallback to generic message if parsing fails
}
throw new Error(errorMessage);
}
const reader = response.body?.getReader();
if (!reader) {
throw new Error("Response body is not readable");
}
const decoder = new TextDecoder();
let buffer = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
buffer += decoder.decode(value, { stream: true });
// Parse SSE events
const lines = buffer.split("\n");
buffer = lines.pop() || ""; // Keep incomplete line in buffer
for (const line of lines) {
if (line.startsWith("data: ")) {
const dataStr = line.slice(6);
// Handle [DONE] signal
if (dataStr === "[DONE]") {
return;
}
try {
const openAIEvent: ExtendedResponseStreamEvent =
JSON.parse(dataStr);
yield openAIEvent; // Direct pass-through - no conversion!
} catch (e) {
console.error("Failed to parse OpenAI SSE event:", e);
}
}
}
}
} finally {
reader.releaseLock();
}
yield* this.streamOpenAIResponse(openAIRequest, request.conversation_id);
}
// REMOVED: Legacy streaming methods - use streamAgentExecutionOpenAI and streamWorkflowExecutionOpenAI instead
@@ -503,8 +614,16 @@ class ApiClient {
body: JSON.stringify(request),
});
}
// Clear streaming state for a conversation (e.g., when starting a new message)
clearStreamingState(conversationId: string): void {
clearStreamingState(conversationId);
}
}
// Export singleton instance
export const apiClient = new ApiClient();
export { ApiClient };
// Export streaming state init function
export { initStreamingState } from "./streaming-state";
@@ -0,0 +1,199 @@
/**
* Streaming State Persistence
*
* Manages browser storage of streaming response state to enable:
* - Resume interrupted streams after page refresh
* - Replay cached events before fetching new ones
* - Graceful recovery from network disconnections
*/
import type { ExtendedResponseStreamEvent } from "@/types/openai";
export interface StreamingState {
conversationId: string;
responseId: string;
lastMessageId?: string;
lastSequenceNumber: number;
events: ExtendedResponseStreamEvent[];
timestamp: number; // When this state was last updated
completed: boolean; // Whether the stream completed successfully
accumulatedText?: string; // Accumulated text content for quick restoration
}
const STORAGE_KEY_PREFIX = "devui_streaming_state_";
const STATE_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours
/**
* Storage key for a specific conversation
*/
function getStorageKey(conversationId: string): string {
return `${STORAGE_KEY_PREFIX}${conversationId}`;
}
/**
* Extract accumulated text from events (for quick restoration)
*/
function extractAccumulatedText(events: ExtendedResponseStreamEvent[]): string {
let text = "";
for (const event of events) {
if (event.type === "response.output_text.delta" && "delta" in event) {
text += event.delta;
}
}
return text;
}
/**
* Save streaming state to browser storage
*/
export function saveStreamingState(state: StreamingState): void {
try {
const key = getStorageKey(state.conversationId);
const data = JSON.stringify(state);
localStorage.setItem(key, data);
} catch (error) {
console.error("Failed to save streaming state:", error);
// If storage is full, try to clear old states
try {
clearExpiredStreamingStates();
// Try again
const key = getStorageKey(state.conversationId);
const data = JSON.stringify(state);
localStorage.setItem(key, data);
} catch {
console.error("Failed to save streaming state even after cleanup");
}
}
}
/**
* Load streaming state from browser storage
*/
export function loadStreamingState(conversationId: string): StreamingState | null {
try {
const key = getStorageKey(conversationId);
const data = localStorage.getItem(key);
if (!data) {
return null;
}
const state: StreamingState = JSON.parse(data);
// Check if state has expired
const age = Date.now() - state.timestamp;
if (age > STATE_EXPIRY_MS) {
clearStreamingState(conversationId);
return null;
}
// If stream was completed, no need to resume
if (state.completed) {
return null;
}
return state;
} catch (error) {
console.error("Failed to load streaming state:", error);
return null;
}
}
/**
* Update streaming state with a new event
*/
export function updateStreamingState(
conversationId: string,
event: ExtendedResponseStreamEvent,
responseId: string,
lastMessageId?: string
): void {
try {
const existing = loadStreamingState(conversationId);
const sequenceNumber = "sequence_number" in event ? event.sequence_number : undefined;
const newEvents = existing ? [...existing.events, event] : [event];
const state: StreamingState = {
conversationId,
responseId,
lastMessageId,
lastSequenceNumber: sequenceNumber ?? (existing?.lastSequenceNumber ?? -1),
events: newEvents,
timestamp: Date.now(),
completed: event.type === "response.completed" || event.type === "response.failed",
accumulatedText: extractAccumulatedText(newEvents),
};
saveStreamingState(state);
} catch (error) {
console.error("Failed to update streaming state:", error);
}
}
/**
* Mark streaming state as completed
*/
export function markStreamingCompleted(conversationId: string): void {
try {
const existing = loadStreamingState(conversationId);
if (existing) {
existing.completed = true;
existing.timestamp = Date.now();
saveStreamingState(existing);
}
} catch (error) {
console.error("Failed to mark streaming as completed:", error);
}
}
/**
* Clear streaming state for a conversation
*/
export function clearStreamingState(conversationId: string): void {
try {
const key = getStorageKey(conversationId);
localStorage.removeItem(key);
} catch (error) {
console.error("Failed to clear streaming state:", error);
}
}
/**
* Clear all expired streaming states
*/
export function clearExpiredStreamingStates(): void {
try {
const keys = Object.keys(localStorage);
const now = Date.now();
for (const key of keys) {
if (key.startsWith(STORAGE_KEY_PREFIX)) {
try {
const data = localStorage.getItem(key);
if (data) {
const state: StreamingState = JSON.parse(data);
const age = now - state.timestamp;
if (age > STATE_EXPIRY_MS || state.completed) {
localStorage.removeItem(key);
}
}
} catch {
// Invalid state, remove it
localStorage.removeItem(key);
}
}
}
} catch (error) {
console.error("Failed to clear expired streaming states:", error);
}
}
/**
* Initialize streaming state management (call on app startup)
*/
export function initStreamingState(): void {
// Clear expired states on startup
clearExpiredStreamingStates();
}
@@ -347,6 +347,18 @@ export interface MessageTextContent {
text: string;
}
export interface MessageInputTextContent {
type: "input_text";
text: string;
}
export interface MessageOutputTextContent {
type: "output_text";
text: string;
annotations?: any[];
logprobs?: any[];
}
export interface MessageInputImage {
type: "input_image";
image_url: string;
@@ -376,6 +388,8 @@ export interface MessageFunctionApprovalResponseContent {
export type MessageContent =
| MessageTextContent
| MessageInputTextContent
| MessageOutputTextContent
| MessageInputImage
| MessageInputFile
| MessageFunctionApprovalResponseContent;
@@ -5,6 +5,7 @@ import path from "path";
// https://vite.dev/config/
export default defineConfig({
base: "",
plugins: [react(), tailwindcss()],
resolve: {
alias: {
@@ -12,12 +13,20 @@ export default defineConfig({
},
},
build: {
commonjsOptions: {
// Enable deterministic builds, as per https://github.com/vitejs/vite/issues/13672#issuecomment-1784110536
strictRequires: true,
},
outDir: "../agent_framework_devui/ui",
emptyOutDir: true,
rollupOptions: {
output: {
manualChunks: undefined,
inlineDynamicImports: true,
// Use static filenames instead of content hashes
entryFileNames: "assets/index.js",
chunkFileNames: "assets/[name].js",
assetFileNames: "assets/[name].[ext]",
},
},
},
+38 -403
View File
@@ -24,7 +24,7 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz"
integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==
"@babel/core@^7.28.3":
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.28.3":
version "7.28.3"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz"
integrity sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==
@@ -168,156 +168,9 @@
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@emnapi/core@^1.4.3", "@emnapi/core@^1.4.5":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.5.0.tgz#85cd84537ec989cebb2343606a1ee663ce4edaf0"
integrity sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==
dependencies:
"@emnapi/wasi-threads" "1.1.0"
tslib "^2.4.0"
"@emnapi/runtime@^1.4.3", "@emnapi/runtime@^1.4.5":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.5.0.tgz#9aebfcb9b17195dce3ab53c86787a6b7d058db73"
integrity sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==
dependencies:
tslib "^2.4.0"
"@emnapi/wasi-threads@1.1.0", "@emnapi/wasi-threads@^1.0.4":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf"
integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==
dependencies:
tslib "^2.4.0"
"@esbuild/aix-ppc64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz#bef96351f16520055c947aba28802eede3c9e9a9"
integrity sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==
"@esbuild/android-arm64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz#d2e70be7d51a529425422091e0dcb90374c1546c"
integrity sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==
"@esbuild/android-arm@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.9.tgz#d2a753fe2a4c73b79437d0ba1480e2d760097419"
integrity sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==
"@esbuild/android-x64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.9.tgz#5278836e3c7ae75761626962f902a0d55352e683"
integrity sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==
"@esbuild/darwin-arm64@0.25.9":
version "0.25.9"
resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz"
integrity sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==
"@esbuild/darwin-x64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz#e27dbc3b507b3a1cea3b9280a04b8b6b725f82be"
integrity sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==
"@esbuild/freebsd-arm64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz#364e3e5b7a1fd45d92be08c6cc5d890ca75908ca"
integrity sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==
"@esbuild/freebsd-x64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz#7c869b45faeb3df668e19ace07335a0711ec56ab"
integrity sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==
"@esbuild/linux-arm64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz#48d42861758c940b61abea43ba9a29b186d6cb8b"
integrity sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==
"@esbuild/linux-arm@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz#6ce4b9cabf148274101701d112b89dc67cc52f37"
integrity sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==
"@esbuild/linux-ia32@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz#207e54899b79cac9c26c323fc1caa32e3143f1c4"
integrity sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==
"@esbuild/linux-loong64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz#0ba48a127159a8f6abb5827f21198b999ffd1fc0"
integrity sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==
"@esbuild/linux-mips64el@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz#a4d4cc693d185f66a6afde94f772b38ce5d64eb5"
integrity sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==
"@esbuild/linux-ppc64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz#0f5805c1c6d6435a1dafdc043cb07a19050357db"
integrity sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==
"@esbuild/linux-riscv64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz#6776edece0f8fca79f3386398b5183ff2a827547"
integrity sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==
"@esbuild/linux-s390x@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz#3f6f29ef036938447c2218d309dc875225861830"
integrity sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==
"@esbuild/linux-x64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz#831fe0b0e1a80a8b8391224ea2377d5520e1527f"
integrity sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==
"@esbuild/netbsd-arm64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz#06f99d7eebe035fbbe43de01c9d7e98d2a0aa548"
integrity sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==
"@esbuild/netbsd-x64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz#db99858e6bed6e73911f92a88e4edd3a8c429a52"
integrity sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==
"@esbuild/openbsd-arm64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz#afb886c867e36f9d86bb21e878e1185f5d5a0935"
integrity sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==
"@esbuild/openbsd-x64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz#30855c9f8381fac6a0ef5b5f31ac6e7108a66ecf"
integrity sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==
"@esbuild/openharmony-arm64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz#2f2144af31e67adc2a8e3705c20c2bd97bd88314"
integrity sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==
"@esbuild/sunos-x64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz#69b99a9b5bd226c9eb9c6a73f990fddd497d732e"
integrity sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==
"@esbuild/win32-arm64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz#d789330a712af916c88325f4ffe465f885719c6b"
integrity sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==
"@esbuild/win32-ia32@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz#52fc735406bd49688253e74e4e837ac2ba0789e3"
integrity sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==
"@esbuild/win32-x64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz#585624dc829cfb6e7c0aa6c3ca7d7e6daa87e34f"
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz"
integrity sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.7.0":
@@ -368,7 +221,7 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@9.33.0", "@eslint/js@^9.33.0":
"@eslint/js@^9.33.0", "@eslint/js@9.33.0":
version "9.33.0"
resolved "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz"
integrity sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==
@@ -482,15 +335,6 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@napi-rs/wasm-runtime@^0.2.12":
version "0.2.12"
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2"
integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==
dependencies:
"@emnapi/core" "^1.4.3"
"@emnapi/runtime" "^1.4.3"
"@tybys/wasm-util" "^0.10.0"
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
@@ -499,7 +343,7 @@
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -740,7 +584,7 @@
aria-hidden "^1.2.4"
react-remove-scroll "^2.6.3"
"@radix-ui/react-slot@1.2.3", "@radix-ui/react-slot@^1.2.3":
"@radix-ui/react-slot@^1.2.3", "@radix-ui/react-slot@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz"
integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==
@@ -829,104 +673,9 @@
resolved "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz"
integrity sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==
"@rollup/rollup-android-arm-eabi@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz#6e236cd2fd29bb01a300ad4ff6ed0f1a17550e69"
integrity sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==
"@rollup/rollup-android-arm64@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz#808f2c9c7e68161add613ebcb0eac5a058a0df3c"
integrity sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==
"@rollup/rollup-darwin-arm64@4.47.1":
version "4.47.1"
resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz"
integrity sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==
"@rollup/rollup-darwin-x64@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz#9aac64e886435493f2e3a0aa5e4aad098a90814c"
integrity sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==
"@rollup/rollup-freebsd-arm64@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz#9fc804264f7b7a7cdad3747950299f990163be1f"
integrity sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==
"@rollup/rollup-freebsd-x64@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz#933feaff864feb03bbbcd0c18ea351ade957cf79"
integrity sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==
"@rollup/rollup-linux-arm-gnueabihf@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz#02915e6b2c55fe5961c27404aba2d9c8ef48ac6c"
integrity sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==
"@rollup/rollup-linux-arm-musleabihf@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz#1afef33191b26e76ae7f0d0dc767efc6be1285ce"
integrity sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==
"@rollup/rollup-linux-arm64-gnu@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz#6e7f38fb99d14143de3ce33204e6cd61e1c2c780"
integrity sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==
"@rollup/rollup-linux-arm64-musl@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz#25ab09f14bbcba85a604bcee2962d2486db90794"
integrity sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==
"@rollup/rollup-linux-loongarch64-gnu@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz#d3e3a3fd61e21b2753094391dee9b515a2bc9ecd"
integrity sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==
"@rollup/rollup-linux-ppc64-gnu@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz#6b44445e2bd5866692010de241bf18d2ae8b0cb8"
integrity sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==
"@rollup/rollup-linux-riscv64-gnu@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz#3ff412d20d3b157e6aadabf84788e8c5cb221ba7"
integrity sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==
"@rollup/rollup-linux-riscv64-musl@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz#104f451497d53d82a49c6d08c13c59f5f30eed57"
integrity sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==
"@rollup/rollup-linux-s390x-gnu@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz#d04de7b21d181f30750760cb3553946306506172"
integrity sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==
"@rollup/rollup-linux-x64-gnu@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz#a6ba88ff7480940a435b1e67ddbb3f207a7ae02f"
integrity sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==
"@rollup/rollup-linux-x64-musl@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz#c912c8ffa0c242ed3175cd91cdeaef98109afa54"
integrity sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==
"@rollup/rollup-win32-arm64-msvc@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz#ca5eaae89443554b461bb359112a056528cfdac0"
integrity sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==
"@rollup/rollup-win32-ia32-msvc@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz#34e76172515fb4b374eb990d59f54faff938246e"
integrity sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==
"@rollup/rollup-win32-x64-msvc@4.47.1":
version "4.47.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz#e5e0a0bae2c9d4858cc9b8dc508b2e10d7f0df8b"
resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz"
integrity sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==
"@tailwindcss/node@4.1.12":
@@ -942,71 +691,9 @@
source-map-js "^1.2.1"
tailwindcss "4.1.12"
"@tailwindcss/oxide-android-arm64@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz#27920fe61fa2743afe8a8ca296fa640b609d17d5"
integrity sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==
"@tailwindcss/oxide-darwin-arm64@4.1.12":
version "4.1.12"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz"
integrity sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==
"@tailwindcss/oxide-darwin-x64@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz#8ddb7e5ddfd9b049ec84a2bda99f2b04a86859f5"
integrity sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==
"@tailwindcss/oxide-freebsd-x64@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz#da1c0b16b7a5f95a1e400f299a3ec94fb6fd40ac"
integrity sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz#34e558aa6e869c6fe9867cb78ed7ba651b9fcaa4"
integrity sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==
"@tailwindcss/oxide-linux-arm64-gnu@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz#0a00a8146ab6215f81b2d385056c991441bf390e"
integrity sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==
"@tailwindcss/oxide-linux-arm64-musl@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz#b138f494068884ae0d8c343dc1904b22f5e98dc6"
integrity sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==
"@tailwindcss/oxide-linux-x64-gnu@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz#5b9d5f23b15cdb714639f5b9741c0df5d610f794"
integrity sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==
"@tailwindcss/oxide-linux-x64-musl@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz#f68ec530d3ca6875ea9015bcd5dd0762ee5e2f5d"
integrity sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==
"@tailwindcss/oxide-wasm32-wasi@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz#9fd15a1ebde6076c42c445c5e305c31673ead965"
integrity sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==
dependencies:
"@emnapi/core" "^1.4.5"
"@emnapi/runtime" "^1.4.5"
"@emnapi/wasi-threads" "^1.0.4"
"@napi-rs/wasm-runtime" "^0.2.12"
"@tybys/wasm-util" "^0.10.0"
tslib "^2.8.0"
"@tailwindcss/oxide-win32-arm64-msvc@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz#938bcc6a82e1120ea4fe2ce94be0a8cdf3ae92c7"
integrity sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==
"@tailwindcss/oxide-win32-x64-msvc@4.1.12":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz#b1ee2ed0ef2c4095ddec3684a1987e2b3613af36"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz"
integrity sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==
"@tailwindcss/oxide@4.1.12":
@@ -1039,13 +726,6 @@
"@tailwindcss/oxide" "4.1.12"
tailwindcss "4.1.12"
"@tybys/wasm-util@^0.10.0":
version "0.10.0"
resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.0.tgz#2fd3cd754b94b378734ce17058d0507c45c88369"
integrity sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==
dependencies:
tslib "^2.4.0"
"@types/babel__core@^7.20.5":
version "7.20.5"
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz"
@@ -1118,7 +798,7 @@
"@types/d3-interpolate" "*"
"@types/d3-selection" "*"
"@types/estree@1.0.8", "@types/estree@^1.0.6":
"@types/estree@^1.0.6", "@types/estree@1.0.8":
version "1.0.8"
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
@@ -1128,19 +808,19 @@
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/node@^24.3.0":
"@types/node@^20.19.0 || >=22.12.0", "@types/node@^24.3.0":
version "24.3.0"
resolved "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz"
integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==
dependencies:
undici-types "~7.10.0"
"@types/react-dom@^19.1.7":
"@types/react-dom@*", "@types/react-dom@^19.1.7":
version "19.1.7"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz"
integrity sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==
"@types/react@^19.1.10":
"@types/react@*", "@types/react@^19.0.0", "@types/react@^19.1.10", "@types/react@>=16.8", "@types/react@>=18.0.0":
version "19.1.10"
resolved "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz"
integrity sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==
@@ -1162,7 +842,7 @@
natural-compare "^1.4.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/parser@8.40.0":
"@typescript-eslint/parser@^8.40.0", "@typescript-eslint/parser@8.40.0":
version "8.40.0"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.40.0.tgz"
integrity sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==
@@ -1190,7 +870,7 @@
"@typescript-eslint/types" "8.40.0"
"@typescript-eslint/visitor-keys" "8.40.0"
"@typescript-eslint/tsconfig-utils@8.40.0", "@typescript-eslint/tsconfig-utils@^8.40.0":
"@typescript-eslint/tsconfig-utils@^8.40.0", "@typescript-eslint/tsconfig-utils@8.40.0":
version "8.40.0"
resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz"
integrity sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==
@@ -1206,7 +886,7 @@
debug "^4.3.4"
ts-api-utils "^2.1.0"
"@typescript-eslint/types@8.40.0", "@typescript-eslint/types@^8.40.0":
"@typescript-eslint/types@^8.40.0", "@typescript-eslint/types@8.40.0":
version "8.40.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz"
integrity sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==
@@ -1259,7 +939,7 @@
"@xyflow/react@^12.8.4":
version "12.8.4"
resolved "https://registry.yarnpkg.com/@xyflow/react/-/react-12.8.4.tgz#db0eabd9e356c25f5ebf427413a8c5dd46113394"
resolved "https://registry.npmjs.org/@xyflow/react/-/react-12.8.4.tgz"
integrity sha512-bqUu4T5QSHiCFPkoH+b+LROKwQJdLvcjhGbNW9c1dLafCBRjmH1IYz0zPE+lRDXCtQ9kRyFxz3tG19+8VORJ1w==
dependencies:
"@xyflow/system" "0.0.68"
@@ -1286,7 +966,7 @@ acorn-jsx@^5.3.2:
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.15.0:
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.15.0:
version "8.15.0"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
@@ -1347,7 +1027,7 @@ braces@^3.0.3:
dependencies:
fill-range "^7.1.1"
browserslist@^4.24.0:
browserslist@^4.24.0, "browserslist@>= 4.21.0":
version "4.25.3"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz"
integrity sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==
@@ -1443,7 +1123,7 @@ csstype@^3.0.2:
resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz"
integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
"d3-drag@2 - 3", d3-drag@^3.0.0:
d3-drag@^3.0.0, "d3-drag@2 - 3":
version "3.0.0"
resolved "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz"
integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
@@ -1456,14 +1136,14 @@ csstype@^3.0.2:
resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz"
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
"d3-interpolate@1 - 3", d3-interpolate@^3.0.1:
d3-interpolate@^3.0.1, "d3-interpolate@1 - 3":
version "3.0.1"
resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz"
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
dependencies:
d3-color "1 - 3"
"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0:
d3-selection@^3.0.0, "d3-selection@2 - 3", d3-selection@3:
version "3.0.0"
resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz"
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
@@ -1600,7 +1280,7 @@ eslint-visitor-keys@^4.2.1:
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz"
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
eslint@^9.33.0:
"eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.33.0, eslint@>=8.40:
version "9.33.0"
resolved "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz"
integrity sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==
@@ -1846,7 +1526,7 @@ isexe@^2.0.0:
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
jiti@^2.5.1:
jiti@*, jiti@^2.5.1, jiti@>=1.21.0:
version "2.5.1"
resolved "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz"
integrity sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==
@@ -1903,57 +1583,12 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
lightningcss-darwin-arm64@1.30.1:
version "1.30.1"
resolved "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz"
integrity sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==
lightningcss-darwin-x64@1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz#e81105d3fd6330860c15fe860f64d39cff5fbd22"
integrity sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==
lightningcss-freebsd-x64@1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz#a0e732031083ff9d625c5db021d09eb085af8be4"
integrity sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==
lightningcss-linux-arm-gnueabihf@1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz#1f5ecca6095528ddb649f9304ba2560c72474908"
integrity sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==
lightningcss-linux-arm64-gnu@1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz#eee7799726103bffff1e88993df726f6911ec009"
integrity sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==
lightningcss-linux-arm64-musl@1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz#f2e4b53f42892feeef8f620cbb889f7c064a7dfe"
integrity sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==
lightningcss-linux-x64-gnu@1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz#2fc7096224bc000ebb97eea94aea248c5b0eb157"
integrity sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==
lightningcss-linux-x64-musl@1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz#66dca2b159fd819ea832c44895d07e5b31d75f26"
integrity sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==
lightningcss-win32-arm64-msvc@1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz#7d8110a19d7c2d22bfdf2f2bb8be68e7d1b69039"
integrity sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==
lightningcss-win32-x64-msvc@1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz#fd7dd008ea98494b85d24b4bea016793f2e0e352"
resolved "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz"
integrity sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==
lightningcss@1.30.1:
lightningcss@^1.21.0, lightningcss@1.30.1:
version "1.30.1"
resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz"
integrity sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==
@@ -2063,7 +1698,7 @@ natural-compare@^1.4.0:
next-themes@^0.4.6:
version "0.4.6"
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.4.6.tgz#8d7e92d03b8fea6582892a50a928c9b23502e8b6"
resolved "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz"
integrity sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==
node-releases@^2.0.19:
@@ -2124,7 +1759,7 @@ picomatch@^2.3.1:
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@^4.0.3:
"picomatch@^3 || ^4", picomatch@^4.0.3:
version "4.0.3"
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
@@ -2153,7 +1788,7 @@ queue-microtask@^1.2.2:
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
react-dom@^19.1.1:
"react-dom@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", react-dom@^19.1.1, react-dom@>=16.8.0, react-dom@>=17:
version "19.1.1"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz"
integrity sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==
@@ -2192,7 +1827,7 @@ react-style-singleton@^2.2.2, react-style-singleton@^2.2.3:
get-nonce "^1.0.0"
tslib "^2.0.0"
react@^19.1.1:
"react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", react@^19.1.1, react@>=16.8, react@>=16.8.0, react@>=17, react@>=18.0.0:
version "19.1.1"
resolved "https://registry.npmjs.org/react/-/react-19.1.1.tgz"
integrity sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==
@@ -2292,7 +1927,7 @@ tailwind-merge@^3.3.1:
resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz"
integrity sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==
tailwindcss@4.1.12, tailwindcss@^4.1.12:
tailwindcss@^4.1.12, tailwindcss@4.1.12:
version "4.1.12"
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz"
integrity sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==
@@ -2316,7 +1951,7 @@ tar@^7.4.3:
tinyglobby@^0.2.15:
version "0.2.15"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz"
integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
dependencies:
fdir "^6.5.0"
@@ -2334,7 +1969,7 @@ ts-api-utils@^2.1.0:
resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz"
integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.8.0:
tslib@^2.0.0, tslib@^2.1.0:
version "2.8.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
@@ -2361,7 +1996,7 @@ typescript-eslint@^8.39.1:
"@typescript-eslint/typescript-estree" "8.40.0"
"@typescript-eslint/utils" "8.40.0"
typescript@~5.8.3:
typescript@>=4.8.4, "typescript@>=4.8.4 <6.0.0", typescript@~5.8.3:
version "5.8.3"
resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz"
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
@@ -2401,15 +2036,15 @@ use-sidecar@^1.1.3:
detect-node-es "^1.1.0"
tslib "^2.0.0"
use-sync-external-store@^1.2.2:
use-sync-external-store@^1.2.2, use-sync-external-store@>=1.2.0:
version "1.5.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz"
integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==
vite@^7.1.11:
version "7.1.11"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.11.tgz#4d006746112fee056df64985191e846ebfb6007e"
integrity sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==
"vite@^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vite@^5.2.0 || ^6 || ^7", vite@^7.1.11:
version "7.1.12"
resolved "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz"
integrity sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==
dependencies:
esbuild "^0.25.0"
fdir "^6.5.0"
@@ -2456,5 +2091,5 @@ zustand@^4.4.0:
zustand@^5.0.8:
version "5.0.8"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.8.tgz#b998a0c088c7027a20f2709141a91cb07ac57f8a"
resolved "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz"
integrity sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==
+1 -1
View File
@@ -4,7 +4,7 @@ description = "Debug UI for Microsoft Agent Framework with OpenAI-compatible API
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://github.com/microsoft/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
@@ -10,8 +10,6 @@ Required Environment Variables:
AZURE_AI_MODEL_DEPLOYMENT_NAME: Name of the model deployment to use
Optional Environment Variables:
BING_CONNECTION_NAME: Name of the Bing connection for web search
OR
BING_CONNECTION_ID: ID of the Bing connection for web search
Authentication:
@@ -21,7 +19,7 @@ Authentication:
Example:
export AZURE_AI_PROJECT_ENDPOINT="https://your-project.azure.com"
export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o"
export BING_CONNECTION_NAME="bing-grounding-connection"
export BING_CONNECTION_ID="connection-id"
az login
"""
+1 -1
View File
@@ -4,7 +4,7 @@ description = "Experimental modules for Microsoft Agent Framework"
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
+1 -1
View File
@@ -4,7 +4,7 @@ description = "Mem0 integration for Microsoft Agent Framework."
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
+1 -1
View File
@@ -4,7 +4,7 @@ description = "Microsoft Purview (Graph dataSecurityAndGovernance) integration f
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://github.com/microsoft/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
+1 -1
View File
@@ -4,7 +4,7 @@ description = "Redis integration for Microsoft Agent Framework."
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
+2 -2
View File
@@ -4,7 +4,7 @@ description = "Microsoft Agent Framework for building AI Agents with Python. Thi
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b251104"
version = "1.0.0b251105"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
@@ -212,7 +212,7 @@ exclude_dirs = ["tests", "./run_tasks_in_packages_if_exists.py", "./check_md_cod
executor.type = "uv"
[tool.poe.tasks]
markdown-code-lint = """uv run python check_md_code_blocks.py README.md ./packages/**/README.md ./samples/**/*.md --exclude cookiecutter-agent-framework-lab --exclude tau2 --exclude packages/devui/frontend"""
markdown-code-lint = "uv run python check_md_code_blocks.py 'README.md' './packages/**/README.md' './samples/**/*.md' --exclude cookiecutter-agent-framework-lab --exclude tau2 --exclude 'packages/devui/frontend'"
docs-install = "uv sync --all-packages --all-extras --dev -U --prerelease=if-necessary-or-explicit --group=docs"
docs-clean = "rm -rf docs/build"
docs-build = "uv run python ./docs/generate_docs.py"
@@ -38,10 +38,8 @@ Before running the examples, you need to set up your environment variables. You
AZURE_AI_MODEL_DEPLOYMENT_NAME="your-model-deployment-name"
```
3. For samples using Bing Grounding search (like `azure_ai_with_bing_grounding.py` and `azure_ai_with_multiple_tools.py`), you'll also need either:
3. For samples using Bing Grounding search (like `azure_ai_with_bing_grounding.py` and `azure_ai_with_multiple_tools.py`), you'll also need:
```
BING_CONNECTION_NAME="bing-grounding-connection"
# OR
BING_CONNECTION_ID="your-bing-connection-id"
```
@@ -49,7 +47,7 @@ Before running the examples, you need to set up your environment variables. You
- Go to [Azure AI Foundry portal](https://ai.azure.com)
- Navigate to your project's "Connected resources" section
- Add a new connection for "Grounding with Bing Search"
- Copy either the connection name or ID
- Copy the ID
### Option 2: Using environment variables directly
@@ -58,9 +56,7 @@ Set the environment variables in your shell:
```bash
export AZURE_AI_PROJECT_ENDPOINT="your-project-endpoint"
export AZURE_AI_MODEL_DEPLOYMENT_NAME="your-model-deployment-name"
export BING_CONNECTION_NAME="your-bing-connection-name" # Optional, only needed for web search samples
# OR
export BING_CONNECTION_ID="your-bing-connection-id" # Alternative to BING_CONNECTION_NAME
export BING_CONNECTION_ID="your-bing-connection-id"
```
### Required Variables
@@ -70,4 +66,4 @@ export BING_CONNECTION_ID="your-bing-connection-id" # Alternative to BING_CONNE
### Optional Variables
- `BING_CONNECTION_NAME` or `BING_CONNECTION_ID`: Your Bing connection name or ID (required for `azure_ai_with_bing_grounding.py` and `azure_ai_with_multiple_tools.py`)
- `BING_CONNECTION_ID`: Your Bing connection ID (required for `azure_ai_with_bing_grounding.py` and `azure_ai_with_multiple_tools.py`)
@@ -5,6 +5,7 @@ import os
from agent_framework import ChatAgent, CitationAnnotation
from agent_framework.azure import AzureAIAgentClient
from azure.ai.agents.aio import AgentsClient
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import ConnectionType
from azure.identity.aio import AzureCliCredential
@@ -38,16 +39,17 @@ async def main() -> None:
# Create the client and manually create an agent with Azure AI Search tool
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as client,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
):
ai_search_conn_id = ""
async for connection in client.connections.list():
async for connection in project_client.connections.list():
if connection.type == ConnectionType.AZURE_AI_SEARCH:
ai_search_conn_id = connection.id
break
# 1. Create Azure AI agent with the search tool
azure_ai_agent = await client.agents.create_agent(
azure_ai_agent = await project_client.agents.create_agent(
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
name="HotelSearchAgent",
instructions=(
@@ -69,7 +71,7 @@ async def main() -> None:
)
# 2. Create chat client with the existing agent
chat_client = AzureAIAgentClient(project_client=client, agent_id=azure_ai_agent.id)
chat_client = AzureAIAgentClient(agents_client=agents_client, agent_id=azure_ai_agent.id)
try:
async with ChatAgent(
@@ -112,7 +114,7 @@ async def main() -> None:
finally:
# Clean up the agent manually
await client.agents.delete_agent(azure_ai_agent.id)
await project_client.agents.delete_agent(azure_ai_agent.id)
if __name__ == "__main__":
@@ -12,8 +12,7 @@ uses Bing Grounding search to find real-time information from the web.
Prerequisites:
1. A connected Grounding with Bing Search resource in your Azure AI project
2. Set either BING_CONNECTION_NAME or BING_CONNECTION_ID environment variable
Example: BING_CONNECTION_NAME="bing-grounding-connection"
2. Set BING_CONNECTION_ID environment variable
Example: BING_CONNECTION_ID="your-bing-connection-id"
To set up Bing Grounding:
@@ -27,7 +26,7 @@ To set up Bing Grounding:
async def main() -> None:
"""Main function demonstrating Azure AI agent with Bing Grounding search."""
# 1. Create Bing Grounding search tool using HostedWebSearchTool
# The connection_name or ID will be automatically picked up from environment variable
# The connection ID will be automatically picked up from environment variable
bing_search_tool = HostedWebSearchTool(
name="Bing Grounding Search",
description="Search the web for current information using Bing",
@@ -5,6 +5,7 @@ import os
from agent_framework import ChatAgent
from agent_framework.azure import AzureAIAgentClient
from azure.ai.agents.aio import AgentsClient
from azure.ai.projects.aio import AIProjectClient
from azure.identity.aio import AzureCliCredential
@@ -22,16 +23,17 @@ async def main() -> None:
# Create the client
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as client,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
):
azure_ai_agent = await client.agents.create_agent(
azure_ai_agent = await project_client.agents.create_agent(
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
# Create remote agent with default instructions
# These instructions will persist on created agent for every run.
instructions="End each response with [END].",
)
chat_client = AzureAIAgentClient(project_client=client, agent_id=azure_ai_agent.id)
chat_client = AzureAIAgentClient(agents_client=agents_client, agent_id=azure_ai_agent.id)
try:
async with ChatAgent(
@@ -50,7 +52,7 @@ async def main() -> None:
print(f"Agent: {result}\n")
finally:
# Clean up the agent manually
await client.agents.delete_agent(azure_ai_agent.id)
await project_client.agents.delete_agent(azure_ai_agent.id)
if __name__ == "__main__":
@@ -7,7 +7,7 @@ from typing import Annotated
from agent_framework import ChatAgent
from agent_framework.azure import AzureAIAgentClient
from azure.ai.projects.aio import AIProjectClient
from azure.ai.agents.aio import AgentsClient
from azure.identity.aio import AzureCliCredential
from pydantic import Field
@@ -33,16 +33,16 @@ async def main() -> None:
# Create the client
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as client,
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
):
# Create an thread that will persist
created_thread = await client.agents.threads.create()
created_thread = await agents_client.threads.create()
try:
async with ChatAgent(
# passing in the client is optional here, so if you take the agent_id from the portal
# you can use it directly without the two lines above.
chat_client=AzureAIAgentClient(project_client=client),
chat_client=AzureAIAgentClient(agents_client=agents_client),
instructions="You are a helpful weather agent.",
tools=get_weather,
) as agent:
@@ -52,7 +52,7 @@ async def main() -> None:
print(f"Result: {result}\n")
finally:
# Clean up the thread manually
await client.agents.threads.delete(created_thread.id)
await agents_client.threads.delete(created_thread.id)
if __name__ == "__main__":
@@ -44,8 +44,6 @@ async def main() -> None:
AzureCliCredential() as credential,
AzureAIAgentClient(async_credential=credential) as chat_client,
):
# enable azure-ai observability
await chat_client.setup_azure_ai_observability()
agent = chat_client.create_agent(
name="DocsAgent",
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
@@ -69,8 +69,6 @@ async def main() -> None:
AzureCliCredential() as credential,
AzureAIAgentClient(async_credential=credential) as chat_client,
):
# enable azure-ai observability
await chat_client.setup_azure_ai_observability()
agent = chat_client.create_agent(
name="DocsAgent",
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
@@ -9,7 +9,9 @@ import dotenv
from agent_framework import ChatAgent
from agent_framework.azure import AzureAIAgentClient
from agent_framework.observability import get_tracer
from azure.ai.agents.aio import AgentsClient
from azure.ai.projects.aio import AIProjectClient
from azure.core.exceptions import ResourceNotFoundError
from azure.identity.aio import AzureCliCredential
from opentelemetry.trace import SpanKind
from opentelemetry.trace.span import format_trace_id
@@ -38,16 +40,36 @@ async def get_weather(
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def setup_azure_ai_observability(
project_client: AIProjectClient, enable_sensitive_data: bool | None = None
) -> None:
"""Use this method to setup tracing in your Azure AI Project.
This will take the connection string from the AIProjectClient.
It will override any connection string that is set in the environment variables.
It will disable any OTLP endpoint that might have been set.
"""
try:
conn_string = await project_client.telemetry.get_application_insights_connection_string()
except ResourceNotFoundError:
print("No Application Insights connection string found for the Azure AI Project.")
return
from agent_framework.observability import setup_observability
setup_observability(applicationinsights_connection_string=conn_string, enable_sensitive_data=enable_sensitive_data)
async def main():
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project,
AzureAIAgentClient(project_client=project) as client,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
AzureAIAgentClient(agents_client=agents_client) as client,
):
# This will enable tracing and configure the application to send telemetry data to the
# Application Insights instance attached to the Azure AI project.
# This will override any existing configuration.
await client.setup_azure_ai_observability()
await setup_azure_ai_observability(project_client)
questions = ["What's the weather in Amsterdam?", "and in Paris, and which is better?", "Why is the sky blue?"]
@@ -9,7 +9,9 @@ import dotenv
from agent_framework import HostedCodeInterpreterTool
from agent_framework.azure import AzureAIAgentClient
from agent_framework.observability import get_tracer
from azure.ai.agents.aio import AgentsClient
from azure.ai.projects.aio import AIProjectClient
from azure.core.exceptions import ResourceNotFoundError
from azure.identity.aio import AzureCliCredential
from opentelemetry.trace import SpanKind
from opentelemetry.trace.span import format_trace_id
@@ -42,6 +44,25 @@ async def get_weather(
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def setup_azure_ai_observability(
project_client: AIProjectClient, enable_sensitive_data: bool | None = None
) -> None:
"""Use this method to setup tracing in your Azure AI Project.
This will take the connection string from the AIProjectClient instance.
It will override any connection string that is set in the environment variables.
It will disable any OTLP endpoint that might have been set.
"""
try:
conn_string = await project_client.telemetry.get_application_insights_connection_string()
except ResourceNotFoundError:
print("No Application Insights connection string found for the Azure AI Project.")
return
from agent_framework.observability import setup_observability
setup_observability(applicationinsights_connection_string=conn_string, enable_sensitive_data=enable_sensitive_data)
async def main() -> None:
"""Run an AI service.
@@ -62,13 +83,14 @@ async def main() -> None:
]
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project,
AzureAIAgentClient(project_client=project) as client,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
AzureAIAgentClient(agents_client=agents_client) as client,
):
# This will enable tracing and configure the application to send telemetry data to the
# Application Insights instance attached to the Azure AI project.
# This will override any existing configuration.
await client.setup_azure_ai_observability()
await setup_azure_ai_observability(project_client)
with get_tracer().start_as_current_span(
name="Foundry Telemetry from Agent Framework", kind=SpanKind.CLIENT
@@ -0,0 +1,103 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from typing import Never
from agent_framework import (
AgentExecutorResponse,
Executor,
HostedCodeInterpreterTool,
WorkflowBuilder,
WorkflowContext,
handler,
)
from agent_framework.azure import AzureAIAgentClient
from azure.identity.aio import AzureCliCredential
"""
This sample demonstrates how to create a workflow that combines an AI agent executor
with a custom executor.
The workflow consists of two stages:
1. An AI agent with code interpreter capabilities that generates and executes Python code
2. An evaluator executor that reviews the agent's output and provides a final assessment
Key concepts demonstrated:
- Creating an AI agent with tool capabilities (HostedCodeInterpreterTool)
- Building workflows using WorkflowBuilder with an agent and a custom executor
- Using the @handler decorator in the executor to process AgentExecutorResponse from the agent
- Connecting workflow executors with edges to create a processing pipeline
- Yielding final outputs from terminal executors
- Non-streaming workflow execution and result collection
Prerequisites:
- Azure AI services configured with required environment variables
- Azure CLI authentication (run 'az login' before executing)
- Basic understanding of async Python and workflow concepts
"""
class Evaluator(Executor):
"""Custom executor that evaluates the output from an AI agent.
This executor demonstrates how to:
- Create a custom workflow executor that processes agent responses
- Use the @handler decorator to define the processing logic
- Access agent execution details including response text and usage metrics
- Yield final results to complete the workflow execution
The evaluator checks if the agent successfully generated the Fibonacci sequence
and provides feedback on correctness along with resource consumption details.
"""
@handler
async def handle(self, message: AgentExecutorResponse, ctx: WorkflowContext[Never, str]) -> None:
"""Evaluate the agent's response and complete the workflow with a final assessment.
This handler:
1. Receives the AgentExecutorResponse containing the agent's complete interaction
2. Checks if the expected Fibonacci sequence appears in the response text
3. Extracts usage details (token consumption, execution time, etc.)
4. Yields a final evaluation string to complete the workflow
Args:
message: The response from the Azure AI agent containing text and metadata
ctx: Workflow context for yielding the final output string
"""
target_text = "1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89"
correctness = target_text in message.agent_run_response.text
consumption = message.agent_run_response.usage_details
await ctx.yield_output(f"Correctness: {correctness}, Consumption: {consumption}")
async def main():
async with (
AzureCliCredential() as credential,
AzureAIAgentClient(async_credential=credential) as chat_client,
):
# Create an agent with code interpretation capabilities
agent = chat_client.create_agent(
name="CodingAgent",
instructions=("You are a helpful assistant that can write and execute Python code to solve problems."),
tools=HostedCodeInterpreterTool(),
)
# Build a workflow: Agent generates code -> Evaluator assesses results
# The agent will be wrapped in a special agent executor which produces AgentExecutorResponse
workflow = WorkflowBuilder().set_start_executor(agent).add_edge(agent, Evaluator(id="evaluator")).build()
# Execute the workflow with a specific coding task
results = await workflow.run(
"Generate the fibonacci numbers to 100 using python code, show the code and execute it."
)
# Extract and display the final evaluation
outputs = results.get_outputs()
if isinstance(outputs, list) and len(outputs) == 1:
print("Workflow results:", outputs[0])
else:
raise ValueError("Unexpected workflow outputs:", outputs)
if __name__ == "__main__":
asyncio.run(main())
@@ -4,7 +4,6 @@ import asyncio
from dataclasses import dataclass
from agent_framework import (
AgentExecutor, # Executor that runs the agent
AgentExecutorRequest, # Message bundle sent to an AgentExecutor
AgentExecutorResponse, # Result returned by an AgentExecutor
ChatMessage, # Chat message structure
@@ -148,6 +147,7 @@ async def main() -> None:
# response_format enforces that the model produces JSON compatible with GuessOutput.
chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
agent = chat_client.create_agent(
name="GuessingAgent",
instructions=(
"You guess a number between 1 and 10. "
"If the user says 'higher' or 'lower', adjust your next guess. "
@@ -158,16 +158,15 @@ async def main() -> None:
response_format=GuessOutput,
)
# Build a simple loop: TurnManager <-> AgentExecutor.
# TurnManager coordinates and gathers human replies while AgentExecutor runs the model.
turn_manager = TurnManager(id="turn_manager")
agent_exec = AgentExecutor(agent=agent, id="agent")
# Build a simple loop: TurnManager <-> AgentExecutor.
workflow = (
WorkflowBuilder()
.set_start_executor(turn_manager)
.add_edge(turn_manager, agent_exec) # Ask agent to make/adjust a guess
.add_edge(agent_exec, turn_manager) # Agent's response comes back to coordinator
.add_edge(turn_manager, agent) # Ask agent to make/adjust a guess
.add_edge(agent, turn_manager) # Agent's response comes back to coordinator
).build()
# Human in the loop run: alternate between invoking the workflow and supplying collected responses.
+17 -13
View File
@@ -87,7 +87,7 @@ wheels = [
[[package]]
name = "agent-framework"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { virtual = "." }
dependencies = [
{ name = "agent-framework-a2a", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -178,7 +178,7 @@ docs = [
[[package]]
name = "agent-framework-a2a"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { editable = "packages/a2a" }
dependencies = [
{ name = "a2a-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -193,7 +193,7 @@ requires-dist = [
[[package]]
name = "agent-framework-ag-ui"
version = "1.0.0b251105"
version = "1.0.0b251106"
source = { editable = "packages/ag-ui" }
dependencies = [
{ name = "ag-ui-protocol", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -223,7 +223,7 @@ provides-extras = ["dev"]
[[package]]
name = "agent-framework-anthropic"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { editable = "packages/anthropic" }
dependencies = [
{ name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -238,10 +238,11 @@ requires-dist = [
[[package]]
name = "agent-framework-azure-ai"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { editable = "packages/azure-ai" }
dependencies = [
{ name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "aiohttp", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "azure-ai-agents", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "azure-ai-projects", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
]
@@ -249,13 +250,14 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "agent-framework-core", editable = "packages/core" },
{ name = "aiohttp" },
{ name = "azure-ai-agents", specifier = "==1.2.0b5" },
{ name = "azure-ai-projects", specifier = ">=2.0.0a20251103001", index = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple" },
]
[[package]]
name = "agent-framework-chatkit"
version = "1.0.0b251001"
version = "1.0.0b251105"
source = { editable = "packages/chatkit" }
dependencies = [
{ name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -270,7 +272,7 @@ requires-dist = [
[[package]]
name = "agent-framework-copilotstudio"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { editable = "packages/copilotstudio" }
dependencies = [
{ name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -285,7 +287,7 @@ requires-dist = [
[[package]]
name = "agent-framework-core"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { editable = "packages/core" }
dependencies = [
{ name = "azure-identity", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -295,6 +297,7 @@ dependencies = [
{ name = "opentelemetry-exporter-otlp-proto-grpc", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "opentelemetry-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "opentelemetry-semantic-conventions-ai", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "pydantic-settings", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -329,6 +332,7 @@ requires-dist = [
{ name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.36.0" },
{ name = "opentelemetry-sdk", specifier = ">=1.24" },
{ name = "opentelemetry-semantic-conventions-ai", specifier = ">=0.4.13" },
{ name = "packaging", specifier = ">=24.1" },
{ name = "pydantic", specifier = ">=2,<3" },
{ name = "pydantic-settings", specifier = ">=2,<3" },
{ name = "typing-extensions" },
@@ -337,7 +341,7 @@ provides-extras = ["all"]
[[package]]
name = "agent-framework-devui"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { editable = "packages/devui" }
dependencies = [
{ name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -371,7 +375,7 @@ provides-extras = ["dev", "all"]
[[package]]
name = "agent-framework-lab"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { editable = "packages/lab" }
dependencies = [
{ name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -462,7 +466,7 @@ dev = [
[[package]]
name = "agent-framework-mem0"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { editable = "packages/mem0" }
dependencies = [
{ name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -477,7 +481,7 @@ requires-dist = [
[[package]]
name = "agent-framework-purview"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { editable = "packages/purview" }
dependencies = [
{ name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -494,7 +498,7 @@ requires-dist = [
[[package]]
name = "agent-framework-redis"
version = "1.0.0b251104"
version = "1.0.0b251105"
source = { editable = "packages/redis" }
dependencies = [
{ name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },