Skip to content

Bi-directional Agent Communication#

This guide provides everything needed to integrate with the Eden API for bi-directional communication with AI agents from a frontend application. The Eden API uses a session-based communication model where you create sessions with specific agents and then exchange messages within those sessions.

Overview#

The Eden API enables real-time, bi-directional communication between your application and Eden's AI agents. This allows for:

  • Interactive Conversations: Back-and-forth dialogue with AI agents
  • Streaming Responses: Real-time message delivery as agents generate content
  • Multi-Agent Sessions: Communicate with multiple agents simultaneously
  • Persistent Context: Maintain conversation history and context across interactions

Authentication#

All API requests require authentication using an API key passed in the request headers:

X-Api-Key: your-api-key-here
Content-Type: application/json

Important

Never expose API keys in frontend code. Use environment variables or a backend proxy.

Core Integration Flow#

1. Create a Session#

Before sending messages, you must create a session with one or more agents.

Endpoint: POST https://api.eden.art/v2/sessions/create

{
  "agent_ids": ["agent-id-1", "agent-id-2"],
  "scenario": "Optional description of what you want to accomplish",
  "title": "Human-readable session title",
  "budget": {
    "manna_budget": 1000,      // Range: 100-50000 (computational resources)
    "token_budget": 10000,     // Range: 1000-1000000 (text generation)
    "turn_budget": 100         // Range: 1-1000 (conversation turns)
  },
  "autonomy_settings": {
    "auto_reply": true,        // Agents respond automatically
    "reply_interval": 5,       // Seconds between responses (0-3600)
    "actor_selection_method": "random"  // How agents are selected to respond
  }
}

Response:

{
  "session_id": "generated-session-id-string"
}

2. Send Messages to Agents#

Once you have a session ID, you can send messages to the agents.

Endpoint: POST https://api.eden.art/v2/sessions

{
  "session_id": "your-session-id",
  "content": "Your message to the agent(s)",
  "attachments": ["https://example.com/file1.png"],  // Optional file URLs
  "stream": true,              // Enable real-time streaming responses
  "thinking": true,            // Show agent's reasoning process
  "agent_ids": ["specific-agent-id"]  // Optional: target specific agents only
}

3. Handle Responses#

Responses vary based on whether you enable streaming:

  • Non-streaming: Complete response once the agent finishes processing
  • Streaming (stream: true): Real-time chunks as the agent generates content

Implementation Examples#

Basic Session Management#

class EdenAPIClient {
  constructor(apiKey, baseURL = 'https://api.eden.art') {
    this.apiKey = apiKey;
    this.baseURL = baseURL;
    this.headers = {
      'X-Api-Key': apiKey,
      'Content-Type': 'application/json'
    };
  }

  async createSession(agentIds, options = {}) {
    const payload = {
      agent_ids: agentIds,
      title: options.title || "Chat Session",
      scenario: options.scenario || "",
      budget: {
        manna_budget: options.mannaBudget || 1000,
        token_budget: options.tokenBudget || 10000,
        turn_budget: options.turnBudget || 100
      },
      autonomy_settings: {
        auto_reply: options.autoReply !== false,
        reply_interval: options.replyInterval || 2,
        actor_selection_method: options.actorSelection || "random"
      }
    };

    try {
      const response = await fetch(`${this.baseURL}/v2/sessions/create`, {
        method: 'POST',
        headers: this.headers,
        body: JSON.stringify(payload)
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const data = await response.json();
      return data.session_id;
    } catch (error) {
      console.error('Failed to create session:', error);
      throw error;
    }
  }

  async sendMessage(sessionId, message, options = {}) {
    const payload = {
      session_id: sessionId,
      content: message,
      stream: options.stream !== false,
      thinking: options.thinking !== false,
      attachments: options.attachments || [],
      agent_ids: options.targetAgents || []
    };

    try {
      const response = await fetch(`${this.baseURL}/v2/sessions`, {
        method: 'POST',
        headers: this.headers,
        body: JSON.stringify(payload)
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return response;
    } catch (error) {
      console.error('Failed to send message:', error);
      throw error;
    }
  }
}

Handling Streaming Responses#

async function handleStreamingResponse(response, onChunk, onComplete, onError) {
  try {
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let buffer = '';

    while (true) {
      const { done, value } = await reader.read();

      if (done) {
        if (buffer) {
          onChunk(buffer);
        }
        onComplete();
        break;
      }

      buffer += decoder.decode(value, { stream: true });

      // Process complete chunks (assuming newline-delimited)
      const lines = buffer.split('\n');
      buffer = lines.pop(); // Keep incomplete line in buffer

      for (const line of lines) {
        if (line.trim()) {
          try {
            // Try to parse as JSON if it's structured data
            const data = JSON.parse(line);
            onChunk(data);
          } catch {
            // Handle as plain text
            onChunk(line);
          }
        }
      }
    }
  } catch (error) {
    onError(error);
  }
}

// Usage example
async function sendStreamingMessage(client, sessionId, message) {
  const response = await client.sendMessage(sessionId, message, { stream: true });

  await handleStreamingResponse(
    response,
    (chunk) => {
      // Update UI with each chunk
      console.log('Received chunk:', chunk);
      updateChatDisplay(chunk);
    },
    () => {
      // Handle completion
      console.log('Response complete');
      markMessageComplete();
    },
    (error) => {
      // Handle errors
      console.error('Streaming error:', error);
      showErrorMessage(error);
    }
  );
}

React Hook Example#

import { useState, useCallback } from 'react';

export function useEdenAPI(apiKey) {
  const [client] = useState(() => new EdenAPIClient(apiKey));
  const [sessions, setSessions] = useState(new Map());
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const createSession = useCallback(async (agentIds, options) => {
    setLoading(true);
    setError(null);

    try {
      const sessionId = await client.createSession(agentIds, options);
      setSessions(prev => new Map(prev.set(sessionId, {
        id: sessionId,
        messages: [],
        agents: agentIds,
        createdAt: new Date()
      })));
      return sessionId;
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [client]);

  const sendMessage = useCallback(async (sessionId, message, options = {}) => {
    setLoading(true);
    setError(null);

    // Add user message to local state
    setSessions(prev => {
      const session = prev.get(sessionId);
      if (session) {
        session.messages.push({
          id: Date.now(),
          content: message,
          sender: 'user',
          timestamp: new Date()
        });
      }
      return new Map(prev);
    });

    try {
      const response = await client.sendMessage(sessionId, message, options);

      if (options.stream !== false) {
        // Handle streaming
        let responseContent = '';
        const messageId = Date.now() + 1;

        await handleStreamingResponse(
          response,
          (chunk) => {
            responseContent += typeof chunk === 'string' ? chunk : JSON.stringify(chunk);
            setSessions(prev => {
              const session = prev.get(sessionId);
              if (session) {
                const existingMessage = session.messages.find(m => m.id === messageId);
                if (existingMessage) {
                  existingMessage.content = responseContent;
                } else {
                  session.messages.push({
                    id: messageId,
                    content: responseContent,
                    sender: 'agent',
                    timestamp: new Date(),
                    streaming: true
                  });
                }
              }
              return new Map(prev);
            });
          },
          () => {
            setSessions(prev => {
              const session = prev.get(sessionId);
              if (session) {
                const message = session.messages.find(m => m.id === messageId);
                if (message) {
                  message.streaming = false;
                }
              }
              return new Map(prev);
            });
          },
          (err) => setError(err.message)
        );
      } else {
        // Handle non-streaming response
        const data = await response.json();
        setSessions(prev => {
          const session = prev.get(sessionId);
          if (session) {
            session.messages.push({
              id: Date.now() + 1,
              content: data.content || 'No response content',
              sender: 'agent',
              timestamp: new Date()
            });
          }
          return new Map(prev);
        });
      }
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [client]);

  return {
    createSession,
    sendMessage,
    sessions: Array.from(sessions.values()),
    loading,
    error,
    clearError: () => setError(null)
  };
}

Session Management#

Get Session Details#

async getSession(sessionId) {
  const response = await fetch(`${this.baseURL}/v2/sessions/${sessionId}`, {
    headers: this.headers
  });
  return response.json();
}

List All Sessions#

async listSessions() {
  const response = await fetch(`${this.baseURL}/v2/sessions`, {
    headers: this.headers
  });
  return response.json();
}

Delete Session#

async deleteSession(sessionId) {
  await fetch(`${this.baseURL}/v2/sessions/${sessionId}`, {
    method: 'DELETE',
    headers: this.headers
  });
}

Agent Management#

List Available Agents#

async listAgents() {
  const response = await fetch(`${this.baseURL}/v2/agents`, {
    headers: this.headers
  });
  return response.json();
}

Get Agent Details#

async getAgent(agentId) {
  const response = await fetch(`${this.baseURL}/v2/agents/${agentId}`, {
    headers: this.headers
  });
  return response.json();
}

Error Handling#

Always implement proper error handling:

async function handleEdenAPICall(apiCall) {
  try {
    return await apiCall();
  } catch (error) {
    if (error.response) {
      // API returned an error response
      const errorData = await error.response.json();
      console.error('API Error:', errorData.message);

      switch (error.response.status) {
        case 401:
          throw new Error('Invalid API key. Please check your authentication.');
        case 400:
          throw new Error(`Bad request: ${errorData.message}`);
        case 404:
          throw new Error('Resource not found. Check your session or agent IDs.');
        case 429:
          throw new Error('Rate limit exceeded. Please wait and try again.');
        default:
          throw new Error(`API error: ${errorData.message || 'Unknown error'}`);
      }
    } else {
      // Network or other error
      console.error('Network Error:', error);
      throw new Error('Failed to connect to Eden API. Please check your connection.');
    }
  }
}

Complete React Component Example#

import React, { useState, useEffect, useRef } from 'react';
import { useEdenAPI } from './useEdenAPI';

export function EdenChat({ apiKey, agentIds }) {
  const { createSession, sendMessage, sessions, loading, error } = useEdenAPI(apiKey);
  const [currentSession, setCurrentSession] = useState(null);
  const [messageInput, setMessageInput] = useState('');
  const messagesEndRef = useRef(null);

  useEffect(() => {
    // Create initial session
    const initSession = async () => {
      try {
        const sessionId = await createSession(agentIds, {
          title: 'New Chat',
          autoReply: true,
          replyInterval: 1
        });
        setCurrentSession(sessionId);
      } catch (err) {
        console.error('Failed to create session:', err);
      }
    };

    initSession();
  }, [createSession, agentIds]);

  useEffect(() => {
    // Auto-scroll to bottom
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [sessions]);

  const handleSendMessage = async (e) => {
    e.preventDefault();
    if (!messageInput.trim() || !currentSession || loading) return;

    const message = messageInput.trim();
    setMessageInput('');

    try {
      await sendMessage(currentSession, message, { stream: true });
    } catch (err) {
      console.error('Failed to send message:', err);
    }
  };

  const currentSessionData = sessions.find(s => s.id === currentSession);

  return (
    <div className="eden-chat">
      <div className="messages">
        {currentSessionData?.messages.map(msg => (
          <div key={msg.id} className={`message ${msg.sender}`}>
            <div className="content">
              {msg.content}
              {msg.streaming && <span className="typing-indicator">...</span>}
            </div>
            <div className="timestamp">
              {msg.timestamp.toLocaleTimeString()}
            </div>
          </div>
        ))}
        {error && (
          <div className="error-message">
            Error: {error}
          </div>
        )}
        <div ref={messagesEndRef} />
      </div>

      <form onSubmit={handleSendMessage} className="input-form">
        <input
          type="text"
          value={messageInput}
          onChange={(e) => setMessageInput(e.target.value)}
          placeholder="Type your message..."
          disabled={loading || !currentSession}
        />
        <button type="submit" disabled={loading || !currentSession || !messageInput.trim()}>
          {loading ? 'Sending...' : 'Send'}
        </button>
      </form>
    </div>
  );
}

Best Practices#

1. Budget Management#

Monitor your session budgets to prevent unexpected terminations: - Manna Budget: Computational resources for agent processing - Token Budget: Text generation limits - Turn Budget: Maximum conversation exchanges

2. Session Lifecycle#

  • Create sessions when starting new conversations
  • Reuse sessions for continued conversations
  • Clean up sessions when conversations end
  • Implement session persistence for user experience

3. Performance Optimization#

  • Use streaming for better perceived performance
  • Implement message caching for session history
  • Consider pagination for long conversation histories
  • Handle network failures gracefully

4. Security Considerations#

  • Never expose API keys in client-side code
  • Use environment variables or backend proxies
  • Implement proper input validation
  • Sanitize user inputs before sending to API

5. User Experience#

  • Show loading states during API calls
  • Display streaming responses in real-time
  • Implement retry mechanisms for failed requests
  • Provide clear error messages to users

Troubleshooting#

Common Issues#

  1. Authentication Errors (401)
  2. Verify API key is correct
  3. Check headers are properly set
  4. Ensure API key has necessary permissions

  5. Session Creation Failures

  6. Verify agent IDs exist
  7. Check budget values are within allowed ranges
  8. Ensure required fields are provided

  9. Streaming Issues

  10. Verify browser supports ReadableStream
  11. Check for network connectivity issues
  12. Implement fallback to non-streaming mode

  13. Rate Limiting

  14. Implement exponential backoff
  15. Add request queuing
  16. Monitor usage patterns

Next Steps#

This guide provides comprehensive coverage for implementing bi-directional communication with Eden's AI agents. Start with the basic examples and gradually add more advanced features as needed.