Skip to content

Latest commit

 

History

History
973 lines (759 loc) · 22.5 KB

File metadata and controls

973 lines (759 loc) · 22.5 KB

HTTP API Reference

DSAgent provides a REST API and WebSocket for building custom UIs and integrations. The streaming chat endpoint uses Server-Sent Events (SSE).

Base URL

All API routes are under /api (no version prefix). Health endpoints are at the root.

http://localhost:8000
Path Description
GET /health Health check (no auth)
GET /health/ready Readiness check (no auth)
/api/sessions Session CRUD
/api/sessions/{id}/chat Chat (sync and stream)
/api/sessions/{id}/kernel Kernel state, execute, reset
/api/sessions/{id}/files List/upload/download/delete files
/api/sessions/{id}/artifacts List/download/delete artifacts
/api/sessions/{id}/hitl/* Human-in-the-loop
WS /ws/chat/{session_id} WebSocket chat

Authentication

If DSAGENT_API_KEY is set, all API endpoints (except /health, /health/ready) require the key.

Header (recommended):

X-API-Key: your-api-key

Query (WebSocket only): ?api_key=your-api-key (see security note in server docs)


Sessions

Create Session

POST /api/sessions

Request Body:

{
  "name": "My Analysis Session",
  "model": "gpt-4o",
  "hitl_mode": "none"
}

Response:

{
  "id": "20260112_215333_b5537c",
  "name": "My Analysis Session",
  "status": "active",
  "created_at": "2026-01-12T21:53:33.204Z",
  "updated_at": "2026-01-12T21:53:33.204Z",
  "message_count": 0,
  "kernel_variables": 0,
  "workspace_path": "/workspace/sessions/20260112_215333_b5537c"
}

List Sessions

GET /api/sessions?status=active&limit=50

Query: status (optional), limit (default 50, max 100).

Get Session

GET /api/sessions/{session_id}

Delete Session

DELETE /api/sessions/{session_id}

Archive Session

POST /api/sessions/{session_id}/archive

Export Session as JSON

GET /api/sessions/{session_id}/export

Returns session data with Content-Disposition: attachment; filename="...json".

Export Session as Notebook

GET /api/sessions/{session_id}/notebook

Returns the session's Jupyter notebook (.ipynb) if available.

Update Session

Update session configuration including model and HITL mode at runtime.

PUT /api/sessions/{session_id}
Content-Type: application/json

{
  "name": "New Session Name",
  "model": "claude-sonnet-4-20250514",
  "hitl_mode": "plan_only"
}

All fields are optional. Only provided fields will be updated.

Request Body:

Field Type Description
name string New name for the session
status string Session status: active, paused, completed
model string LLM model (e.g., gpt-4o, claude-sonnet-4-20250514)
hitl_mode string HITL mode (see below)

HITL Modes:

Mode Description
none Fully autonomous (default)
plan_only Pause after generating plan for approval
on_error Pause only when code execution fails
plan_and_answer Pause for plan + before final answer
full Pause before every code execution

Response: Updated session object

Example - Change model mid-conversation:

curl -X PUT "http://localhost:8000/api/sessions/{session_id}" \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-sonnet-4-20250514"}'

Example - Enable HITL:

curl -X PUT "http://localhost:8000/api/sessions/{session_id}" \
  -H "Content-Type: application/json" \
  -d '{"hitl_mode": "plan_only"}'

Human-in-the-Loop (HITL)

HITL allows human approval before the agent executes plans or code. When enabled, the agent pauses and waits for approval via these endpoints.

Get HITL Status

Check if the agent is awaiting human approval.

GET /api/sessions/{session_id}/hitl/status

Response:

{
  "enabled": true,
  "mode": "plan_only",
  "awaiting_feedback": true,
  "awaiting_type": "plan",
  "pending_plan": {
    "raw_text": "1. [ ] Load data\n2. [ ] Analyze\n3. [ ] Visualize",
    "steps": [
      {"number": 1, "description": "Load data", "completed": false},
      {"number": 2, "description": "Analyze", "completed": false},
      {"number": 3, "description": "Visualize", "completed": false}
    ]
  },
  "pending_code": null,
  "pending_error": null,
  "pending_answer": null
}

Approve

Approve the pending plan/code and continue execution.

POST /api/sessions/{session_id}/hitl/approve

Response:

{
  "success": true,
  "message": "Approved"
}

Reject

Reject and abort the current task.

POST /api/sessions/{session_id}/hitl/reject

Response:

{
  "success": true,
  "message": "Rejected - task aborted"
}

Respond (Advanced)

Send detailed HITL response with optional modifications.

POST /api/sessions/{session_id}/hitl/respond
Content-Type: application/json

{
  "action": "modify",
  "message": "Please also add error handling",
  "modified_plan": "1. [ ] Load data with error handling\n2. [ ] Analyze\n3. [ ] Visualize"
}

Actions:

Action Description
approve Approve and continue
reject Reject and abort
modify Provide modified plan or code
retry Retry the failed operation
skip Skip current step
feedback Send textual feedback

HITL Workflow Example

1. Create session with HITL enabled:

curl -X POST "http://localhost:8000/api/sessions" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_KEY" \
  -d '{"name": "HITL Session", "hitl_mode": "plan_only"}'

2. Send a message (this will block waiting for approval):

# In terminal 1 — waits for approval
curl -X POST "http://localhost:8000/api/sessions/{session_id}/chat" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_KEY" \
  -d '{"message": "Analyze sales data and create visualizations"}'

3. Check status (in another terminal):

curl "http://localhost:8000/api/sessions/{session_id}/hitl/status" \
  -H "X-API-Key: YOUR_KEY"
# Returns: awaiting_feedback: true, awaiting_type: "plan"

4. Approve the plan:

curl -X POST "http://localhost:8000/api/sessions/{session_id}/hitl/approve" \
  -H "X-API-Key: YOUR_KEY"

5. The chat request in terminal 1 now continues execution.

HITL with Streaming

When using the streaming endpoint, a hitl_request event is emitted when approval is needed:

event: hitl_request
data: {
  "request_type": "plan",
  "plan": {
    "steps": [...],
    "raw_text": "..."
  },
  "code": null,
  "error": null
}

The stream pauses until you call /hitl/approve or /hitl/reject.


Chat

Send Message (Synchronous)

For simple integrations that don't need real-time updates:

POST /api/sessions/{session_id}/chat
Content-Type: application/json

{
  "message": "Analyze the iris dataset"
}

Response:

{
  "content": "I'll analyze the iris dataset...",
  "code": "import pandas as pd\ndf = pd.read_csv('data/iris.csv')",
  "execution_result": {
    "stdout": "   sepal.length  sepal.width ...",
    "stderr": "",
    "error": null,
    "images": [],
    "success": true
  },
  "plan": {
    "steps": [...],
    "completed_steps": 1,
    "total_steps": 3,
    "is_complete": false
  },
  "has_answer": false,
  "answer": null,
  "is_complete": false
}

Send Message (Streaming) - Recommended for UIs

POST /api/sessions/{session_id}/chat/stream
Content-Type: application/json

{
  "message": "Analyze the iris dataset and create visualizations"
}

Response: Server-Sent Events stream


SSE Events Reference

The streaming endpoint emits granular events that allow UIs to show real-time progress.

Event Flow

┌─────────────────────────────────────────────────────────────┐
│                     ROUND N                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  thinking ──► llm_response ──► plan ──► code_executing     │
│                                              │              │
│                                              ▼              │
│                                        code_result          │
│                                              │              │
│                                              ▼              │
│                                       round_complete        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                              │
                        (repeat for each round)
                              │
                              ▼
                      ┌──────────────┐
                      │     done     │
                      └──────────────┘

Event Types

thinking

Emitted when the LLM starts processing. Use this to show a loading indicator.

event: thinking
data: {"message": "Processing..."}

UI Action: Show spinner or "Thinking..." indicator


llm_response

Emitted when the LLM response is received (before code execution).

event: llm_response
data: {
  "content": "I'll analyze the iris dataset. Let me start by loading the data...\n\n<plan>\n1. [ ] Load data\n2. [ ] Create visualizations\n</plan>\n\n<code>\nimport pandas as pd\ndf = pd.read_csv('data/iris.csv')\n</code>"
}

UI Action: Display the agent's response text (you may want to parse and hide <plan>, <code> tags)


plan

Emitted when a plan is extracted from the response.

event: plan
data: {
  "steps": [
    {"number": 1, "description": "Load and explore data", "completed": false},
    {"number": 2, "description": "Create visualizations", "completed": false},
    {"number": 3, "description": "Save to artifacts/", "completed": false}
  ],
  "raw_text": "1. [ ] Load and explore data\n2. [ ] Create visualizations\n3. [ ] Save to artifacts/",
  "total_steps": 3,
  "completed_steps": 0,
  "is_complete": false
}

UI Action: Update plan panel with steps and progress bar


code_executing

Emitted just before code is executed in the kernel.

event: code_executing
data: {
  "code": "import pandas as pd\ndf = pd.read_csv('data/iris.csv')\ndf.head()"
}

UI Action: Show code block with "Executing..." badge


code_result

Emitted after code execution completes.

event: code_result
data: {
  "stdout": "   sepal.length  sepal.width  petal.length  petal.width variety\n0           5.1          3.5           1.4          0.2  Setosa\n...",
  "stderr": "",
  "error": null,
  "images": [
    {"format": "png", "data": "iVBORw0KGgoAAAANSUhEUgAAA..."}
  ],
  "success": true
}

UI Action:

  • Show output in console panel
  • Render images from base64
  • Update code block badge to "Success" or "Error"

round_complete

Emitted at the end of each autonomous execution round. Contains the full response for that round.

event: round_complete
data: {
  "round": 1,
  "content": "Full LLM response text...",
  "code": "import pandas as pd\n...",
  "execution_result": {
    "stdout": "...",
    "stderr": "",
    "error": null,
    "images": [],
    "success": true
  },
  "plan": {
    "steps": [...],
    "completed_steps": 1,
    "total_steps": 3,
    "is_complete": false
  },
  "has_answer": false,
  "answer": null,
  "thinking": null,
  "is_complete": false
}

UI Action: This is the "complete" event for the round - useful for logging or if you prefer to wait for complete data rather than granular events.


done

Emitted when the entire task is complete.

event: done
data: {}

UI Action:

  • Hide loading indicators
  • Enable input for new message
  • Optionally show "Task Complete" notification

error

Emitted if an error occurs during processing.

event: error
data: {
  "error": "Session not found"
}

UI Action: Show error notification/toast


Client Examples

JavaScript/TypeScript

async function streamChat(sessionId: string, message: string) {
  const response = await fetch(`/api/sessions/${sessionId}/chat/stream`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': 'your-api-key',
    },
    body: JSON.stringify({ message }),
  });

  const reader = response.body!.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

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

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop() || '';

    for (const line of lines) {
      if (line.startsWith('event: ')) {
        const eventType = line.slice(7);
        continue;
      }
      if (line.startsWith('data: ')) {
        const data = JSON.parse(line.slice(6));
        handleEvent(eventType, data);
      }
    }
  }
}

function handleEvent(type: string, data: any) {
  switch (type) {
    case 'thinking':
      showSpinner();
      break;
    case 'llm_response':
      appendMessage(data.content);
      break;
    case 'plan':
      updatePlanPanel(data);
      break;
    case 'code_executing':
      showCodeBlock(data.code, 'executing');
      break;
    case 'code_result':
      updateCodeResult(data);
      renderImages(data.images);
      break;
    case 'done':
      hideSpinner();
      enableInput();
      break;
    case 'error':
      showError(data.error);
      break;
  }
}

Using EventSource (alternative)

// Note: EventSource only supports GET, so you'd need a different approach
// or use a library like eventsource-parser for POST requests

React Hook Example

import { useState, useCallback } from 'react';

interface StreamState {
  isLoading: boolean;
  plan: Plan | null;
  code: string | null;
  codeStatus: 'idle' | 'executing' | 'success' | 'error';
  output: string;
  images: Image[];
  error: string | null;
}

function useChatStream(sessionId: string) {
  const [state, setState] = useState<StreamState>({
    isLoading: false,
    plan: null,
    code: null,
    codeStatus: 'idle',
    output: '',
    images: [],
    error: null,
  });

  const sendMessage = useCallback(async (message: string) => {
    setState(s => ({ ...s, isLoading: true, error: null }));

    try {
      const response = await fetch(`/api/sessions/${sessionId}/chat/stream`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message }),
      });

      // ... parse SSE events and update state

    } catch (err) {
      setState(s => ({ ...s, error: err.message, isLoading: false }));
    }
  }, [sessionId]);

  return { ...state, sendMessage };
}

cURL

curl -X POST "http://localhost:8000/api/sessions/SESSION_ID/chat/stream" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{"message": "Analyze iris.csv"}' \
  --no-buffer

Message History

Get Messages

GET /api/sessions/{session_id}/messages?limit=50&offset=0&role=assistant

Query Parameters:

Parameter Type Default Description
limit int 50 Max messages to return (1-200)
offset int 0 Skip N messages
role string null Filter by role: user, assistant, execution, system

Response:

{
  "messages": [
    {
      "id": "msg_001",
      "role": "user",
      "content": "Analyze the iris dataset",
      "timestamp": "2026-01-12T21:54:44.673Z",
      "metadata": {}
    },
    {
      "id": "msg_002",
      "role": "assistant",
      "content": "I'll analyze the iris dataset...",
      "timestamp": "2026-01-12T21:54:47.900Z",
      "metadata": {
        "has_code": true,
        "has_plan": true
      }
    }
  ],
  "total": 10,
  "has_more": false
}

Get Conversation Turns (Recommended for UI)

Returns conversation history as structured turns, matching the round_complete SSE event format. Use this endpoint to load historical messages so the UI can render them identically to live streaming.

!!! info "Data Source" This endpoint reads from {workspace}/logs/events.jsonl which contains the complete event history including all LLM responses, code executions, and plan updates. This ensures the UI receives the full conversation history, even after internal summarization events.

GET /api/sessions/{session_id}/turns?limit=50&offset=0

Query Parameters:

Parameter Type Default Description
limit int 50 Max turns to return (1-200)
offset int 0 Skip N turns

Response:

{
  "turns": [
    {
      "round": 1,
      "timestamp": "2026-01-12T21:54:47.900Z",
      "user_message": "Analyze the iris dataset and create visualizations",
      "content": "I'll analyze the iris dataset...\n\n<plan>\n1. [ ] Load data\n</plan>\n\n<code>\nimport pandas as pd\n</code>",
      "code": "import pandas as pd\ndf = pd.read_csv('data/iris.csv')",
      "execution_result": {
        "stdout": "   sepal.length  sepal.width...",
        "stderr": "",
        "error": null,
        "images": [],
        "success": true
      },
      "plan": {
        "steps": [
          {"number": 1, "description": "Load data", "completed": false}
        ],
        "total_steps": 3,
        "completed_steps": 0,
        "is_complete": false
      },
      "has_answer": false,
      "answer": null,
      "thinking": null,
      "is_complete": false
    },
    {
      "round": 2,
      "timestamp": "2026-01-12T21:54:52.100Z",
      "user_message": null,
      "content": "Now I'll create visualizations...",
      "code": "import matplotlib.pyplot as plt\n...",
      "execution_result": {...},
      "plan": {...},
      "has_answer": false,
      "answer": null,
      "thinking": null,
      "is_complete": false
    }
  ],
  "total": 6,
  "has_more": false
}

Note: user_message is null for autonomous continuation rounds (when the agent continues without user input).

UI Usage Pattern:

// On session load, fetch historical turns
const history = await fetch(`/api/sessions/${sessionId}/turns`);
const { turns } = await history.json();

// Render each turn using the same component as round_complete events
turns.forEach(turn => renderRoundComplete(turn));

// For live streaming, connect to SSE
const eventSource = connectToStream(sessionId);
eventSource.on('round_complete', renderRoundComplete);

Files & Artifacts

Files are organized by category: data, artifacts, or notebooks. Session ID must match pattern [a-zA-Z0-9_-]+.

List Files

GET /api/sessions/{session_id}/files?category=data

Query: categorydata, artifacts, or notebooks (default: data).

Upload Files

POST /api/sessions/{session_id}/files
Content-Type: multipart/form-data

files: (one or more files)
category: data   (optional; default: data)

Upload size per file is limited by DSAGENT_MAX_UPLOAD_MB (default 50 MB; 0 = no limit).

Download File

GET /api/sessions/{session_id}/files/{filename}?category=data

Delete File

DELETE /api/sessions/{session_id}/files/{filename}?category=data

List Artifacts

GET /api/sessions/{session_id}/artifacts?type=image

Query: type — optional filter: image, document, data, model, code, notebook.

Download Artifact

GET /api/sessions/{session_id}/artifacts/{filename}

Returns the file content (image, CSV, etc.).

Delete Artifact

DELETE /api/sessions/{session_id}/artifacts/{filename}

Data Schemas

ChatResponseModel

interface ChatResponseModel {
  content: string;              // Full LLM response text
  code: string | null;          // Extracted code (if any)
  execution_result: ExecutionResult | null;
  plan: PlanResponse | null;
  has_answer: boolean;          // True if contains <answer> tag
  answer: string | null;        // Extracted answer text
  thinking: string | null;      // Model's thinking (if available)
  is_complete: boolean;         // True when task is done
}

ExecutionResult

interface ExecutionResult {
  stdout: string;               // Standard output
  stderr: string;               // Standard error
  error: string | null;         // Error message if failed
  images: Image[];              // Generated images (base64)
  success: boolean;             // True if code ran successfully
}

interface Image {
  format: string;               // "png", "jpeg", etc.
  data: string;                 // Base64 encoded image data
}

PlanResponse

interface PlanResponse {
  steps: PlanStep[];
  raw_text: string;             // Original plan text
  total_steps: number;
  completed_steps: number;
  is_complete: boolean;         // True when all steps done
}

interface PlanStep {
  number: number;
  description: string;
  completed: boolean;
}

UI Component Mapping

SSE Event Suggested UI Component
thinking Loading spinner / "Thinking..." text
llm_response Chat message bubble
plan Collapsible plan panel with checkboxes
code_executing Code block with syntax highlighting + "Running" badge
code_result Output console + image gallery
round_complete (Optional) Round divider
done Enable input, hide spinner
error Toast notification / error banner

Running the Server

# Start the API server (host/port via flags; config from env)
dsagent serve --port 8000

# With API key (from env)
export DSAGENT_API_KEY=my-secret-key
dsagent serve

# Production: require API key
export DSAGENT_REQUIRE_API_KEY=true
export DSAGENT_API_KEY=my-secret-key
dsagent serve --port 8000

OpenAPI docs: http://localhost:8000/docs. Health: http://localhost:8000/health.


Next Steps