ryOS ryOS / Docs
GitHub Launch

Chat API

The main AI chat endpoint for ryOS that powers the Chats application. This endpoint handles conversational AI interactions with the "Ryo" persona, supports multiple AI providers, and includes extensive tool calling capabilities for controlling the ryOS system.

This endpoint is implemented with the shared apiHandler utility and optional auth via request-auth.

Endpoint

MethodPathDescription
POST/api/chatMain AI chat with streaming responses and tool calling

Request

Headers

HeaderRequiredDescription
AuthorizationYesBearer token (Bearer {token})
X-UsernameYesUsername paired with the token
Content-TypeYesMust be application/json

*Required for authenticated users. Anonymous users are allowed with reduced rate limits.

If one auth header is present without the other, the request is rejected (400).

Query Parameters

ParameterTypeRequiredDescription
modelstringNoOverride the AI model (takes precedence over body parameter)

Body

interface ChatRequest {
  // Array of conversation messages
  messages: UIMessage[];
  
  // AI model to use (optional, defaults to "gpt-5.4")
  model?: SupportedModel;
  
  // Optional proactive greeting mode (non-streaming JSON response)
  proactiveGreeting?: boolean;

  // System state context (optional but recommended)
  systemState?: SystemState;
}

interface UIMessage {
  role: "user" | "assistant" | "system";
  content: string | MessageContent[];
}

interface MessageContent {
  type: "text" | "image";
  text?: string;
  image?: string; // base64 or URL
}

interface SystemState {
  // User identity
  username?: string | null;
  userOS?: string; // e.g., "iOS", "Android", "macOS", "Windows", "Linux"
  locale?: string; // e.g., "en", "zh-TW", "ja", "ko"
  
  // User's local time (from browser)
  userLocalTime?: {
    timeString: string;
    dateString: string;
    timeZone: string;
  };
  
  // Running applications context
  runningApps?: {
    foreground: AppInstance | null;
    background: AppInstance[];
  };
  
  // Internet Explorer state
  internetExplorer: {
    url: string;
    year: string;
    currentPageTitle: string | null;
    aiGeneratedMarkdown?: string | null;
  };
  
  // Video player state
  video: {
    currentVideo: TrackInfo | null;
    isPlaying: boolean;
  };
  
  // iPod state (optional)
  ipod?: {
    currentTrack: TrackInfo | null;
    isPlaying: boolean;
    currentLyrics?: {
      lines: Array<{ startTimeMs: string; words: string }>;
    } | null;
  };
  
  // Karaoke state (optional)
  karaoke?: {
    currentTrack: TrackInfo | null;
    isPlaying: boolean;
  };
  
  // TextEdit state (optional)
  textEdit?: {
    instances: Array<{
      instanceId: string;
      filePath: string | null;
      title: string;
      contentMarkdown?: string | null;
      hasUnsavedChanges: boolean;
    }>;
  };
  
  // Chat room context (for chat room @mentions)
  chatRoomContext?: {
    roomId: string;
    recentMessages: string;
    mentionedMessage: string;
  };
}

interface AppInstance {
  instanceId: string;
  appId: string;
  title?: string;
  appletPath?: string;
  appletId?: string;
}

interface TrackInfo {
  id: string;
  title: string;
  artist?: string;
}

Response

The endpoint returns a Server-Sent Events (SSE) stream using the Vercel AI SDK's UI message stream format.

If proactiveGreeting is true, the endpoint returns JSON instead of SSE:

{ "greeting": "..." }

Stream Format

The response uses Content-Type: text/event-stream and streams data in the following format:

data: {"type":"text-delta","textDelta":"Hello"}
data: {"type":"text-delta","textDelta":" there"}
data: {"type":"tool-call","toolCallId":"...","toolName":"launchApp","args":{...}}
data: {"type":"tool-result","toolCallId":"...","result":{...}}
data: {"type":"finish","finishReason":"stop"}

Error Responses

StatusErrorDescription
400Invalid messages formatMessages array is missing or malformed
400Unsupported modelRequested model is not supported
400Invalid JSONRequest body is not valid JSON
400Both Authorization and X-Username headers are requiredPartial auth headers
401Unauthorized - invalid tokenInvalid token/username pair
403UnauthorizedRequest origin not allowed
405Method not allowedOnly POST requests are accepted
429rate_limit_exceededRate limit exceeded for the user
500Internal Server ErrorServer-side error

Rate Limit Error Response

interface RateLimitError {
  error: "rate_limit_exceeded";
  isAuthenticated: boolean;
  count: number;
  limit: number;
  message: string;
}

AI Models

Supported Models

Model IDProviderUnderlying Model
gpt-5.4OpenAIgpt-5.4 (default)
sonnet-4.6Anthropicclaude-sonnet-4-6
gemini-3-flashGooglegemini-3-flash-preview
gemini-3.1-pro-previewGooglegemini-3.1-pro-preview

Model Selection

// Priority: Query param > Body param > Default
const model = queryModel || bodyModel || "gpt-5.4";

Rate Limiting

User TypeLimitWindow
Authenticated15 messages5 hours
Anonymous3 messages24 hours

Anonymous users are identified by IP address. The special user "ryo" (with valid token) has unlimited access.

Tool Calling

The Chat API supports extensive tool calling for controlling ryOS applications and system features.

Application Control

launchApp

Launch an application in ryOS.
{
  id: AppId;           // Required: App to launch
  url?: string;        // For internet-explorer: URL to load
  year?: string;       // For internet-explorer: Time-travel year
}

// Available AppIds:
type AppId = 
  | "finder" | "soundboard" | "internet-explorer" | "chats"
  | "textedit" | "paint" | "photo-booth" | "minesweeper"
  | "videos" | "ipod" | "karaoke" | "synth" | "pc"
  | "terminal" | "applet-viewer" | "control-panels" | "admin";

closeApp

Close an application.
{
  id: AppId;  // App to close
}

Media Control

ipodControl

Control iPod playback.
{
  action: "toggle" | "play" | "pause" | "playKnown" | "addAndPlay" | "next" | "previous";
  id?: string;              // YouTube ID or URL (for addAndPlay/playKnown)
  title?: string;           // Track title (for playKnown)
  artist?: string;          // Artist name (for playKnown)
  enableVideo?: boolean;    // Enable video playback
  enableFullscreen?: boolean;
  enableTranslation?: string; // Language code for lyrics translation
}

karaokeControl

Control Karaoke playback (same schema as iPod, without enableVideo).
{
  action: "toggle" | "play" | "pause" | "playKnown" | "addAndPlay" | "next" | "previous";
  id?: string;
  title?: string;
  artist?: string;
  enableFullscreen?: boolean;
  enableTranslation?: string;
}

searchSongs

Search for songs on YouTube.
// Input
{
  query: string;       // Search query (1-200 chars)
  maxResults?: number; // 1-10, default 5
}

// Output
{
  results: Array<{
    videoId: string;
    title: string;
    channelTitle: string;
    publishedAt: string;
  }>;
  message: string;
  hint: string;
}

Virtual File System

list

List items from the virtual file system.
{
  path: "/Applets" | "/Documents" | "/Applications" | "/Music" | "/Applets Store";
  query?: string;  // Search filter (for Applets Store)
  limit?: number;  // Max results 1-50 (for Applets Store)
}

open

Open a file or launch an app.
{
  path: string;  // e.g., "/Applets/Calculator.app", "/Documents/notes.md"
}

read

Read file contents.
{
  path: string;  // Path to file in /Applets, /Documents, or /Applets Store
}

write

Create or modify documents.
{
  path: string;     // e.g., "/Documents/notes.md"
  content: string;  // Document content
  mode?: "overwrite" | "append" | "prepend";
}

edit

Make targeted edits to existing files.
{
  path: string;       // File path
  old_string: string; // Text to replace (must be unique)
  new_string: string; // Replacement text
}

HTML/Applet Generation

generateHtml

Generate an HTML applet for ryOS.
// Input
{
  html: string;    // HTML content (body only)
  title?: string;  // Applet title
  icon?: string;   // Emoji icon
}

// Output (executed server-side)
{
  html: string;
  title: string;
  icon: string;
}

System Settings

settings

Change system preferences.
{
  language?: "en" | "zh-TW" | "ja" | "ko" | "fr" | "de" | "es" | "pt" | "it" | "ru";
  theme?: "system7" | "macosx" | "xp" | "win98";
  masterVolume?: number;     // 0-1
  speechEnabled?: boolean;   // Text-to-speech
  checkForUpdates?: boolean; // Trigger update check
}

Special Tools

aquarium

Render an emoji aquarium in the chat bubble.
{}  // No parameters

Example Usage

Basic Chat Request

const response = await fetch('/api/chat', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${authToken}`,
    'X-Username': 'myusername'
  },
  body: JSON.stringify({
    messages: [
      { role: 'user', content: 'Hello, Ryo!' }
    ],
      model: 'sonnet-4.6',
    systemState: {
      username: 'myusername',
      locale: 'en',
      internetExplorer: { url: '', year: 'current', currentPageTitle: null },
      video: { currentVideo: null, isPlaying: false }
    }
  })
});

// Handle SSE stream
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  
  const chunk = decoder.decode(value);
  // Process SSE events...
}

Using with Vercel AI SDK

import { useChat } from 'ai/react';

function ChatComponent() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: '/api/chat',
    headers: {
      'Authorization': `Bearer ${token}`,
      'X-Username': username
    },
    body: {
      model: 'gpt-5.4',
      systemState: { /* ... */ }
    }
  });

  return (
    <form onSubmit={handleSubmit}>
      <input value={input} onChange={handleInputChange} />
      <button type="submit">Send</button>
    </form>
  );
}

Playing a Song via Tool Calling

The AI will automatically use tools when appropriate:

// User message: "Play Never Gonna Give You Up"

// AI will:
// 1. Call list({ path: "/Music" }) to check library
// 2. If not found, call searchSongs({ query: "Never Gonna Give You Up" })
// 3. Call ipodControl({ action: "addAndPlay", id: "dQw4w9WgXcQ" })

Configuration

Runtime Settings

export const runtime = "nodejs";   // Node.js runtime
export const maxDuration = 80;     // Max duration (seconds)

Model Configuration

const result = streamText({
  model: selectedModel,
  messages: enrichedMessages,
  tools: { /* ... */ },
  temperature: 0.7,
  maxOutputTokens: 48000,
  stopWhen: stepCountIs(10),  // Max 10 tool-calling steps
  experimental_transform: smoothStream({
    chunking: /[\u4E00-\u9FFF]|\S+\s+/,  // CJK-aware chunking
  })
});

Related Endpoints

EndpointDocumentationDescription
/api/applet-ai-Applet text + image generation
/api/ie-generate-Time-travel page generation
/api/speech-Text-to-speech synthesis
/api/audio-transcribe-Speech-to-text transcription
/api/youtube-search-YouTube music search
/api/songs/-Song library CRUD operations

System Prompt Structure

The Chat API uses a layered system prompt approach:

  1. Static System Prompt (cached for performance):
    • Core priority instructions
    • Ryo persona instructions
    • Answer style guidelines
    • Chat instructions
    • Tool usage instructions
    • Code generation instructions
  2. Dynamic System Prompt (per-request):
    • User context (username, OS, locale)
    • Time information (Ryo's time + user's local time)
    • Running applications
    • Media playback state
    • Browser state
    • TextEdit documents
    • Chat room context (if applicable)