Skip to content

Latest commit

 

History

History
1592 lines (1251 loc) · 36.8 KB

File metadata and controls

1592 lines (1251 loc) · 36.8 KB

Snow CLI Usage Documentation - SSE Service Mode

Welcome to Snow CLI! Agentic coding in your terminal.

Quick Start

Want to quickly experience the SSE client? We provide a complete browser test client:

Location: source/test/sse-client/index.html

Simply open this file in your browser and connect to the SSE server to start testing.

What is SSE Service Mode

SSE (Server-Sent Events) service mode allows you to run Snow CLI as a backend service, providing AI capabilities to external applications. It is perfect for:

  • Web application integration
  • Mobile app backend
  • Third-party tool integration
  • Microservice architecture
  • Custom chat interfaces

Basic Usage

Starting SSE Server

Basic Startup

# Use default port 3000 (foreground)
snow --sse

# Specify port
snow --sse --sse-port 8080

# Specify working directory
snow --sse --work-dir /path/to/project

# Custom interaction timeout (default 300000ms, i.e., 5 minutes)
snow --sse --sse-timeout 600000  # Set to 10 minutes

# Combined usage
snow --sse --sse-port 8080 --work-dir /path/to/project --sse-timeout 600000

Background Daemon Mode

If you don't want to occupy the terminal, you can run the server as a background daemon:

# Start background daemon (default port 3000)
snow --sse-daemon

# Specify different ports (supports multiple instances)
snow --sse-daemon --sse-port 3000
snow --sse-daemon --sse-port 8080
snow --sse-daemon --sse-port 9000

# Specify full parameters
snow --sse-daemon --sse-port 8080 --work-dir /path/to/project --sse-timeout 600000

# Check all daemon status
snow --sse-status

# Stop daemon (three methods)
snow --sse-stop                    # Stop default port 3000 daemon
snow --sse-stop --sse-port 8080    # Stop by port number
snow --sse-stop 12345              # Stop by PID

Daemon features:

  • Supports multiple instances (different ports)
  • Runs in background, doesn't occupy terminal
  • Individual log file per port: ~/.snow/sse-logs/port-<port>.log
  • Individual PID file per port: ~/.snow/sse-daemons/port-<port>.pid
  • Supports stop by port or PID
  • Status check shows all running daemons

Enabling YOLO Mode on Startup

While the SSE server itself doesn't use the --yolo parameter, you can achieve similar functionality through the following methods:

Method 1: Client message with yoloMode

This is the recommended approach, allowing flexible control over whether each request uses YOLO mode:

// Specify YOLO mode when sending message
await fetch('http://localhost:3000/message', {
 method: 'POST',
 headers: {'Content-Type': 'application/json'},
 body: JSON.stringify({
  type: 'chat',
  content: 'Your question',
  yoloMode: true, // Enable YOLO mode
 }),
});

Method 2: Configure auto-approval list

Add commonly used tools to the project's permission configuration file for default auto-approval:

# Edit project permission configuration
vi .snow/permissions.json
{
 "alwaysApprovedTools": [
  "filesystem-read",
  "filesystem-edit_search",
  "filesystem-create",
  "codebase-search",
  "ace-semantic_search",
  "notebook-add"
 ]
}

This way, tools in the list will be automatically approved without requiring confirmation each time.

Notes:

  • SSE server startup doesn't support --yolo parameter
  • YOLO mode needs to be enabled via client message's yoloMode field
  • Or implement tool auto-approval via .snow/permissions.json configuration
  • Sensitive commands require confirmation even in YOLO mode

Server Information

After startup, the terminal will display a beautiful server status interface:

✓ SSE server started
Port: 3000 | Working Directory: /Users/xxx/project | ● Running

Available Endpoints:
  http://localhost:3000/events
  POST http://localhost:3000/message
  POST http://localhost:3000/session/create
  POST http://localhost:3000/session/load
  GET http://localhost:3000/session/list
  GET http://localhost:3000/session/rollback-points?sessionId={sessionId}
  DELETE http://localhost:3000/session/{sessionId}
  GET http://localhost:3000/health

Running Logs:
[14:30:45] SSE service started on port 3000
[14:30:50] Created new session: abc-123

Press Ctrl+C to stop server

API Endpoints

1. SSE Event Stream Connection

Endpoint: GET /events

Establish SSE connection to receive real-time event stream.

JavaScript Example

const eventSource = new EventSource('http://localhost:3000/events');

eventSource.onmessage = event => {
 const data = JSON.parse(event.data);
 console.log('Received event:', data);

 switch (data.type) {
  case 'connected':
   console.log(
    'Connection successful, connection ID:',
    data.data.connectionId,
   );
   break;

  case 'message':
   if (data.data.streaming) {
    console.log('AI is responding:', data.data.content);
   } else if (data.data.role === 'user') {
    console.log('User message:', data.data.content);
   }
   break;

  case 'tool_confirmation_request':
   // User confirmation needed for tool execution
   handleToolConfirmation(data);
   break;

  case 'complete':
   console.log('Conversation complete');
   break;
 }
};

2. Send Message

Endpoint: POST /message

Content-Type: application/json

Send Plain Text Message

async function sendMessage(content) {
 const response = await fetch('http://localhost:3000/message', {
  method: 'POST',
  headers: {
   'Content-Type': 'application/json',
  },
  body: JSON.stringify({
   type: 'chat',
   content: content,
  }),
 });

 return await response.json();
}

// Usage example
await sendMessage('Help me create a React component');

Continuous Conversation with Session

async function continueConversation(content, sessionId) {
 const response = await fetch('http://localhost:3000/message', {
  method: 'POST',
  headers: {
   'Content-Type': 'application/json',
  },
  body: JSON.stringify({
   type: 'chat',
   content: content,
   sessionId: sessionId, // Use session ID to continue conversation
  }),
 });

 return await response.json();
}

// Session ID will be returned in complete event
eventSource.onmessage = event => {
 const data = JSON.parse(event.data);
 if (data.type === 'complete') {
  const sessionId = data.data.sessionId;
  console.log('Session ID:', sessionId);
 }
};

Send Image Message

async function sendImageMessage(content, imageFile) {
 // Convert image to Base64 Data URI
 const reader = new FileReader();
 const imageData = await new Promise((resolve, reject) => {
  reader.onload = e => resolve(e.target.result);
  reader.onerror = reject;
  reader.readAsDataURL(imageFile);
 });

 const response = await fetch('http://localhost:3000/message', {
  method: 'POST',
  headers: {
   'Content-Type': 'application/json',
  },
  body: JSON.stringify({
   type: 'chat',
   content: content || 'Please analyze this image',
   images: [
    {
     data: imageData, // Complete data URI, e.g., data:image/png;base64,iVBORw0KG...
     mimeType: imageFile.type, // e.g., image/png, image/jpeg
    },
   ],
  }),
 });

 return await response.json();
}

// Usage example
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async e => {
 const file = e.target.files[0];
 if (file && file.type.startsWith('image/')) {
  await sendImageMessage('What is this?', file);
 }
});

Abort Running Task

async function abortTask(sessionId) {
 const response = await fetch('http://localhost:3000/message', {
  method: 'POST',
  headers: {
   'Content-Type': 'application/json',
  },
  body: JSON.stringify({
   type: 'abort',
   sessionId: sessionId,
  }),
 });

 return await response.json();
}

// Listen for abort confirmation
eventSource.onmessage = event => {
 const data = JSON.parse(event.data);
 if (data.type === 'complete' && data.data.cancelled) {
  console.log('Task has been aborted by user');
 }
};

Enable YOLO Mode

async function sendWithYolo(content) {
 const response = await fetch('http://localhost:3000/message', {
  method: 'POST',
  headers: {
   'Content-Type': 'application/json',
  },
  body: JSON.stringify({
   type: 'chat',
   content: content,
   yoloMode: true, // Auto-approve all non-sensitive tools
  }),
 });

 return await response.json();
}

3. Session Management

Create New Session

Endpoint: POST /session/create

Content-Type: application/json

Create a new conversation session, returns session information and automatically binds to current connection.

async function createSession() {
 const response = await fetch('http://localhost:3000/session/create', {
  method: 'POST',
  headers: {
   'Content-Type': 'application/json',
  },
  body: JSON.stringify({
   connectionId: 'conn_xxx', // Optional, specify connection ID
  }),
 });

 const data = await response.json();
 console.log('Session ID:', data.session.id);
 console.log('Created at:', data.session.createdAt);
 return data.session;
}

Load Existing Session

Endpoint: POST /session/load

Content-Type: application/json

Load a saved session to restore conversation context.

async function loadSession(sessionId) {
 const response = await fetch('http://localhost:3000/session/load', {
  method: 'POST',
  headers: {
   'Content-Type': 'application/json',
  },
  body: JSON.stringify({
   sessionId: sessionId,
   connectionId: 'conn_xxx', // Optional, specify connection ID
  }),
 });

 const data = await response.json();
 if (data.success) {
  console.log('Session loaded:', data.session.id);
  console.log('Message count:', data.session.messages.length);
  return data.session;
 } else {
  console.error('Load failed:', data.error);
 }
}

Get Session List

Endpoint: GET /session/list

Query Parameters:

  • page: Page number, starting from 0 (optional, default 0)
  • pageSize: Items per page (optional, default 20, max 200)
  • q: Search keyword (optional, searches message content in sessions)

Get all saved sessions with pagination and search support.

async function listSessions(page = 0, pageSize = 20, searchQuery = '') {
 const params = new URLSearchParams({
  page: page.toString(),
  pageSize: pageSize.toString(),
 });

 if (searchQuery) {
  params.append('q', searchQuery);
 }

 const response = await fetch(`http://localhost:3000/session/list?${params}`);
 const data = await response.json();

 console.log('Total sessions:', data.total);
 console.log('Current page:', data.page);
 console.log('Page size:', data.pageSize);
 console.log('Session list:', data.sessions);

 // Session list example
 // data.sessions = [
 //   {
 //     id: 'abc-123',
 //     createdAt: '2025-12-30T10:00:00.000Z',
 //     updatedAt: '2025-12-30T10:30:00.000Z',
 //     messageCount: 10,
 //     firstMessage: 'Help me create a function'
 //   },
 //   ...
 // ]

 return data;
}

Get Rollback Points

Endpoint: GET /session/rollback-points

Query Parameters:

  • sessionId: Session ID (required)

Returns a list of user messages in the specified session that can be used as rollback points (demo use).

async function getRollbackPoints(sessionId) {
 const params = new URLSearchParams({sessionId});
 const response = await fetch(
  `http://localhost:3000/session/rollback-points?${params.toString()}`,
 );
 const data = await response.json();

 // Example (key fields):
 // {
 //   success: true,
 //   sessionId: 'abc-123',
 //   points: [
 //     {
 //       messageIndex: 0,
 //       role: 'user',
 //       timestamp: 1730000000000,
 //       summary: '...',
 //       hasSnapshot: true,
 //       snapshot: {timestamp: 1730000000000, fileCount: 12},
 //       filesToRollbackCount: 5
 //     }
 //   ]
 // }
 return data;
}

Delete Session

Endpoint: DELETE /session/{sessionId}

Delete the specified session and all its data.

async function deleteSession(sessionId) {
 const response = await fetch(`http://localhost:3000/session/${sessionId}`, {
  method: 'DELETE',
 });

 const data = await response.json();
 if (data.success) {
  console.log('Session deleted:', data.deleted);
 }
 return data;
}

4. Health Check

Endpoint: GET /health

Check server status and current connection count.

async function checkHealth() {
 const response = await fetch('http://localhost:3000/health');
 const data = await response.json();
 console.log('Status:', data.status);
 console.log('Connections:', data.connections);
}

Event Type Descriptions

connected

Connection successful event.

{
  type: 'connected',
  data: {
    connectionId: 'conn_1234567890'
  },
  timestamp: '2025-12-30T15:30:00.000Z'
}

message

Message event (user or AI).

// User message
{
  type: 'message',
  data: {
    role: 'user',
    content: 'Help me create a function'
  }
}

// AI streaming response
{
  type: 'message',
  data: {
    role: 'assistant',
    content: 'Sure, let me help you...',
    streaming: true
  }
}

// AI final response
{
  type: 'message',
  data: {
    role: 'assistant',
    content: 'Complete response content',
    streaming: false
  }
}

tool_call

Tool invocation event.

{
  type: 'tool_call',
  data: {
    name: 'filesystem-create',
    arguments: {
      filePath: 'example.js',
      content: '...'
    }
  }
}

tool_confirmation_request

Request confirmation for tool execution.

{
  type: 'tool_confirmation_request',
  data: {
    toolCall: {
      function: {
        name: 'terminal-execute',
        arguments: '{"command":"rm -rf node_modules"}'
      }
    },
    isSensitive: true,  // Whether it's a sensitive command
    sensitiveInfo: {
      pattern: 'rm -rf',
      description: 'Delete files or directories'
    },
    availableOptions: [
      {value: 'approve', label: 'Approve once'},
      {value: 'approve_always', label: 'Always approve'},  // Only for non-sensitive commands
      {value: 'reject_with_reply', label: 'Reject with reply'},
      {value: 'reject', label: 'Reject and end session'}
    ]
  },
  requestId: 'req_1234567890'
}

tool_result

Tool execution result.

{
  type: 'tool_result',
  data: {
    content: 'Execution successful',
    status: 'success'
  }
}

user_question_request

AI asking user a question.

{
  type: 'user_question_request',
  data: {
    question: 'Please select an option',
    options: ['Option 1', 'Option 2', 'Option 3'],
    multiSelect: false
  },
  requestId: 'req_1234567890'
}

usage

Token usage information.

{
  type: 'usage',
  data: {
    input_tokens: 150,
    output_tokens: 200
  }
}

error

Error message.

{
  type: 'error',
  data: {
    message: 'Error description',
    stack: 'Error stack (optional)'
  }
}

complete

Conversation completed.

{
  type: 'complete',
  data: {
    usage: {
      input_tokens: 150,
      output_tokens: 200
    },
    tokenCount: 350,
    sessionId: 'abc-123-def-456',  // Session ID
    cancelled: false  // Whether cancelled by user (optional)
  }
}

abort

Task abort request (sent by client).

// Client sends abort request
await fetch('http://localhost:3000/message', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
    type: 'abort',
    sessionId: 'abc-123-def-456'
  })
});

// Server responds with abort confirmation
{
  type: 'message',
  data: {
    role: 'assistant',
    content: 'Task has been aborted'
  },
  timestamp: '2025-12-30T15:30:00.000Z'
}

// Followed by complete event
{
  type: 'complete',
  data: {
    usage: {input_tokens: 0, output_tokens: 0},
    tokenCount: 0,
    sessionId: 'abc-123-def-456',
    cancelled: true
  }
}

Tool Confirmation Flow

Confirmation Request Response

When receiving a tool_confirmation_request event, send a confirmation response:

async function handleToolConfirmation(event) {
 const toolCall = event.data.toolCall;
 const options = event.data.availableOptions;

 // Display tool information to user
 console.log('Tool:', toolCall.function.name);
 console.log('Arguments:', toolCall.function.arguments);
 console.log('Available options:', options);

 // Send response after user selection
 const response = await fetch('http://localhost:3000/message', {
  method: 'POST',
  headers: {
   'Content-Type': 'application/json',
  },
  body: JSON.stringify({
   type: 'tool_confirmation_response',
   requestId: event.requestId,
   response: 'approve', // or 'approve_always', 'reject', {type: 'reject_with_reply', reason: '...'}
  }),
 });

 return await response.json();
}

Confirmation Options Explained

Option Value Description Applicable Scenario
Approve once 'approve' Approve this execution only All tools
Always approve 'approve_always' Approve and add to auto-approval list Non-sensitive only
Reject with reply {type: 'reject_with_reply', reason: '...'} Reject and tell AI the reason All tools
Reject and end 'reject' Reject and end session All tools

Sensitive Command Detection

The system automatically detects sensitive commands (like rm -rf, sudo, etc.). Sensitive commands:

  • Will not show "Always approve" option
  • Require confirmation even in YOLO mode
  • Display warning information and matched command pattern

For sensitive command configuration, refer to: Sensitive Commands Configuration

User Question Response

When receiving a user_question_request event:

async function handleUserQuestion(event) {
 const question = event.data.question;
 const options = event.data.options;
 const multiSelect = event.data.multiSelect;

 // Display question and options to user
 console.log('Question:', question);
 console.log('Options:', options);

 // Send response after user selection
 const response = await fetch('http://localhost:3000/message', {
  method: 'POST',
  headers: {
   'Content-Type': 'application/json',
  },
  body: JSON.stringify({
   type: 'user_question_response',
   requestId: event.requestId,
   response: {
    selected: multiSelect ? ['Option 1', 'Option 2'] : 'Option 1',
    customInput: '', // Optional custom input
   },
  }),
 });

 return await response.json();
}

Permission Configuration

Auto-Approval List

SSE server automatically reads permission configuration file from project root directory:

Location: .snow/permissions.json

{
 "alwaysApprovedTools": [
  "filesystem-read",
  "codebase-search",
  "filesystem-edit_search",
  "notebook-add",
  "filesystem-create"
 ]
}

Permission Inheritance Rules

  1. Project-level Configuration: Server reads .snow/permissions.json from working directory on startup
  2. Auto-approval: Tools in the list are executed automatically without user confirmation
  3. Sensitive Commands Priority: Sensitive commands require confirmation even if in auto-approval list
  4. Dynamic Updates: When user selects "Always approve", the tool is automatically added to configuration file

Configuration Example

{
 "alwaysApprovedTools": [
  "filesystem-read", // Read files
  "filesystem-edit_search", // Search and replace edit
  "filesystem-create", // Create files
  "codebase-search", // Code search
  "ace-semantic_search", // Semantic search
  "ace-find_definition", // Find definition
  "notebook-add" // Add note
 ]
}

YOLO Mode

Enable YOLO Mode

Carry yoloMode parameter when sending message:

const response = await fetch('http://localhost:3000/message', {
 method: 'POST',
 headers: {'Content-Type': 'application/json'},
 body: JSON.stringify({
  type: 'chat',
  content: 'Your question',
  yoloMode: true, // Enable YOLO mode
 }),
});

YOLO Mode Features

  • Auto-approval: Non-sensitive commands execute automatically
  • Sensitive Command Exception: Sensitive commands still require confirmation
  • Fast Response: Reduce interaction waiting time
  • Suitable for Automation: Script and automation scenarios

Security Considerations

Even with YOLO mode enabled:

  1. Sensitive commands still require confirmation
  2. Tools not in permission list require first-time confirmation
  3. Can abort execution at any time through rejection

Complete Examples

JavaScript Client

class SnowAIClient {
 constructor(baseUrl = 'http://localhost:3000') {
  this.baseUrl = baseUrl;
  this.eventSource = null;
  this.sessionId = null;
 }

 // Connect to SSE server
 connect() {
  return new Promise((resolve, reject) => {
   this.eventSource = new EventSource(`${this.baseUrl}/events`);

   this.eventSource.onopen = () => {
    console.log('Connected to Snow AI');
    resolve();
   };

   this.eventSource.onerror = error => {
    console.error('Connection error:', error);
    reject(error);
   };

   this.eventSource.onmessage = event => {
    this.handleEvent(JSON.parse(event.data));
   };
  });
 }

 // Handle events
 handleEvent(event) {
  console.log('[Event]', event.type);

  switch (event.type) {
   case 'tool_confirmation_request':
    this.handleToolConfirmation(event);
    break;

   case 'user_question_request':
    this.handleUserQuestion(event);
    break;

   case 'message':
    if (event.data.streaming) {
     process.stdout.write(event.data.content);
    }
    break;

   case 'complete':
    this.sessionId = event.data.sessionId;
    console.log('\nConversation complete, Session ID:', this.sessionId);
    break;
  }
 }

 // Handle tool confirmation
 async handleToolConfirmation(event) {
  const options = event.data.availableOptions;

  // Custom confirmation logic can be implemented here
  // Example: Auto-approve non-sensitive commands
  const decision = event.data.isSensitive ? 'reject' : 'approve';

  await this.sendToolConfirmation(event.requestId, decision);
 }

 // Handle user question
 async handleUserQuestion(event) {
  // Custom selection logic can be implemented here
  const selected = event.data.options[0];

  await this.sendUserQuestionResponse(event.requestId, {
   selected: selected,
  });
 }

 // Send message
 async sendMessage(content, yoloMode = false) {
  const payload = {
   type: 'chat',
   content: content,
  };

  if (this.sessionId) {
   payload.sessionId = this.sessionId;
  }

  if (yoloMode) {
   payload.yoloMode = true;
  }

  const response = await fetch(`${this.baseUrl}/message`, {
   method: 'POST',
   headers: {'Content-Type': 'application/json'},
   body: JSON.stringify(payload),
  });

  return await response.json();
 }

 // Send tool confirmation response
 async sendToolConfirmation(requestId, decision) {
  const response = await fetch(`${this.baseUrl}/message`, {
   method: 'POST',
   headers: {'Content-Type': 'application/json'},
   body: JSON.stringify({
    type: 'tool_confirmation_response',
    requestId: requestId,
    response: decision,
   }),
  });

  return await response.json();
 }

 // Send user question response
 async sendUserQuestionResponse(requestId, answer) {
  const response = await fetch(`${this.baseUrl}/message`, {
   method: 'POST',
   headers: {'Content-Type': 'application/json'},
   body: JSON.stringify({
    type: 'user_question_response',
    requestId: requestId,
    response: answer,
   }),
  });

  return await response.json();
 }

 // Disconnect
 disconnect() {
  if (this.eventSource) {
   this.eventSource.close();
   this.eventSource = null;
  }
 }
}

// Usage example
async function main() {
 const client = new SnowAIClient();

 // Connect
 await client.connect();

 // Send message (enable YOLO mode)
 await client.sendMessage('Help me create a TypeScript function', true);

 // Wait for response handling (via event listeners)
}

main();

Python Client

import requests
import json
import sseclient

class SnowAIClient:
    def __init__(self, base_url='http://localhost:3000'):
        self.base_url = base_url
        self.session = requests.Session()
        self.session_id = None

    def connect(self):
        """Connect to SSE server"""
        response = self.session.get(
            f'{self.base_url}/events',
            stream=True,
            headers={'Accept': 'text/event-stream'}
        )
        client = sseclient.SSEClient(response)

        for event in client.events():
            data = json.loads(event.data)
            self.handle_event(data)

    def handle_event(self, event):
        """Handle events"""
        print(f"[Event] {event['type']}")

        if event['type'] == 'tool_confirmation_request':
            self.handle_tool_confirmation(event)
        elif event['type'] == 'user_question_request':
            self.handle_user_question(event)
        elif event['type'] == 'complete':
            self.session_id = event['data']['sessionId']
            print(f"Session ID: {self.session_id}")

    def handle_tool_confirmation(self, event):
        """Handle tool confirmation"""
        # Auto-approve non-sensitive commands
        decision = 'reject' if event['data']['isSensitive'] else 'approve'
        self.send_tool_confirmation_response(event['requestId'], decision)

    def handle_user_question(self, event):
        """Handle user question"""
        selected = event['data']['options'][0]
        self.send_user_question_response(event['requestId'], {'selected': selected})

    def send_message(self, content, yolo_mode=False):
        """Send message"""
        payload = {
            'type': 'chat',
            'content': content,
        }

        if self.session_id:
            payload['sessionId'] = self.session_id

        if yolo_mode:
            payload['yoloMode'] = True

        response = self.session.post(
            f'{self.base_url}/message',
            json=payload
        )
        return response.json()

    def send_tool_confirmation_response(self, request_id, decision):
        """Send tool confirmation response"""
        response = self.session.post(
            f'{self.base_url}/message',
            json={
                'type': 'tool_confirmation_response',
                'requestId': request_id,
                'response': decision
            }
        )
        return response.json()

    def send_user_question_response(self, request_id, answer):
        """Send user question response"""
        response = self.session.post(
            f'{self.base_url}/message',
            json={
                'type': 'user_question_response',
                'requestId': request_id,
                'response': answer
            }
        )
        return response.json()

# Usage example
if __name__ == '__main__':
    client = SnowAIClient()

    # Send message (enable YOLO mode)
    client.send_message('Help me create a Python function', yolo_mode=True)

    # Listen for events
    client.connect()

Use Cases

Web Application Integration

Integrate Snow AI into your web application to provide intelligent programming assistant functionality:

// React component example
import {useState, useEffect, useRef} from 'react';

function AIAssistantChat() {
 const [connected, setConnected] = useState(false);
 const [messages, setMessages] = useState([]);
 const [sessionId, setSessionId] = useState(null);
 const eventSourceRef = useRef(null);

 // Connect to SSE server
 useEffect(() => {
  const eventSource = new EventSource('http://localhost:3000/events');
  eventSourceRef.current = eventSource;

  eventSource.onopen = () => {
   setConnected(true);
   console.log('Connected to Snow AI');
  };

  eventSource.onmessage = event => {
   const data = JSON.parse(event.data);
   handleSSEEvent(data);
  };

  eventSource.onerror = () => {
   setConnected(false);
   console.error('Connection lost');
  };

  return () => {
   eventSource.close();
  };
 }, []);

 // Handle SSE events
 const handleSSEEvent = data => {
  switch (data.type) {
   case 'message':
    if (data.data.role === 'assistant') {
     if (data.data.streaming) {
      // Stream update last message
      setMessages(prev => {
       const newMessages = [...prev];
       if (
        newMessages.length > 0 &&
        newMessages[newMessages.length - 1].role === 'assistant'
       ) {
        newMessages[newMessages.length - 1].content = data.data.content;
       } else {
        newMessages.push({
         role: 'assistant',
         content: data.data.content,
        });
       }
       return newMessages;
      });
     }
    }
    break;

   case 'complete':
    setSessionId(data.data.sessionId);
    console.log('Conversation complete');
    break;

   case 'tool_confirmation_request':
    // Show tool confirmation dialog
    handleToolConfirmation(data);
    break;

   case 'error':
    console.error('Error:', data.data.message);
    break;
  }
 };

 // Send message
 const sendMessage = async text => {
  const newMessage = {role: 'user', content: text};
  setMessages(prev => [...prev, newMessage]);

  const payload = {
   type: 'chat',
   content: text,
   yoloMode: true, // Auto-approve safe tools
  };

  if (sessionId) {
   payload.sessionId = sessionId;
  }

  await fetch('http://localhost:3000/message', {
   method: 'POST',
   headers: {'Content-Type': 'application/json'},
   body: JSON.stringify(payload),
  });
 };

 // Handle tool confirmation
 const handleToolConfirmation = async event => {
  const confirmed = window.confirm(
   `AI wants to execute tool: ${event.data.toolCall.function.name}\nAllow?`,
  );

  await fetch('http://localhost:3000/message', {
   method: 'POST',
   headers: {'Content-Type': 'application/json'},
   body: JSON.stringify({
    type: 'tool_confirmation_response',
    requestId: event.requestId,
    response: confirmed ? 'approve' : 'reject',
   }),
  });
 };

 return (
  <div>
   <div>Status: {connected ? 'Connected' : 'Disconnected'}</div>
   <div>
    {messages.map((msg, i) => (
     <div key={i}>
      <strong>{msg.role}:</strong> {msg.content}
     </div>
    ))}
   </div>
   <input
    type="text"
    onKeyPress={e => {
     if (e.key === 'Enter') {
      sendMessage(e.target.value);
      e.target.value = '';
     }
    }}
   />
  </div>
 );
}

Mobile App Backend

Provide AI capabilities for mobile applications:

// Express middleware
app.post('/api/ai/chat', async (req, res) => {
 const {message, sessionId} = req.body;

 // Forward to Snow AI
 const response = await fetch('http://localhost:3000/message', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
   type: 'chat',
   content: message,
   sessionId: sessionId,
   yoloMode: true,
  }),
 });

 res.json(await response.json());
});

Microservice Architecture

Use as AI microservice:

# Kubernetes deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: snow-ai-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: snow-ai
  template:
    metadata:
      labels:
        app: snow-ai
    spec:
      containers:
        - name: snow-ai
          image: snow-ai:latest
          command: ['snow', '--sse', '--sse-port', '3000']
          ports:
            - containerPort: 3000

Test Client

Snow CLI provides a complete HTML test client:

Location: sse-test-client.html

Features

  • Real-time SSE event monitoring
  • Beautiful chat interface
  • Event log viewer
  • YOLO mode toggle
  • Tool confirmation UI (with complete option display)
  • Session management
  • Connection status display

Usage

  1. Start SSE server:

    snow --sse
  2. Open sse-test-client.html in browser

  3. Click "Connect" button

  4. Start chatting and testing

Best Practices

1. Error Handling

// Comprehensive error handling
eventSource.onerror = error => {
 console.error('SSE connection error:', error);

 // Auto-reconnect
 setTimeout(() => {
  console.log('Attempting to reconnect...');
  connect();
 }, 5000);
};

2. Timeout Handling

// Set timeout for interaction requests
const TIMEOUT = 300000; // 5 minutes (default value, configurable via --sse-timeout parameter)

function waitForResponse(requestId) {
 return new Promise((resolve, reject) => {
  const timeout = setTimeout(() => {
   reject(new Error('Interaction timeout'));
  }, TIMEOUT);

  // Listen for response
  // clearTimeout(timeout) after receiving response
 });
}

3. Session Management

// Persist Session ID
localStorage.setItem('snow-session-id', sessionId);

// Restore Session
const savedSessionId = localStorage.getItem('snow-session-id');
if (savedSessionId) {
 await client.sendMessage(
  'Continue previous conversation',
  false,
  savedSessionId,
 );
}

4. Security Considerations

// Validate and sanitize user input
function sanitizeInput(input) {
 // Remove dangerous characters
 return input.replace(/[<>]/g, '');
}

// Add authentication in production
const response = await fetch('http://localhost:3000/message', {
 headers: {
  'Content-Type': 'application/json',
  Authorization: `Bearer ${apiToken}`,
 },
 // ...
});

Limitations and Notes

Unsupported Features

  1. Interactive UI:

    • Cannot use Ink terminal interface
    • Keyboard shortcuts not supported
  2. Plan Mode:

    • Interactive plan approval not supported
    • All operations execute immediately
  3. Local File Access Restrictions:

    • Can only access files under server working directory
    • Cannot access client-side local files

Performance Notes

  1. Connection Limit:

    • Recommended maximum 100 concurrent connections per server
    • Consider load balancing
  2. Session Size:

    • Long sessions increase memory usage
    • Regularly clean up old sessions
  3. Network Bandwidth:

    • Streaming output continuously occupies connection
    • Consider message size limits

Security Notes

  1. Authentication and Authorization:

    • Must add authentication in production environment
    • Implement access control
  2. API Key Protection:

    • Don't expose API keys on client side
    • Use server-side configuration
  3. Command Execution Risks:

    • Review all tool invocations
    • Restrict sensitive operations

FAQ

Q: What's the difference between SSE server and headless mode?

A: SSE server is a continuously running backend service supporting multiple client connections. Headless mode is single-execution mode that automatically exits after completion. SSE is suitable for web application integration, headless mode for script automation.

Q: How to use different API configurations in SSE mode?

A: SSE server reads configuration files from working directory. Use --work-dir parameter to specify different project directories, each with independent configuration.

Q: Can I run multiple SSE servers simultaneously?

A: Yes, but need to use different ports. For example:

snow --sse --sse-port 3000
snow --sse --sse-port 3001 --work-dir /another-project

Q: Do sessions expire?

A: Sessions don't expire and are permanently saved in ~/.snow/sessions/ directory. However, very long sessions increase token consumption.

Q: How to handle tool confirmation timeout?

A: Tool confirmation has a default 5-minute (300000ms) timeout. If timeout occurs, execution is automatically rejected and error returned. Recommend implementing auto-handling or user prompts in client.

You can customize the timeout duration via --sse-timeout parameter:

# Set to 10 minutes (600000ms)
snow --sse --sse-timeout 600000

# Set to 30 seconds (30000ms)
snow --sse --sse-timeout 30000

Q: Does YOLO mode execute all commands?

A: No. Sensitive commands require confirmation even in YOLO mode. YOLO mode only auto-approves safe tools in the permission list.

Q: How to debug SSE connection issues?

A:

  1. Check server logs (terminal display)
  2. Use browser developer tools to view network requests
  3. Use sse-test-client.html for testing
  4. Check firewall and port usage

Q: Can SSE server run in Docker?

A: Yes. Example Dockerfile:

FROM node:18
RUN npm install -g snow-ai
EXPOSE 3000
CMD ["snow", "--sse", "--sse-port", "3000"]

Configuration File Locations

Configuration files used by SSE server:

  • API Configuration: ~/.snow/profiles.json
  • Permission Configuration: <working-directory>/.snow/permissions.json
  • Sensitive Commands: ~/.snow/sensitive-commands.json
  • Session Storage: ~/.snow/sessions/<project-name>/<date>/

For configuration methods, refer to: First Time Configuration

Related Features