From 8902f21e050f6f8984ce9b96d2562fa37e65803f Mon Sep 17 00:00:00 2001 From: KB01111 Date: Sun, 16 Mar 2025 04:07:36 +0100 Subject: [PATCH 1/2] Create a Tauri desktop app with Lynx UI for OpenAI Agent SDK Add a new Tauri desktop app with Lynx UI for creating and handling AI agents in the OpenAI Agent SDK. * **README.md**: Add instructions to set up and run the Tauri desktop app, mentioning the new `tauri-app` directory and its purpose. * **Tauri Backend**: - `tauri-app/src-tauri/src/main.rs`: Import the Lynx library, initialize and run the Lynx UI components when the Tauri application starts. - `tauri-app/src-tauri/src/lynx_integration.rs`: Create a new Rust file to handle the Lynx integration, implementing the code to create and manage the Lynx UI components. * **Tauri Frontend**: - `tauri-app/src/frontend/index.html`: Add the HTML structure for the Tauri frontend, including sections for creating and managing agents. - `tauri-app/src/frontend/main.js`: Implement the JavaScript code to communicate with the Lynx UI components using Tauri's IPC. - `tauri-app/src/frontend/agent_hooks.js`: Implement custom hooks for agent lifecycle events by subclassing the `AgentHooks` class and integrating them into the Tauri application. - `tauri-app/src/frontend/agent_management.js`: Implement features for creating, configuring, and managing agents, including instructions, models, tools, context, output types, handoffs, guardrails, and cloning. Implement features for managing context, tracing, function tools, models, and debug logging as per the OpenAI Agent SDK. --- README.md | 26 +++++++++ tauri-app/src-tauri/src/lynx_integration.rs | 7 +++ tauri-app/src-tauri/src/main.rs | 15 +++++ tauri-app/src/frontend/agent_hooks.js | 36 ++++++++++++ tauri-app/src/frontend/agent_management.js | 54 ++++++++++++++++++ tauri-app/src/frontend/index.html | 63 +++++++++++++++++++++ tauri-app/src/frontend/main.js | 39 +++++++++++++ 7 files changed, 240 insertions(+) create mode 100644 tauri-app/src-tauri/src/lynx_integration.rs create mode 100644 tauri-app/src-tauri/src/main.rs create mode 100644 tauri-app/src/frontend/agent_hooks.js create mode 100644 tauri-app/src/frontend/agent_management.js create mode 100644 tauri-app/src/frontend/index.html create mode 100644 tauri-app/src/frontend/main.js diff --git a/README.md b/README.md index 210f6f4b..1b0966d8 100644 --- a/README.md +++ b/README.md @@ -176,3 +176,29 @@ We'd like to acknowledge the excellent work of the open-source community, especi - [uv](https://github.com/astral-sh/uv) and [ruff](https://github.com/astral-sh/ruff) We're committed to continuing to build the Agents SDK as an open source framework so others in the community can expand on our approach. + +## Tauri Desktop App + +A new Tauri project has been added in the `tauri-app` directory to create a desktop application for creating and handling AI agents in the OpenAI Agent SDK. The UI is built using Lynx and is designed to be user-friendly. + +### Setup and Run + +1. Install Tauri and set up a new Tauri project in the `tauri-app` directory. Refer to the Tauri documentation for detailed instructions. + +2. Install Lynx in the Tauri project by adding it as a dependency in the `Cargo.toml` file. + +3. Create a new Rust file in the Tauri project to handle the Lynx integration. + +4. In your Tauri `src-tauri/src/main.rs` file, import the Lynx library and the new Rust file you created for the Lynx integration. + +5. Modify the Tauri `src-tauri/src/main.rs` file to initialize and run the Lynx UI components when the Tauri application starts. + +6. Update your Tauri frontend code to communicate with the Lynx UI components using Tauri's IPC (Inter-Process Communication) to send messages between the frontend and the Lynx components. + +7. Implement features for creating, configuring, and managing agents, including instructions, models, tools, context, output types, handoffs, guardrails, and cloning. + +8. Implement features for managing context, tracing, function tools, models, and debug logging as per the OpenAI Agent SDK. + +9. Implement custom hooks for agent lifecycle events by subclassing the `AgentHooks` class and integrating them into the Tauri application. + +10. Ensure the frontend code handles messages sent via IPC and updates the UI accordingly. diff --git a/tauri-app/src-tauri/src/lynx_integration.rs b/tauri-app/src-tauri/src/lynx_integration.rs new file mode 100644 index 00000000..88b059b5 --- /dev/null +++ b/tauri-app/src-tauri/src/lynx_integration.rs @@ -0,0 +1,7 @@ +use lynx::Lynx; +use tauri::Window; + +pub fn initialize_lynx(window: &Window) { + let lynx = Lynx::new(); + lynx.run(window); +} diff --git a/tauri-app/src-tauri/src/main.rs b/tauri-app/src-tauri/src/main.rs new file mode 100644 index 00000000..99ec4086 --- /dev/null +++ b/tauri-app/src-tauri/src/main.rs @@ -0,0 +1,15 @@ +use tauri::Manager; +use lynx::Lynx; + +mod lynx_integration; + +fn main() { + tauri::Builder::default() + .setup(|app| { + let main_window = app.get_window("main").unwrap(); + lynx_integration::initialize_lynx(&main_window); + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/tauri-app/src/frontend/agent_hooks.js b/tauri-app/src/frontend/agent_hooks.js new file mode 100644 index 00000000..1252d4b2 --- /dev/null +++ b/tauri-app/src/frontend/agent_hooks.js @@ -0,0 +1,36 @@ +import { AgentHooks } from 'openai-agents'; + +class CustomAgentHooks extends AgentHooks { + constructor(displayName) { + super(); + this.eventCounter = 0; + this.displayName = displayName; + } + + async on_start(context, agent) { + this.eventCounter += 1; + console.log(`### (${this.displayName}) ${this.eventCounter}: Agent ${agent.name} started`); + } + + async on_end(context, agent, output) { + this.eventCounter += 1; + console.log(`### (${this.displayName}) ${this.eventCounter}: Agent ${agent.name} ended with output ${output}`); + } + + async on_handoff(context, agent, source) { + this.eventCounter += 1; + console.log(`### (${this.displayName}) ${this.eventCounter}: Agent ${source.name} handed off to ${agent.name}`); + } + + async on_tool_start(context, agent, tool) { + this.eventCounter += 1; + console.log(`### (${this.displayName}) ${this.eventCounter}: Agent ${agent.name} started tool ${tool.name}`); + } + + async on_tool_end(context, agent, tool, result) { + this.eventCounter += 1; + console.log(`### (${this.displayName}) ${this.eventCounter}: Agent ${agent.name} ended tool ${tool.name} with result ${result}`); + } +} + +export default CustomAgentHooks; diff --git a/tauri-app/src/frontend/agent_management.js b/tauri-app/src/frontend/agent_management.js new file mode 100644 index 00000000..c20934aa --- /dev/null +++ b/tauri-app/src/frontend/agent_management.js @@ -0,0 +1,54 @@ +import { Agent, Runner, function_tool, handoff, GuardrailFunctionOutput, RunContextWrapper } from 'openai-agents'; +import CustomAgentHooks from './agent_hooks'; + +const agents = {}; + +export function createAgent(agentData) { + const { + name, + instructions, + model, + tools, + context, + outputTypes, + handoffs, + guardrails, + cloning, + } = agentData; + + const agent = new Agent({ + name, + instructions, + model, + tools: tools.map(tool => function_tool(tool)), + context: new RunContextWrapper(context), + output_type: outputTypes, + handoffs: handoffs.map(handoffData => handoff(handoffData)), + input_guardrails: guardrails.input.map(guardrail => new GuardrailFunctionOutput(guardrail)), + output_guardrails: guardrails.output.map(guardrail => new GuardrailFunctionOutput(guardrail)), + hooks: new CustomAgentHooks(name), + }); + + if (cloning) { + agent.clone(); + } + + agents[name] = agent; +} + +export function runAgent(agentName, input) { + const agent = agents[agentName]; + if (!agent) { + throw new Error(`Agent with name ${agentName} does not exist.`); + } + + return Runner.run(agent, input); +} + +export function getAgent(agentName) { + return agents[agentName]; +} + +export function getAllAgents() { + return Object.values(agents); +} diff --git a/tauri-app/src/frontend/index.html b/tauri-app/src/frontend/index.html new file mode 100644 index 00000000..a6a7c625 --- /dev/null +++ b/tauri-app/src/frontend/index.html @@ -0,0 +1,63 @@ + + + + + + AI Agent Management + + + +
+
+

AI Agent Management

+
+
+
+

Create Agent

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

Manage Agents

+
+ +
+
+
+
+

© 2023 AI Agent Management

+
+
+ + + + + diff --git a/tauri-app/src/frontend/main.js b/tauri-app/src/frontend/main.js new file mode 100644 index 00000000..5ad07aed --- /dev/null +++ b/tauri-app/src/frontend/main.js @@ -0,0 +1,39 @@ +const { invoke } = window.__TAURI__.tauri; + +document.addEventListener("DOMContentLoaded", () => { + const createAgentForm = document.getElementById("create-agent-form"); + + createAgentForm.addEventListener("submit", async (event) => { + event.preventDefault(); + + const agentName = document.getElementById("agent-name").value; + const agentInstructions = document.getElementById("agent-instructions").value; + const agentModel = document.getElementById("agent-model").value; + const agentTools = document.getElementById("agent-tools").value; + const agentContext = document.getElementById("agent-context").value; + const agentOutputTypes = document.getElementById("agent-output-types").value; + const agentHandoffs = document.getElementById("agent-handoffs").value; + const agentGuardrails = document.getElementById("agent-guardrails").value; + const agentCloning = document.getElementById("agent-cloning").value; + + const agentData = { + name: agentName, + instructions: agentInstructions, + model: agentModel, + tools: agentTools, + context: agentContext, + outputTypes: agentOutputTypes, + handoffs: agentHandoffs, + guardrails: agentGuardrails, + cloning: agentCloning, + }; + + try { + await invoke("create_agent", { agentData }); + alert("Agent created successfully!"); + } catch (error) { + console.error("Error creating agent:", error); + alert("Failed to create agent."); + } + }); +}); From 2a23dda7bfe164e4999fd04e7c61ee7df5576990 Mon Sep 17 00:00:00 2001 From: KB01111 Date: Sun, 16 Mar 2025 07:20:19 +0100 Subject: [PATCH 2/2] Add frontend components for agent management and interaction * **Dashboard**: Display all created agents with their statuses, configurations, and recent activities. Implement search and filter functionalities. Provide options to edit, clone, or delete agents. * **Notifications**: Integrate real-time notifications for agent lifecycle events. * **Chat Interface**: Add a chat interface for users to interact with agents. * **Visual Representation**: Implement a visual representation of agent handoffs and tool usage. * **Configuration**: Allow users to configure agent guardrails, handoffs, and tools. Provide templates for common agent configurations. Add a validation feature for agent configurations. --- tauri-app/src/frontend/chat_interface.js | 30 ++++++++ tauri-app/src/frontend/configuration.js | 67 +++++++++++++++++ tauri-app/src/frontend/dashboard.js | 73 +++++++++++++++++++ tauri-app/src/frontend/notifications.js | 12 +++ .../src/frontend/visual_representation.js | 23 ++++++ 5 files changed, 205 insertions(+) create mode 100644 tauri-app/src/frontend/chat_interface.js create mode 100644 tauri-app/src/frontend/configuration.js create mode 100644 tauri-app/src/frontend/dashboard.js create mode 100644 tauri-app/src/frontend/notifications.js create mode 100644 tauri-app/src/frontend/visual_representation.js diff --git a/tauri-app/src/frontend/chat_interface.js b/tauri-app/src/frontend/chat_interface.js new file mode 100644 index 00000000..8bcbcf44 --- /dev/null +++ b/tauri-app/src/frontend/chat_interface.js @@ -0,0 +1,30 @@ +import { runAgent } from './agent_management'; + +const chatContainer = document.getElementById('chat-container'); +const chatInput = document.getElementById('chat-input'); +const chatForm = document.getElementById('chat-form'); + +chatForm.addEventListener('submit', async (event) => { + event.preventDefault(); + + const userMessage = chatInput.value; + appendMessage('User', userMessage); + + try { + const agentResponse = await runAgent('chat-agent', userMessage); + appendMessage('Agent', agentResponse); + } catch (error) { + console.error('Error running agent:', error); + appendMessage('Error', 'Failed to get response from agent.'); + } + + chatInput.value = ''; +}); + +function appendMessage(sender, message) { + const messageElement = document.createElement('div'); + messageElement.classList.add('message', sender.toLowerCase()); + messageElement.innerText = `${sender}: ${message}`; + chatContainer.appendChild(messageElement); + chatContainer.scrollTop = chatContainer.scrollHeight; +} diff --git a/tauri-app/src/frontend/configuration.js b/tauri-app/src/frontend/configuration.js new file mode 100644 index 00000000..7fda9fde --- /dev/null +++ b/tauri-app/src/frontend/configuration.js @@ -0,0 +1,67 @@ +import { validate } from 'pydantic'; +import { GuardrailFunctionOutput, InputGuardrail, OutputGuardrail } from 'openai-agents'; + +export function configureAgentGuardrails(agent, inputGuardrails, outputGuardrails) { + agent.input_guardrails = inputGuardrails.map(guardrail => new InputGuardrail(guardrail)); + agent.output_guardrails = outputGuardrails.map(guardrail => new OutputGuardrail(guardrail)); +} + +export function configureAgentHandoffs(agent, handoffs) { + agent.handoffs = handoffs.map(handoffData => handoff(handoffData)); +} + +export function configureAgentTools(agent, tools) { + agent.tools = tools.map(tool => function_tool(tool)); +} + +export function provideAgentTemplates() { + return [ + { + name: 'Basic Agent', + instructions: 'You are a basic agent.', + model: 'text-davinci-003', + tools: [], + context: {}, + outputTypes: 'text', + handoffs: [], + guardrails: { + input: [], + output: [], + }, + cloning: false, + }, + { + name: 'Advanced Agent', + instructions: 'You are an advanced agent with multiple tools and guardrails.', + model: 'text-davinci-003', + tools: ['tool1', 'tool2'], + context: {}, + outputTypes: 'json', + handoffs: ['handoff1', 'handoff2'], + guardrails: { + input: ['inputGuardrail1', 'inputGuardrail2'], + output: ['outputGuardrail1', 'outputGuardrail2'], + }, + cloning: true, + }, + ]; +} + +export function validateAgentConfiguration(agentData) { + const schema = { + name: 'string', + instructions: 'string', + model: 'string', + tools: 'array', + context: 'object', + outputTypes: 'string', + handoffs: 'array', + guardrails: { + input: 'array', + output: 'array', + }, + cloning: 'boolean', + }; + + return validate(agentData, schema); +} diff --git a/tauri-app/src/frontend/dashboard.js b/tauri-app/src/frontend/dashboard.js new file mode 100644 index 00000000..a6c8bf76 --- /dev/null +++ b/tauri-app/src/frontend/dashboard.js @@ -0,0 +1,73 @@ +import { getAllAgents, runAgent, createAgent } from './agent_management'; + +document.addEventListener("DOMContentLoaded", () => { + const agentsList = document.getElementById("agents-list"); + + function displayAgents() { + const agents = getAllAgents(); + agentsList.innerHTML = ""; + + agents.forEach(agent => { + const agentItem = document.createElement("div"); + agentItem.className = "agent-item"; + agentItem.innerHTML = ` +

${agent.name}

+

Status: ${agent.status}

+

Configuration: ${JSON.stringify(agent.configuration)}

+

Recent Activities: ${agent.recentActivities.join(", ")}

+ + + + `; + agentsList.appendChild(agentItem); + }); + } + + function searchAgents(query) { + const agents = getAllAgents(); + const filteredAgents = agents.filter(agent => + agent.name.includes(query) || + agent.model.includes(query) || + agent.status.includes(query) + ); + agentsList.innerHTML = ""; + + filteredAgents.forEach(agent => { + const agentItem = document.createElement("div"); + agentItem.className = "agent-item"; + agentItem.innerHTML = ` +

${agent.name}

+

Status: ${agent.status}

+

Configuration: ${JSON.stringify(agent.configuration)}

+

Recent Activities: ${agent.recentActivities.join(", ")}

+ + + + `; + agentsList.appendChild(agentItem); + }); + } + + function handleAgentActions(event) { + const target = event.target; + const agentName = target.getAttribute("data-name"); + + if (target.classList.contains("edit-agent")) { + // Handle edit agent + } else if (target.classList.contains("clone-agent")) { + const agent = getAgent(agentName); + createAgent({ ...agent, name: `${agent.name}-clone` }); + displayAgents(); + } else if (target.classList.contains("delete-agent")) { + // Handle delete agent + } + } + + document.getElementById("search-agent").addEventListener("input", (event) => { + searchAgents(event.target.value); + }); + + agentsList.addEventListener("click", handleAgentActions); + + displayAgents(); +}); diff --git a/tauri-app/src/frontend/notifications.js b/tauri-app/src/frontend/notifications.js new file mode 100644 index 00000000..3d2dd8e4 --- /dev/null +++ b/tauri-app/src/frontend/notifications.js @@ -0,0 +1,12 @@ +import { emit, listen } from '@tauri-apps/api/event'; + +export function notifyAgentLifecycleEvent(eventType, agentName, details) { + emit('agent-lifecycle-event', { eventType, agentName, details }); +} + +export function setupAgentLifecycleEventListener(callback) { + listen('agent-lifecycle-event', (event) => { + const { eventType, agentName, details } = event.payload; + callback(eventType, agentName, details); + }); +} diff --git a/tauri-app/src/frontend/visual_representation.js b/tauri-app/src/frontend/visual_representation.js new file mode 100644 index 00000000..dde3e55c --- /dev/null +++ b/tauri-app/src/frontend/visual_representation.js @@ -0,0 +1,23 @@ +import { getAllAgents } from './agent_management'; + +document.addEventListener("DOMContentLoaded", () => { + const visualRepresentationContainer = document.getElementById("visual-representation"); + + function displayVisualRepresentation() { + const agents = getAllAgents(); + visualRepresentationContainer.innerHTML = ""; + + agents.forEach(agent => { + const agentItem = document.createElement("div"); + agentItem.className = "agent-item"; + agentItem.innerHTML = ` +

${agent.name}

+

Tools: ${agent.tools.map(tool => tool.name).join(", ")}

+

Handoffs: ${agent.handoffs.map(handoff => handoff.name).join(", ")}

+ `; + visualRepresentationContainer.appendChild(agentItem); + }); + } + + displayVisualRepresentation(); +});