Chat API
Core conversation API handling all Agent interactions
Overview
The Chat API is the core endpoint of the Lens OS SDK, handling all Agent conversations. It receives user messages and streams responses via SSE (Server-Sent Events), including Agent thinking process, tool calls, and final answers.
Data Flow
SSE Stream Flow
Client Server LLM
│ │ │
│ POST /chat │ │
│ ───────────────────────►│ │
│ │ Request completion │
│ │ ──────────────────────►│
│ │ │
│ SSE: tool_call │◄─ Tool call needed ────│
│◄────────────────────────│ │
│ │ Execute tool │
│ │ ────────┐ │
│ │◄────────┘ │
│ SSE: tool_result │ │
│◄────────────────────────│ Return result │
│ │ ──────────────────────►│
│ SSE: text │◄─ Final response ──────│
│◄────────────────────────│ │
│ SSE: done │ │
│◄────────────────────────│ │Flow explanation:
- Client → Server: Frontend sends POST request to Chat API
- Server → LLM: SDK sends message to LLM model
- tool_call: LLM decides to execute a tool, sends tool call event
- Execute tool: Backend executes the corresponding Tool Executor
- tool_result: Tool execution complete, returns result to LLM
- text: LLM generates final response text
- done: Conversation round complete
Basic Setup
Create the Chat API route, the core file for handling all Agent conversations:
src/app/api/lens/agent/chat/route.ts
// src/app/api/lens/agent/chat/route.ts
import { createAgentHandler } from "@lens-os/sdk/server";
import { auth } from "@/auth";
import type { ToolExecutorConfig } from "@lens-os/sdk";
// Define your tools here — see Tool Executors docs
const tools: Record<string, ToolExecutorConfig> = {};
// Create Handler (initialized once on app start)
const handler = createAgentHandler({
// Lens OS API Key
apiKey: process.env.LENS_OS_API_KEY!,
// LLM settings
openaiKey: process.env.LENS_MODEL_API_KEY!,
openaiBaseUrl: process.env.LENS_MODEL_BASE_URL || "",
model: process.env.LENS_MODEL_NAME || "gpt-4o",
// Agent behavior
maxTurns: 10, // Max 10 tool call rounds
debug: process.env.NODE_ENV === "development",
// Tool executors
toolExecutors: tools,
});
export async function POST(req: Request) {
// 1. Authenticate
const session = await auth();
if (!session?.user) {
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
// 2. Get userId
const userId = session.user.id || "default-user";
// 3. Inject userId into request
const body = await req.json();
const newReq = new Request(req.url, {
method: "POST",
headers: req.headers,
body: JSON.stringify({ ...body, userId }),
});
// 4. Let SDK handle the request
return handler.POST(newReq);
}Tip
See the "Tool Executors" docs to learn how to create tools.
Handler Options
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | required | Lens OS API Key |
openaiKey | string | required | LLM API Key |
openaiBaseUrl | string | "" | LLM API Base URL |
model | string | "gpt-4o" | LLM model name |
maxTurns | number | 10 | Max tool call rounds |
debug | boolean | false | Enable verbose logging |
toolExecutors | object | {} | Tool executors |
Request Body
The frontend sendMessage sends requests in this format:
Request
interface ChatRequest {
message: string; // User message
sessionId?: string; // Session ID (auto-generated)
userId?: string; // User ID (injected by backend)
context?: {
currentUrl?: string; // Current page URL
pageState?: PageState; // Page state (for visual understanding)
};
}Response Format
Response is an SSE stream, each event formatted as:
SSE Events
// Text response
{ "type": "text", "content": "Let me search for you..." }
// Tool call started
{ "type": "tool_call", "name": "product_search", "parameters": { "query": "TypeScript" } }
// Tool execution completed
{ "type": "tool_result", "name": "product_search", "result": { ... } }
// Error
{ "type": "error", "error": "Something went wrong" }
// Done
{ "type": "done" }Event type descriptions:
text: Agent text response, may be sent multiple timestool_call: Agent starts executing a tooltool_result: Tool execution complete, includes resulterror: Error occurreddone: Entire conversation round complete
Without Authentication
If your app doesn't need user authentication, use anonymous mode:
route.ts
// src/app/api/lens/agent/chat/route.ts
export async function POST(req: Request) {
const body = await req.json();
// Use anonymous ID or get from cookie
const userId = body.userId || "anonymous";
return handler.POST(
new Request(req.url, {
method: "POST",
headers: req.headers,
body: JSON.stringify({ ...body, userId }),
})
);
}Error Handling
Recommended to add complete error handling:
route.ts
export async function POST(req: Request) {
try {
const session = await auth();
if (!session?.user) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 401 }
);
}
const body = await req.json();
// ... handler logic
} catch (error) {
console.error("[Chat API] Error:", error);
return new Response(
JSON.stringify({
error: error instanceof Error ? error.message : "Internal error",
}),
{ status: 500 }
);
}
}