Skip to main content

Overview

The OpsHub AI Agent provides real-time assistance through a WebSocket connection. This enables:
  • Natural language query building
  • Interactive spreadsheet manipulation
  • Context-aware suggestions
  • Real-time validation feedback

WebSocket Endpoint

wss://opshub.nomark.ai/api/agent

Connection

Establish Connection

const ws = new WebSocket('wss://opshub.nomark.ai/api/agent', {
  headers: {
    'Authorization': `Bearer ${accessToken}`
  }
});

ws.onopen = () => {
  console.log('Connected to AI Agent');
  
  // Send initial message
  ws.send(JSON.stringify({
    type: 'initialize',
    data: {
      sessionId: 'session-uuid',
      context: {
        currentPage: 'visual-data-modeler',
        selectedTables: ['investment.portfolios', 'investment.holdings']
      }
    }
  }));
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  handleAgentMessage(message);
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = () => {
  console.log('Disconnected from AI Agent');
};

Message Types

Client → Agent Messages

Initialize Session

{
  "type": "initialize",
  "data": {
    "sessionId": "session-uuid",
    "context": {
      "currentPage": "visual-data-modeler",
      "selectedTables": ["investment.portfolios"],
      "workspaceContext": {
        "organizationId": "org-uuid",
        "teamId": "team-uuid",
        "userId": "user-uuid"
      }
    }
  }
}

User Message

{
  "type": "user_message",
  "data": {
    "sessionId": "session-uuid",
    "content": "Show me all portfolios with their current holdings",
    "context": {
      "selectedTables": ["investment.portfolios", "investment.holdings"],
      "filters": {}
    }
  }
}

Tool Result

{
  "type": "tool_result",
  "data": {
    "sessionId": "session-uuid",
    "toolCallId": "tool-call-uuid",
    "result": {
      "success": true,
      "data": [...]
    }
  }
}

Feedback

{
  "type": "feedback",
  "data": {
    "sessionId": "session-uuid",
    "messageId": "message-uuid",
    "rating": "positive",
    "comment": "This query worked perfectly!"
  }
}

Agent → Client Messages

Agent Response

{
  "type": "agent_message",
  "data": {
    "sessionId": "session-uuid",
    "messageId": "message-uuid",
    "content": "I'll help you query portfolios with their holdings...",
    "context": {}
  }
}

Tool Call

{
  "type": "tool_call",
  "data": {
    "sessionId": "session-uuid",
    "toolCallId": "tool-call-uuid",
    "tool": "query_database",
    "parameters": {
      "query": "SELECT p.*, h.* FROM investment.portfolios p JOIN investment.holdings h ON p.id = h.portfolio_id",
      "preview": true
    }
  }
}

Streaming Response

{
  "type": "streaming_chunk",
  "data": {
    "sessionId": "session-uuid",
    "messageId": "message-uuid",
    "chunk": "Here are the portfolios...",
    "isDone": false
  }
}

Error

{
  "type": "error",
  "data": {
    "sessionId": "session-uuid",
    "code": "QUERY_ERROR",
    "message": "Invalid SQL syntax",
    "details": {}
  }
}

Available Tools

The agent can invoke various tools to assist you:

Database Query Tool

{
  "tool": "query_database",
  "parameters": {
    "query": "SELECT * FROM investment.portfolios WHERE status = 'ACTIVE'",
    "preview": true,
    "limit": 100
  }
}

Visual Data Modeler Tool

{
  "tool": "data_modeler_control",
  "parameters": {
    "action": "add_table",
    "table": "investment.securities",
    "position": { "x": 100, "y": 200 }
  }
}

Formula Translation Tool

{
  "tool": "translate_formula",
  "parameters": {
    "formula": "=NAV_CALCULATE(A2, TODAY())",
    "targetLanguage": "sql"
  }
}

Smart Suggestions Tool

{
  "tool": "generate_suggestions",
  "parameters": {
    "context": {
      "currentTables": ["investment.portfolios"],
      "userIntent": "calculate nav"
    }
  }
}

Context Awareness

The agent maintains context throughout the conversation:
interface WorkspaceContext {
  organizationId: string;
  teamId: string;
  userId: string;
  currentPage: string;
  selectedTables?: string[];
  filters?: Record<string, any>;
  preferences?: {
    model?: 'gpt-4' | 'gpt-3.5-turbo' | 'claude-3-opus';
    temperature?: number;
  };
}

Rate Limits

WebSocket connections are subject to rate limits:
  • Max Connections: 5 per user
  • Messages/Minute: 60
  • Max Message Size: 1MB
  • Connection Duration: 4 hours (auto-reconnect)

Error Handling

Handle connection failures gracefully:
ws.onerror = (error) => {
  console.error('Connection error:', error);
  // Implement exponential backoff
  setTimeout(() => reconnect(), backoffDelay);
};
Validate messages before sending:
function validateMessage(message: any): boolean {
  if (!message.type || !message.data) {
    return false;
  }
  if (JSON.stringify(message).length > 1024 * 1024) {
    return false; // Exceeds 1MB limit
  }
  return true;
}
Implement ping/pong for connection health:
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ type: 'ping' }));
  }
}, 30000); // Every 30 seconds

Best Practices

Reconnection Logic

Implement exponential backoff for reconnections

Message Validation

Validate all messages before sending

Context Management

Keep context minimal and relevant

Error Logging

Log all errors for debugging

Complete Example

class AgentWebSocket {
  private ws: WebSocket | null = null;
  private sessionId: string;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 5;

  constructor(sessionId: string) {
    this.sessionId = sessionId;
  }

  connect(accessToken: string) {
    this.ws = new WebSocket('wss://opshub.nomark.ai/api/agent', {
      headers: { Authorization: `Bearer ${accessToken}` }
    });

    this.ws.onopen = () => {
      console.log('Connected');
      this.reconnectAttempts = 0;
      this.initialize();
    };

    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      this.handleMessage(message);
    };

    this.ws.onerror = (error) => {
      console.error('Error:', error);
    };

    this.ws.onclose = () => {
      console.log('Disconnected');
      this.reconnect(accessToken);
    };
  }

  private initialize() {
    this.send({
      type: 'initialize',
      data: {
        sessionId: this.sessionId,
        context: { currentPage: 'visual-data-modeler' }
      }
    });
  }

  private reconnect(accessToken: string) {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('Max reconnection attempts reached');
      return;
    }

    const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000);
    this.reconnectAttempts++;

    setTimeout(() => this.connect(accessToken), delay);
  }

  sendMessage(content: string, context?: any) {
    this.send({
      type: 'user_message',
      data: { sessionId: this.sessionId, content, context }
    });
  }

  private send(message: any) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(message));
    }
  }

  private handleMessage(message: any) {
    switch (message.type) {
      case 'agent_message':
        console.log('Agent:', message.data.content);
        break;
      case 'tool_call':
        console.log('Tool call:', message.data.tool);
        break;
      case 'error':
        console.error('Agent error:', message.data);
        break;
    }
  }

  disconnect() {
    this.ws?.close();
  }
}

// Usage
const agent = new AgentWebSocket('session-123');
agent.connect('your-access-token');
agent.sendMessage('Show me all portfolios');

Next Steps