@octavus/docs 0.0.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +127 -0
  2. package/content/01-getting-started/01-introduction.md +3 -3
  3. package/content/01-getting-started/02-quickstart.md +40 -17
  4. package/content/02-server-sdk/01-overview.md +54 -11
  5. package/content/02-server-sdk/02-sessions.md +166 -15
  6. package/content/02-server-sdk/03-tools.md +21 -21
  7. package/content/02-server-sdk/04-streaming.md +50 -20
  8. package/content/02-server-sdk/05-cli.md +247 -0
  9. package/content/03-client-sdk/01-overview.md +65 -35
  10. package/content/03-client-sdk/02-messages.md +116 -8
  11. package/content/03-client-sdk/03-streaming.md +36 -17
  12. package/content/03-client-sdk/04-execution-blocks.md +8 -12
  13. package/content/03-client-sdk/05-socket-transport.md +161 -45
  14. package/content/03-client-sdk/06-http-transport.md +48 -24
  15. package/content/03-client-sdk/07-structured-output.md +412 -0
  16. package/content/03-client-sdk/08-file-uploads.md +473 -0
  17. package/content/03-client-sdk/09-error-handling.md +274 -0
  18. package/content/04-protocol/01-overview.md +25 -14
  19. package/content/04-protocol/02-input-resources.md +35 -35
  20. package/content/04-protocol/03-triggers.md +9 -11
  21. package/content/04-protocol/04-tools.md +72 -29
  22. package/content/04-protocol/05-skills.md +304 -0
  23. package/content/04-protocol/06-handlers.md +304 -0
  24. package/content/04-protocol/07-agent-config.md +334 -0
  25. package/content/04-protocol/08-provider-options.md +294 -0
  26. package/content/04-protocol/09-skills-advanced.md +439 -0
  27. package/content/04-protocol/10-types.md +719 -0
  28. package/content/05-api-reference/01-overview.md +20 -21
  29. package/content/05-api-reference/02-sessions.md +192 -37
  30. package/content/05-api-reference/03-agents.md +25 -37
  31. package/content/06-examples/01-overview.md +6 -4
  32. package/content/06-examples/02-nextjs-chat.md +28 -18
  33. package/content/06-examples/03-socket-chat.md +53 -30
  34. package/content/06-examples/_meta.md +0 -1
  35. package/dist/chunk-WJ2W3DUC.js +663 -0
  36. package/dist/chunk-WJ2W3DUC.js.map +1 -0
  37. package/dist/content.js +1 -1
  38. package/dist/docs.json +106 -34
  39. package/dist/index.js +1 -1
  40. package/dist/search-index.json +1 -1
  41. package/dist/search.js +1 -1
  42. package/dist/search.js.map +1 -1
  43. package/dist/sections.json +106 -34
  44. package/package.json +12 -2
  45. package/content/04-protocol/05-handlers.md +0 -251
  46. package/content/04-protocol/06-agent-config.md +0 -209
  47. package/dist/chunk-232K4EME.js +0 -439
  48. package/dist/chunk-232K4EME.js.map +0 -1
  49. package/dist/chunk-2JDZLMS3.js +0 -439
  50. package/dist/chunk-2JDZLMS3.js.map +0 -1
  51. package/dist/chunk-2YMRODFE.js +0 -421
  52. package/dist/chunk-2YMRODFE.js.map +0 -1
  53. package/dist/chunk-2ZBPX5QB.js +0 -421
  54. package/dist/chunk-2ZBPX5QB.js.map +0 -1
  55. package/dist/chunk-3PIIST4D.js +0 -421
  56. package/dist/chunk-3PIIST4D.js.map +0 -1
  57. package/dist/chunk-42JETGDO.js +0 -421
  58. package/dist/chunk-42JETGDO.js.map +0 -1
  59. package/dist/chunk-4WWUKU4V.js +0 -421
  60. package/dist/chunk-4WWUKU4V.js.map +0 -1
  61. package/dist/chunk-5M7DS4DF.js +0 -519
  62. package/dist/chunk-5M7DS4DF.js.map +0 -1
  63. package/dist/chunk-6JQ3OMGF.js +0 -421
  64. package/dist/chunk-6JQ3OMGF.js.map +0 -1
  65. package/dist/chunk-7AOWCJHW.js +0 -421
  66. package/dist/chunk-7AOWCJHW.js.map +0 -1
  67. package/dist/chunk-7AS4ST73.js +0 -421
  68. package/dist/chunk-7AS4ST73.js.map +0 -1
  69. package/dist/chunk-7F5WOCIL.js +0 -421
  70. package/dist/chunk-7F5WOCIL.js.map +0 -1
  71. package/dist/chunk-7FPUAWSX.js +0 -421
  72. package/dist/chunk-7FPUAWSX.js.map +0 -1
  73. package/dist/chunk-APASMJBS.js +0 -421
  74. package/dist/chunk-APASMJBS.js.map +0 -1
  75. package/dist/chunk-BCEV3WV2.js +0 -421
  76. package/dist/chunk-BCEV3WV2.js.map +0 -1
  77. package/dist/chunk-CHGY4G27.js +0 -421
  78. package/dist/chunk-CHGY4G27.js.map +0 -1
  79. package/dist/chunk-CI7JDWKU.js +0 -421
  80. package/dist/chunk-CI7JDWKU.js.map +0 -1
  81. package/dist/chunk-CVFWWRL7.js +0 -421
  82. package/dist/chunk-CVFWWRL7.js.map +0 -1
  83. package/dist/chunk-EPDM2NIJ.js +0 -421
  84. package/dist/chunk-EPDM2NIJ.js.map +0 -1
  85. package/dist/chunk-ESGSYVGK.js +0 -421
  86. package/dist/chunk-ESGSYVGK.js.map +0 -1
  87. package/dist/chunk-GDCTM2SV.js +0 -421
  88. package/dist/chunk-GDCTM2SV.js.map +0 -1
  89. package/dist/chunk-GJ6FMIPD.js +0 -421
  90. package/dist/chunk-GJ6FMIPD.js.map +0 -1
  91. package/dist/chunk-H6JGSSAJ.js +0 -519
  92. package/dist/chunk-H6JGSSAJ.js.map +0 -1
  93. package/dist/chunk-IKQHGGUZ.js +0 -421
  94. package/dist/chunk-IKQHGGUZ.js.map +0 -1
  95. package/dist/chunk-IUKE3XDN.js +0 -421
  96. package/dist/chunk-IUKE3XDN.js.map +0 -1
  97. package/dist/chunk-J26MLMLN.js +0 -421
  98. package/dist/chunk-J26MLMLN.js.map +0 -1
  99. package/dist/chunk-J7BMB3ZW.js +0 -421
  100. package/dist/chunk-J7BMB3ZW.js.map +0 -1
  101. package/dist/chunk-JCBQRD5N.js +0 -421
  102. package/dist/chunk-JCBQRD5N.js.map +0 -1
  103. package/dist/chunk-JOB6YWEF.js +0 -421
  104. package/dist/chunk-JOB6YWEF.js.map +0 -1
  105. package/dist/chunk-JZRABTHU.js +0 -519
  106. package/dist/chunk-JZRABTHU.js.map +0 -1
  107. package/dist/chunk-K3GFQUMC.js +0 -421
  108. package/dist/chunk-K3GFQUMC.js.map +0 -1
  109. package/dist/chunk-LWYMRXBF.js +0 -421
  110. package/dist/chunk-LWYMRXBF.js.map +0 -1
  111. package/dist/chunk-M2R2NDPR.js +0 -421
  112. package/dist/chunk-M2R2NDPR.js.map +0 -1
  113. package/dist/chunk-MA3P7WCA.js +0 -421
  114. package/dist/chunk-MA3P7WCA.js.map +0 -1
  115. package/dist/chunk-MDMRCS4W.mjs +0 -421
  116. package/dist/chunk-MDMRCS4W.mjs.map +0 -1
  117. package/dist/chunk-MJXTA2R6.js +0 -421
  118. package/dist/chunk-MJXTA2R6.js.map +0 -1
  119. package/dist/chunk-NFVJQNDP.js +0 -421
  120. package/dist/chunk-NFVJQNDP.js.map +0 -1
  121. package/dist/chunk-O5TLYMQP.js +0 -421
  122. package/dist/chunk-O5TLYMQP.js.map +0 -1
  123. package/dist/chunk-OECAPVSX.js +0 -439
  124. package/dist/chunk-OECAPVSX.js.map +0 -1
  125. package/dist/chunk-OL5QDJ42.js +0 -483
  126. package/dist/chunk-OL5QDJ42.js.map +0 -1
  127. package/dist/chunk-PMOVVTHO.js +0 -519
  128. package/dist/chunk-PMOVVTHO.js.map +0 -1
  129. package/dist/chunk-QCHDPR2D.js +0 -421
  130. package/dist/chunk-QCHDPR2D.js.map +0 -1
  131. package/dist/chunk-R5MTVABN.js +0 -439
  132. package/dist/chunk-R5MTVABN.js.map +0 -1
  133. package/dist/chunk-RJ4H4YVA.js +0 -519
  134. package/dist/chunk-RJ4H4YVA.js.map +0 -1
  135. package/dist/chunk-S5U4IWCR.js +0 -439
  136. package/dist/chunk-S5U4IWCR.js.map +0 -1
  137. package/dist/chunk-SCKIOGKI.js +0 -421
  138. package/dist/chunk-SCKIOGKI.js.map +0 -1
  139. package/dist/chunk-TGJSIJYP.js +0 -421
  140. package/dist/chunk-TGJSIJYP.js.map +0 -1
  141. package/dist/chunk-TQZRBMU7.js +0 -421
  142. package/dist/chunk-TQZRBMU7.js.map +0 -1
  143. package/dist/chunk-TRL4RSEO.js +0 -421
  144. package/dist/chunk-TRL4RSEO.js.map +0 -1
  145. package/dist/chunk-TWUMRHQ7.js +0 -421
  146. package/dist/chunk-TWUMRHQ7.js.map +0 -1
  147. package/dist/chunk-UCJE36LL.js +0 -519
  148. package/dist/chunk-UCJE36LL.js.map +0 -1
  149. package/dist/chunk-VCASA6KL.js +0 -421
  150. package/dist/chunk-VCASA6KL.js.map +0 -1
  151. package/dist/chunk-VWPQ6ORV.js +0 -421
  152. package/dist/chunk-VWPQ6ORV.js.map +0 -1
  153. package/dist/chunk-WPXKIHLT.js +0 -421
  154. package/dist/chunk-WPXKIHLT.js.map +0 -1
  155. package/dist/chunk-WUNFFJ32.js +0 -421
  156. package/dist/chunk-WUNFFJ32.js.map +0 -1
  157. package/dist/chunk-WW7TRC7S.js +0 -519
  158. package/dist/chunk-WW7TRC7S.js.map +0 -1
  159. package/dist/chunk-XVSMRXBJ.js +0 -421
  160. package/dist/chunk-XVSMRXBJ.js.map +0 -1
  161. package/dist/chunk-YPPXXV3I.js +0 -421
  162. package/dist/chunk-YPPXXV3I.js.map +0 -1
  163. package/dist/chunk-ZKZVV4OQ.js +0 -421
  164. package/dist/chunk-ZKZVV4OQ.js.map +0 -1
  165. package/dist/chunk-ZOFEX73I.js +0 -421
  166. package/dist/chunk-ZOFEX73I.js.map +0 -1
  167. package/dist/content.mjs +0 -17
  168. package/dist/content.mjs.map +0 -1
  169. package/dist/index.mjs +0 -11
  170. package/dist/index.mjs.map +0 -1
  171. package/dist/search.mjs +0 -30
  172. package/dist/search.mjs.map +0 -1
  173. package/dist/types-BltYGlWI.d.ts +0 -36
package/dist/docs.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "section": "getting-started",
5
5
  "title": "Introduction",
6
6
  "description": "Overview of Octavus AI - an agent orchestration platform for developers.",
7
- "content": "\n# Introduction to Octavus\n\nOctavus is an agent orchestration platform that lets developers define, manage, and deploy AI agents through a unified service. It handles the orchestration layer so teams can focus on their agent logic and business requirements.\n\n## What is Octavus?\n\nBuilding and managing AI agents is complex. Developers face challenges with:\n\n- **Fragmented tooling** — No unified way to define, manage, and deploy agents\n- **Prompt management** — Prompts are scattered across codebases, hard to version and iterate\n- **Integration complexity** — Connecting agents to tools, resources, and other agents requires significant custom work\n- **Observability** — Difficult to debug, monitor, and understand agent behavior in production\n- **Infrastructure overhead** — Teams rebuild the same agent infrastructure repeatedly\n\nOctavus solves these problems by providing:\n\n- A **protocol-based approach** to defining agent behavior\n- **Server and client SDKs** for easy integration\n- **Built-in streaming** support for real-time responses\n- **Tool execution** that runs on your servers with your data\n- **Session management** for stateful conversations\n\n## Core Concepts\n\n### Agents\n\nAn **Agent** is the main entity in Octavus — a self-contained unit that defines how an AI agent behaves. Agents are defined using YAML protocols that specify:\n\n- Input variables the agent accepts\n- Triggers that invoke the agent (user messages, button clicks, API calls)\n- Tools the agent can use\n- Handlers that define execution flow\n\n### Sessions\n\nA **Session** represents a conversation with an agent. Sessions:\n\n- Store conversation history\n- Track resources and variables\n- Enable stateful interactions across multiple messages\n\n### Triggers\n\n**Triggers** define how an agent is invoked:\n\n- **User Message** — Respond to a text message in a chat interface\n- **User Action** — Respond to UI actions (button clicks, form submissions)\n- **API Call** — Direct invocation via SDK\n\n### Tools\n\n**Tools** extend what agents can do:\n\n- **External Tools** — Consumer-defined tools that call back to your systems\n- **Internal Tools** — Built-in capabilities (web search, code execution)\n\nTools execute on your server, not on Octavus, giving you full control over data and authentication.\n\n## Architecture Overview\n\n```mermaid\nsequenceDiagram\n participant App as Your App\n participant API as Octavus API\n participant LLM as LLM Provider\n\n App->>API: 1. Trigger action\n API->>LLM: 2. Execute protocol\n LLM-->>API: Response\n API-->>App: 3. Stream events\n \n Note over App: Tool request received\n API-->>App: 4. Tool request\n Note over App: 5. Execute tool locally\n App->>API: 6. Return results\n \n API->>LLM: 7. Continue\n LLM-->>API: Response\n API-->>App: 8. Stream response\n```\n\n## Next Steps\n\n- [Quick Start](/docs/getting-started/quickstart) — Get your first agent running in minutes\n- [Server SDK](/docs/server-sdk/overview) — Learn about backend integration\n- [Client SDK](/docs/client-sdk/overview) — Build chat interfaces with React (or other frameworks)\n- [Protocol Reference](/docs/protocol/overview) — Deep dive into agent protocols\n\n",
7
+ "content": "\n# Introduction to Octavus\n\nOctavus is an agent orchestration platform that lets developers define, manage, and deploy AI agents through a unified service. It handles the orchestration layer so teams can focus on their agent logic and business requirements.\n\n## What is Octavus?\n\nBuilding and managing AI agents is complex. Developers face challenges with:\n\n- **Fragmented tooling** — No unified way to define, manage, and deploy agents\n- **Prompt management** — Prompts are scattered across codebases, hard to version and iterate\n- **Integration complexity** — Connecting agents to tools, resources, and other agents requires significant custom work\n- **Observability** — Difficult to debug, monitor, and understand agent behavior in production\n- **Infrastructure overhead** — Teams rebuild the same agent infrastructure repeatedly\n\nOctavus solves these problems by providing:\n\n- A **protocol-based approach** to defining agent behavior\n- **Agent Preview** for testing agents directly in the platform\n- **Server and client SDKs** for easy integration\n- **Built-in streaming** support for real-time responses\n- **Tool execution** that runs on your servers with your data\n- **Session management** for stateful conversations\n\n## Core Concepts\n\n### Agents\n\nAn **Agent** is the main entity in Octavus — a self-contained unit that defines how an AI agent behaves. Agents are defined using YAML protocols that specify:\n\n- Input variables the agent accepts\n- Triggers that invoke the agent (user messages, button clicks, API calls)\n- Tools the agent can use\n- Handlers that define execution flow\n\n### Sessions\n\nA **Session** represents a conversation with an agent. Sessions:\n\n- Store conversation history\n- Track resources and variables\n- Enable stateful interactions across multiple messages\n\n### Triggers\n\n**Triggers** define how an agent is invoked:\n\n- **User Message** — Respond to a text message in a chat interface\n- **User Action** — Respond to UI actions (button clicks, form submissions)\n- **API Call** — Direct invocation via SDK\n\n### Tools\n\n**Tools** extend what agents can do:\n\n- **External Tools** — Consumer-defined tools that call back to your systems\n- **Internal Tools** — Built-in capabilities (web search, code execution)\n\nTools execute on your server, not on Octavus, giving you full control over data and authentication.\n\n## Architecture Overview\n\n```mermaid\nsequenceDiagram\n participant App as Your App\n participant API as Octavus API\n participant LLM as LLM Provider\n\n App->>API: 1. Trigger action\n API->>LLM: 2. Execute protocol\n LLM-->>API: Response\n API-->>App: 3. Stream events\n\n Note over App: Tool request received\n API-->>App: 4. Tool request\n Note over App: 5. Execute tool locally\n App->>API: 6. Return results\n\n API->>LLM: 7. Continue\n LLM-->>API: Response\n API-->>App: 8. Stream response\n```\n\n## Next Steps\n\n- [Quick Start](/docs/getting-started/quickstart) — Get your first agent running in minutes\n- [Server SDK](/docs/server-sdk/overview) — Learn about backend integration\n- [Client SDK](/docs/client-sdk/overview) — Build chat interfaces with React (or other frameworks)\n- [Protocol Reference](/docs/protocol/overview) — Deep dive into agent protocols\n",
8
8
  "excerpt": "Introduction to Octavus Octavus is an agent orchestration platform that lets developers define, manage, and deploy AI agents through a unified service. It handles the orchestration layer so teams can...",
9
9
  "order": 1
10
10
  },
@@ -13,7 +13,7 @@
13
13
  "section": "getting-started",
14
14
  "title": "Quick Start",
15
15
  "description": "Get your first Octavus agent running in minutes.",
16
- "content": "\n# Quick Start\n\nThis guide will walk you through integrating Octavus into your application in under 10 minutes.\n\n## Prerequisites\n\n- Node.js 18+\n- An Octavus account with API key\n- A Next.js application (or any Node.js backend)\n\n## Installation\n\nInstall the Octavus SDKs in your project:\n\n```bash\n# Server SDK for backend\nnpm install @octavus/server-sdk\n\n# React bindings for frontend\nnpm install @octavus/react\n```\n\n## Backend Setup\n\n### 1. Initialize the Client\n\nCreate an Octavus client instance in your backend:\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n### 2. Create a Session Endpoint\n\nCreate an API endpoint that creates sessions and returns the session ID:\n\n```typescript\n// app/api/chat/create/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { agentSlug, input } = await request.json();\n\n // Look up agent by slug to get its ID\n const agent = await octavus.agents.getBySlug(agentSlug);\n if (!agent) {\n return NextResponse.json({ error: 'Agent not found' }, { status: 404 });\n }\n\n // Create a new session using the agent ID\n const sessionId = await octavus.agentSessions.create(agent.id, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n### 3. Create a Trigger Endpoint\n\nCreate an endpoint that handles triggers and streams responses:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { sessionId, triggerName, input } = await request.json();\n\n // Attach to session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Define tool handlers that run on your server\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n return {\n name: 'Demo User',\n email: 'demo@example.com',\n plan: 'pro',\n };\n },\n 'create-support-ticket': async (args) => {\n // Create ticket in your system\n return {\n ticketId: 'TICKET-123',\n estimatedResponse: '24 hours',\n };\n },\n },\n });\n\n // Trigger the action and convert to SSE stream\n const events = session.trigger(triggerName, input);\n\n // Return as streaming response\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n```\n\n## Frontend Setup\n\n### 1. Create a Chat Component\n\nUse the `useOctavusChat` hook with the HTTP transport:\n\n```tsx\n// components/chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create a stable transport instance\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, error, send } = useOctavusChat({ transport });\n\n const isStreaming = status === 'streaming';\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || isStreaming) return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Add user message and trigger in one call\n await send(\n 'user-message',\n { USER_MESSAGE: message },\n { userMessage: { content: message } },\n );\n };\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={isStreaming}\n />\n <button\n type=\"submit\"\n disabled={isStreaming}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg disabled:opacity-50\"\n >\n Send\n </button>\n </div>\n </form>\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n const isUser = message.role === 'user';\n\n return (\n <div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>\n <div\n className={`p-3 rounded-lg max-w-md ${\n isUser ? 'bg-blue-500 text-white' : 'bg-gray-100'\n }`}\n >\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n\n {/* Streaming indicator */}\n {message.status === 'streaming' && (\n <span className=\"inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1\" />\n )}\n </div>\n </div>\n );\n}\n```\n\n### 2. Create Session and Render Chat\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/chat';\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n async function createSession() {\n const response = await fetch('/api/chat/create', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentSlug: 'support-chat',\n input: {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n },\n }),\n });\n const { sessionId } = await response.json();\n setSessionId(sessionId);\n }\n\n createSession();\n }, []);\n\n if (!sessionId) {\n return <div>Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Environment Variables\n\nAdd these to your `.env.local`:\n\n```bash\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key-here\n```\n\n## What's Next?\n\nNow that you have a basic integration working:\n\n- [Learn about the protocol](/docs/protocol/overview) to define custom agent behavior\n- [Explore the Server SDK](/docs/server-sdk/overview) for advanced backend features\n- [Build rich UIs](/docs/client-sdk/overview) with the Client SDK\n",
16
+ "content": "\n# Quick Start\n\nThis guide will walk you through integrating Octavus into your application in under 10 minutes.\n\n## Prerequisites\n\n- Node.js 18+\n- An Octavus account with API key\n- A Next.js application (or any Node.js backend)\n\n## Test Your Agent First\n\nBefore integrating with SDKs, use **Agent Preview** to test your agent directly in the platform:\n\n1. Open your agent in the platform at `octavus.ai/agents/[agentId]`\n2. Click the **Preview** tab\n3. Configure session inputs and tool mock responses\n4. Start a conversation to test agent behavior\n\nAgent Preview supports all trigger types, file attachments, tool mocking, and real-time streaming. This is the fastest way to iterate on your agent logic before writing any integration code.\n\n## Installation\n\nInstall the Octavus SDKs in your project:\n\n```bash\n# Server SDK for backend\nnpm install @octavus/server-sdk\n\n# React bindings for frontend\nnpm install @octavus/react\n```\n\n## Backend Setup\n\n### 1. Initialize the Client\n\nCreate an Octavus client instance in your backend:\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n### 2. Create a Session Endpoint\n\nCreate an API endpoint that creates sessions and returns the session ID:\n\n```typescript\n// app/api/chat/create/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\n// Agent ID - get from platform or CLI (see below)\nconst SUPPORT_AGENT_ID = process.env.OCTAVUS_SUPPORT_AGENT_ID!;\n\nexport async function POST(request: Request) {\n const { input } = await request.json();\n\n // Create a new session using the agent ID\n const sessionId = await octavus.agentSessions.create(SUPPORT_AGENT_ID, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n### Getting Your Agent ID\n\nThere are two ways to create and manage agents:\n\n**Option 1: Platform UI (Recommended for getting started)**\n\n1. Go to [octavus.ai](https://octavus.ai) and create an agent in the web editor\n2. Copy the agent ID from the URL (e.g., `octavus.ai/agents/clxyz123abc456`)\n3. Add it to your `.env.local`: `OCTAVUS_SUPPORT_AGENT_ID=clxyz123abc456`\n\n**Option 2: Local Development with CLI**\n\nFor version-controlled agent definitions, use the [Octavus CLI](/docs/server-sdk/cli):\n\n```bash\nnpm install --save-dev @octavus/cli\noctavus sync ./agents/support-chat\n# Output: Agent ID: clxyz123abc456\n```\n\nThe CLI approach is better for teams and CI/CD pipelines where you want agent definitions in your repository.\n\n### 3. Create a Trigger Endpoint\n\nCreate an endpoint that handles triggers and streams responses:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { sessionId, triggerName, input } = await request.json();\n\n // Attach to session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Define tool handlers that run on your server\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n return {\n name: 'Demo User',\n email: 'demo@example.com',\n plan: 'pro',\n };\n },\n 'create-support-ticket': async (args) => {\n // Create ticket in your system\n return {\n ticketId: 'TICKET-123',\n estimatedResponse: '24 hours',\n };\n },\n },\n });\n\n // Trigger the action and convert to SSE stream\n const events = session.trigger(triggerName, input);\n\n // Return as streaming response\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n```\n\n## Frontend Setup\n\n### 1. Create a Chat Component\n\nUse the `useOctavusChat` hook with the HTTP transport:\n\n```tsx\n// components/chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create a stable transport instance\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, error, send } = useOctavusChat({ transport });\n\n const isStreaming = status === 'streaming';\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || isStreaming) return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Add user message and trigger in one call\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={isStreaming}\n />\n <button\n type=\"submit\"\n disabled={isStreaming}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg disabled:opacity-50\"\n >\n Send\n </button>\n </div>\n </form>\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n const isUser = message.role === 'user';\n\n return (\n <div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>\n <div\n className={`p-3 rounded-lg max-w-md ${isUser ? 'bg-blue-500 text-white' : 'bg-gray-100'}`}\n >\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n\n {/* Streaming indicator */}\n {message.status === 'streaming' && (\n <span className=\"inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1\" />\n )}\n </div>\n </div>\n );\n}\n```\n\n### 2. Create Session and Render Chat\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/chat';\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n async function createSession() {\n const response = await fetch('/api/chat/create', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n input: {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n },\n }),\n });\n const { sessionId } = await response.json();\n setSessionId(sessionId);\n }\n\n createSession();\n }, []);\n\n if (!sessionId) {\n return <div>Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Environment Variables\n\nAdd these to your `.env.local`:\n\n```bash\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key-here\n```\n\n## What's Next?\n\nNow that you have a basic integration working:\n\n- [Learn about the protocol](/docs/protocol/overview) to define custom agent behavior\n- [Explore the Server SDK](/docs/server-sdk/overview) for advanced backend features\n- [Build rich UIs](/docs/client-sdk/overview) with the Client SDK\n",
17
17
  "excerpt": "Quick Start This guide will walk you through integrating Octavus into your application in under 10 minutes. Prerequisites - Node.js 18+ - An Octavus account with API key - A Next.js application (or...",
18
18
  "order": 2
19
19
  },
@@ -22,7 +22,7 @@
22
22
  "section": "server-sdk",
23
23
  "title": "Overview",
24
24
  "description": "Introduction to the Octavus Server SDK for backend integration.",
25
- "content": "\n# Server SDK Overview\n\nThe `@octavus/server-sdk` package provides a Node.js SDK for integrating Octavus agents into your backend application. It handles session management, streaming, and the tool execution continuation loop.\n\n**Current version:** `0.0.8`\n\n## Installation\n\n```bash\nnpm install @octavus/server-sdk\n```\n\n## Basic Usage\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: 'https://octavus.ai',\n apiKey: 'your-api-key',\n});\n```\n\n## Key Features\n\n### Session Management\n\nCreate and manage agent sessions:\n\n```typescript\n// Create a new session\nconst sessionId = await client.agentSessions.create('agent-id', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n});\n\n// Get UI-ready session messages (for session restore)\nconst session = await client.agentSessions.getMessages(sessionId);\n```\n\n### Tool Handlers\n\nTools run on your server with your data:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n // Access your database, APIs, etc.\n return await db.users.findById(args.userId);\n },\n },\n});\n```\n\n### Streaming\n\nAll responses stream in real-time:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// trigger() returns an async generator of events\nconst events = session.trigger('user-message', {\n USER_MESSAGE: 'Hello!',\n});\n\n// Convert to SSE stream for HTTP responses\nreturn new Response(toSSEStream(events), {\n headers: { 'Content-Type': 'text/event-stream' },\n});\n```\n\n## API Reference\n\n### OctavusClient\n\nThe main entry point for interacting with Octavus.\n\n```typescript\ninterface OctavusClientConfig {\n baseUrl: string; // Octavus API URL\n apiKey?: string; // Your API key\n}\n\nclass OctavusClient {\n readonly agents: AgentsApi;\n readonly agentSessions: AgentSessionsApi;\n\n constructor(config: OctavusClientConfig);\n}\n```\n\n### AgentSessionsApi\n\nManages agent sessions.\n\n```typescript\nclass AgentSessionsApi {\n // Create a new session\n async create(agentId: string, input?: Record<string, unknown>): Promise<string>;\n\n // Get full session state (for debugging/internal use)\n async get(sessionId: string): Promise<SessionState>;\n\n // Get UI-ready messages (for client display)\n async getMessages(sessionId: string): Promise<UISessionState>;\n\n // Attach to a session for triggering\n attach(sessionId: string, options?: SessionAttachOptions): AgentSession;\n}\n\n// Full session state (internal format)\ninterface SessionState {\n id: string;\n agentId: string;\n input: Record<string, unknown>;\n variables: Record<string, unknown>;\n resources: Record<string, unknown>;\n messages: ChatMessage[]; // Internal message format\n createdAt: string;\n updatedAt: string;\n}\n\n// UI-ready session state\ninterface UISessionState {\n sessionId: string;\n agentId: string;\n messages: UIMessage[]; // UI-ready messages for frontend\n}\n```\n\n### AgentSession\n\nHandles triggering and streaming for a specific session.\n\n```typescript\nclass AgentSession {\n // Trigger an action and stream parsed events\n trigger(\n triggerName: string,\n input?: Record<string, unknown>\n ): AsyncGenerator<StreamEvent>;\n\n // Get the session ID\n getSessionId(): string;\n}\n\n// Helper to convert events to SSE stream\nfunction toSSEStream(events: AsyncIterable<StreamEvent>): ReadableStream<Uint8Array>;\n```\n\n## Next Steps\n\n- [Sessions](/docs/server-sdk/sessions) — Deep dive into session management\n- [Tools](/docs/server-sdk/tools) — Implementing tool handlers\n- [Streaming](/docs/server-sdk/streaming) — Understanding stream events\n",
25
+ "content": "\n# Server SDK Overview\n\nThe `@octavus/server-sdk` package provides a Node.js SDK for integrating Octavus agents into your backend application. It handles session management, streaming, and the tool execution continuation loop.\n\n**Current version:** `1.0.0`\n\n## Installation\n\n```bash\nnpm install @octavus/server-sdk\n```\n\nFor agent management (sync, validate), install the CLI as a dev dependency:\n\n```bash\nnpm install --save-dev @octavus/cli\n```\n\n## Basic Usage\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: 'https://octavus.ai',\n apiKey: 'your-api-key',\n});\n```\n\n## Key Features\n\n### Agent Management\n\nAgent definitions are managed via the CLI. See the [CLI documentation](/docs/server-sdk/cli) for details.\n\n```bash\n# Sync agent from local files\noctavus sync ./agents/support-chat\n\n# Output: Created: support-chat\n# Agent ID: clxyz123abc456\n```\n\n### Session Management\n\nCreate and manage agent sessions using the agent ID:\n\n```typescript\n// Create a new session (use agent ID from CLI sync)\nconst sessionId = await client.agentSessions.create('clxyz123abc456', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n});\n\n// Get UI-ready session messages (for session restore)\nconst session = await client.agentSessions.getMessages(sessionId);\n```\n\n### Tool Handlers\n\nTools run on your server with your data:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n // Access your database, APIs, etc.\n return await db.users.findById(args.userId);\n },\n },\n});\n```\n\n### Streaming\n\nAll responses stream in real-time:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// trigger() returns an async generator of events\nconst events = session.trigger('user-message', {\n USER_MESSAGE: 'Hello!',\n});\n\n// Convert to SSE stream for HTTP responses\nreturn new Response(toSSEStream(events), {\n headers: { 'Content-Type': 'text/event-stream' },\n});\n```\n\n## API Reference\n\n### OctavusClient\n\nThe main entry point for interacting with Octavus.\n\n```typescript\ninterface OctavusClientConfig {\n baseUrl: string; // Octavus API URL\n apiKey?: string; // Your API key\n}\n\nclass OctavusClient {\n readonly agents: AgentsApi;\n readonly agentSessions: AgentSessionsApi;\n readonly files: FilesApi;\n\n constructor(config: OctavusClientConfig);\n}\n```\n\n### AgentSessionsApi\n\nManages agent sessions.\n\n```typescript\nclass AgentSessionsApi {\n // Create a new session\n async create(agentId: string, input?: Record<string, unknown>): Promise<string>;\n\n // Get full session state (for debugging/internal use)\n async get(sessionId: string): Promise<SessionState>;\n\n // Get UI-ready messages (for client display)\n async getMessages(sessionId: string): Promise<UISessionState>;\n\n // Attach to a session for triggering\n attach(sessionId: string, options?: SessionAttachOptions): AgentSession;\n}\n\n// Full session state (internal format)\ninterface SessionState {\n id: string;\n agentId: string;\n input: Record<string, unknown>;\n variables: Record<string, unknown>;\n resources: Record<string, unknown>;\n messages: ChatMessage[]; // Internal message format\n createdAt: string;\n updatedAt: string;\n}\n\n// UI-ready session state\ninterface UISessionState {\n sessionId: string;\n agentId: string;\n messages: UIMessage[]; // UI-ready messages for frontend\n}\n```\n\n### AgentSession\n\nHandles triggering and streaming for a specific session.\n\n```typescript\nclass AgentSession {\n // Trigger an action and stream parsed events\n trigger(triggerName: string, input?: Record<string, unknown>): AsyncGenerator<StreamEvent>;\n\n // Get the session ID\n getSessionId(): string;\n}\n\n// Helper to convert events to SSE stream\nfunction toSSEStream(events: AsyncIterable<StreamEvent>): ReadableStream<Uint8Array>;\n```\n\n### FilesApi\n\nHandles file uploads for sessions.\n\n```typescript\nclass FilesApi {\n // Get presigned URLs for file uploads\n async getUploadUrls(sessionId: string, files: FileUploadRequest[]): Promise<UploadUrlsResponse>;\n}\n\ninterface FileUploadRequest {\n filename: string;\n mediaType: string;\n size: number;\n}\n\ninterface UploadUrlsResponse {\n files: {\n id: string; // File ID for references\n uploadUrl: string; // PUT to this URL\n downloadUrl: string; // GET URL after upload\n }[];\n}\n```\n\nThe client uploads files directly to S3 using the presigned upload URL. See [File Uploads](/docs/client-sdk/file-uploads) for the full integration pattern.\n\n## Next Steps\n\n- [Sessions](/docs/server-sdk/sessions) — Deep dive into session management\n- [Tools](/docs/server-sdk/tools) — Implementing tool handlers\n- [Streaming](/docs/server-sdk/streaming) — Understanding stream events\n",
26
26
  "excerpt": "Server SDK Overview The package provides a Node.js SDK for integrating Octavus agents into your backend application. It handles session management, streaming, and the tool execution continuation...",
27
27
  "order": 1
28
28
  },
@@ -31,7 +31,7 @@
31
31
  "section": "server-sdk",
32
32
  "title": "Sessions",
33
33
  "description": "Managing agent sessions with the Server SDK.",
34
- "content": "\n# Sessions\n\nSessions represent conversations with an agent. They store conversation history, track resources and variables, and enable stateful interactions.\n\n## Creating Sessions\n\nCreate a session by specifying the agent ID and initial input variables:\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\n// Create a session with the support-chat agent\nconst sessionId = await client.agentSessions.create('support-chat', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n USER_ID: 'user-123', // Optional inputs\n});\n\nconsole.log('Session created:', sessionId);\n```\n\n## Getting Session Messages\n\nTo restore a conversation on page load, use `getMessages()` to retrieve UI-ready messages:\n\n```typescript\nconst session = await client.agentSessions.getMessages(sessionId);\n\nconsole.log({\n sessionId: session.sessionId,\n agentId: session.agentId,\n messages: session.messages.length, // UIMessage[] ready for frontend\n});\n```\n\nThe returned messages can be passed directly to the client SDK's `initialMessages` option.\n\n### UISessionState Interface\n\n```typescript\ninterface UISessionState {\n sessionId: string;\n agentId: string;\n messages: UIMessage[]; // UI-ready conversation history\n}\n```\n\n## Full Session State (Debug)\n\nFor debugging or internal use, you can retrieve the complete session state including all variables and internal message format:\n\n```typescript\nconst state = await client.agentSessions.get(sessionId);\n\nconsole.log({\n id: state.id,\n agentId: state.agentId,\n messages: state.messages.length, // ChatMessage[] (internal format)\n resources: state.resources,\n variables: state.variables,\n createdAt: state.createdAt,\n updatedAt: state.updatedAt,\n});\n```\n\n> **Note**: Use `getMessages()` for client-facing code. The `get()` method returns internal message format that includes hidden content not intended for end users.\n\n## Attaching to Sessions\n\nTo trigger actions on a session, you need to attach to it first:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n // Tool handlers (see Tools documentation)\n },\n resources: [\n // Resource watchers (optional)\n ],\n});\n```\n\n## Triggering Actions\n\nOnce attached, trigger actions on the session:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// trigger() returns an async generator of events\nconst events = session.trigger('user-message', {\n USER_MESSAGE: 'How do I reset my password?',\n});\n\n// Convert to SSE stream for HTTP responses\nreturn new Response(toSSEStream(events), {\n headers: { 'Content-Type': 'text/event-stream' },\n});\n```\n\n## Session Lifecycle\n\n```mermaid\nflowchart TD\n A[1. CREATE] --> B[2. ATTACH]\n B --> C[3. TRIGGER]\n C --> C\n C --> D[4. RETRIEVE]\n D --> C\n C --> E[5. EXPIRE]\n\n A -.- A1[\"`**client.agentSessions.create()**\n Returns sessionId\n Initializes state`\"]\n \n B -.- B1[\"`**client.agentSessions.attach()**\n Configure tool handlers\n Configure resource watchers`\"]\n \n C -.- C1[\"`**session.trigger()**\n Execute handler\n Stream events\n Update state`\"]\n \n D -.- D1[\"`**client.agentSessions.getMessages()**\n Get UI-ready messages\n Restore conversation`\"]\n \n E -.- E1[\"`Sessions expire after\n 24 hours (configurable)`\"]\n```\n\n## Restoring Sessions\n\nWhen a user returns to your app, restore their session:\n\n```typescript\n// On page load\nconst session = await client.agentSessions.getMessages(sessionId);\n\n// Pass messages to frontend - they're already UI-ready\nreturn {\n sessionId: session.sessionId,\n messages: session.messages, // UIMessage[] with parts\n};\n```\n\nThe `messages` array contains `UIMessage` objects with properly structured `parts`, enabling direct use with the client SDK's `initialMessages` option.\n\n## Error Handling\n\n```typescript\nimport { ApiError } from '@octavus/server-sdk';\n\ntry {\n const session = await client.agentSessions.getMessages(sessionId);\n} catch (error) {\n if (error instanceof ApiError) {\n if (error.status === 404) {\n // Session not found or expired\n console.log('Session expired, create a new one');\n } else {\n console.error('API Error:', error.message);\n }\n }\n throw error;\n}\n```\n",
34
+ "content": "\n# Sessions\n\nSessions represent conversations with an agent. They store conversation history, track resources and variables, and enable stateful interactions.\n\n## Creating Sessions\n\nCreate a session by specifying the agent ID and initial input variables:\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\n// Create a session with the support-chat agent\nconst sessionId = await client.agentSessions.create('support-chat', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n USER_ID: 'user-123', // Optional inputs\n});\n\nconsole.log('Session created:', sessionId);\n```\n\n## Getting Session Messages\n\nTo restore a conversation on page load, use `getMessages()` to retrieve UI-ready messages:\n\n```typescript\nconst session = await client.agentSessions.getMessages(sessionId);\n\nconsole.log({\n sessionId: session.sessionId,\n agentId: session.agentId,\n messages: session.messages.length, // UIMessage[] ready for frontend\n});\n```\n\nThe returned messages can be passed directly to the client SDK's `initialMessages` option.\n\n### UISessionState Interface\n\n```typescript\ninterface UISessionState {\n sessionId: string;\n agentId: string;\n messages: UIMessage[]; // UI-ready conversation history\n}\n```\n\n## Full Session State (Debug)\n\nFor debugging or internal use, you can retrieve the complete session state including all variables and internal message format:\n\n```typescript\nconst state = await client.agentSessions.get(sessionId);\n\nconsole.log({\n id: state.id,\n agentId: state.agentId,\n messages: state.messages.length, // ChatMessage[] (internal format)\n resources: state.resources,\n variables: state.variables,\n createdAt: state.createdAt,\n updatedAt: state.updatedAt,\n});\n```\n\n> **Note**: Use `getMessages()` for client-facing code. The `get()` method returns internal message format that includes hidden content not intended for end users.\n\n## Attaching to Sessions\n\nTo trigger actions on a session, you need to attach to it first:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n // Tool handlers (see Tools documentation)\n },\n resources: [\n // Resource watchers (optional)\n ],\n});\n```\n\n## Triggering Actions\n\nOnce attached, trigger actions on the session:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// trigger() returns an async generator of events\nconst events = session.trigger('user-message', {\n USER_MESSAGE: 'How do I reset my password?',\n});\n\n// Convert to SSE stream for HTTP responses\nreturn new Response(toSSEStream(events), {\n headers: { 'Content-Type': 'text/event-stream' },\n});\n```\n\n### Stop Support\n\nPass an abort signal to allow clients to stop generation:\n\n```typescript\n// In your API route handler\nconst events = session.trigger(triggerName, input, {\n signal: request.signal, // Forward the client's abort signal\n});\n```\n\nWhen the client aborts the request, the signal propagates through to the LLM provider, stopping generation immediately. Any partial content is preserved.\n\n## Session Lifecycle\n\n```mermaid\nflowchart TD\n A[1. CREATE] --> B[2. ATTACH]\n B --> C[3. TRIGGER]\n C --> C\n C --> D[4. RETRIEVE]\n D --> C\n C --> E[5. EXPIRE]\n E --> F{6. RESTORE?}\n F -->|Yes| C\n F -->|No| A\n\n A -.- A1[\"`**client.agentSessions.create()**\n Returns sessionId\n Initializes state`\"]\n\n B -.- B1[\"`**client.agentSessions.attach()**\n Configure tool handlers\n Configure resource watchers`\"]\n\n C -.- C1[\"`**session.trigger()**\n Execute handler\n Stream events\n Update state`\"]\n\n D -.- D1[\"`**client.agentSessions.getMessages()**\n Get UI-ready messages\n Check session status`\"]\n\n E -.- E1[\"`Sessions expire after\n 24 hours (configurable)`\"]\n\n F -.- F1[\"`**client.agentSessions.restore()**\n Restore from stored messages\n Or create new session`\"]\n```\n\n## Session Expiration\n\nSessions expire after a period of inactivity (default: 24 hours). When you call `getMessages()` or `get()`, the response includes a `status` field:\n\n```typescript\nconst result = await client.agentSessions.getMessages(sessionId);\n\nif (result.status === 'expired') {\n // Session has expired - restore or create new\n console.log('Session expired:', result.sessionId);\n} else {\n // Session is active\n console.log('Messages:', result.messages.length);\n}\n```\n\n### Response Types\n\n| Status | Type | Description |\n| --------- | --------------------- | ------------------------------------------------------------- |\n| `active` | `UISessionState` | Session is active, includes `messages` array |\n| `expired` | `ExpiredSessionState` | Session expired, includes `sessionId`, `agentId`, `createdAt` |\n\n## Persisting Chat History\n\nTo enable session restoration, store the chat messages in your own database after each interaction:\n\n```typescript\n// After each trigger completes, save messages\nconst result = await client.agentSessions.getMessages(sessionId);\n\nif (result.status === 'active') {\n // Store in your database\n await db.chats.update({\n where: { id: chatId },\n data: {\n sessionId: result.sessionId,\n messages: result.messages, // Store UIMessage[] as JSON\n },\n });\n}\n```\n\n> **Best Practice**: Store the full `UIMessage[]` array. This preserves all message parts (text, tool calls, files, etc.) needed for accurate restoration.\n\n## Restoring Sessions\n\nWhen a user returns to your app:\n\n```typescript\n// 1. Load stored data from your database\nconst chat = await db.chats.findUnique({ where: { id: chatId } });\n\n// 2. Check if session is still active\nconst result = await client.agentSessions.getMessages(chat.sessionId);\n\nif (result.status === 'active') {\n // Session is active - use it directly\n return {\n sessionId: result.sessionId,\n messages: result.messages,\n };\n}\n\n// 3. Session expired - restore from stored messages\nif (chat.messages && chat.messages.length > 0) {\n const restored = await client.agentSessions.restore(\n chat.sessionId,\n chat.messages,\n { COMPANY_NAME: 'Acme Corp' }, // Optional: same input as create()\n );\n\n if (restored.restored) {\n // Session restored successfully\n return {\n sessionId: restored.sessionId,\n messages: chat.messages,\n };\n }\n}\n\n// 4. Cannot restore - create new session\nconst newSessionId = await client.agentSessions.create('support-chat', {\n COMPANY_NAME: 'Acme Corp',\n});\n\nreturn {\n sessionId: newSessionId,\n messages: [],\n};\n```\n\n### Restore Response\n\n```typescript\ninterface RestoreSessionResult {\n sessionId: string;\n restored: boolean; // true if restored, false if session was already active\n}\n```\n\n## Complete Example\n\nHere's a complete session management flow:\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nasync function getOrCreateSession(chatId: string, agentId: string, input: Record<string, unknown>) {\n // Load existing chat data\n const chat = await db.chats.findUnique({ where: { id: chatId } });\n\n if (chat?.sessionId) {\n // Check session status\n const result = await client.agentSessions.getMessages(chat.sessionId);\n\n if (result.status === 'active') {\n return { sessionId: result.sessionId, messages: result.messages };\n }\n\n // Try to restore expired session\n if (chat.messages?.length > 0) {\n const restored = await client.agentSessions.restore(chat.sessionId, chat.messages, input);\n if (restored.restored) {\n return { sessionId: restored.sessionId, messages: chat.messages };\n }\n }\n }\n\n // Create new session\n const sessionId = await client.agentSessions.create(agentId, input);\n\n // Save to database\n await db.chats.upsert({\n where: { id: chatId },\n create: { id: chatId, sessionId, messages: [] },\n update: { sessionId, messages: [] },\n });\n\n return { sessionId, messages: [] };\n}\n```\n\n## Error Handling\n\n```typescript\nimport { ApiError } from '@octavus/server-sdk';\n\ntry {\n const session = await client.agentSessions.getMessages(sessionId);\n} catch (error) {\n if (error instanceof ApiError) {\n if (error.status === 404) {\n // Session not found or expired\n console.log('Session expired, create a new one');\n } else {\n console.error('API Error:', error.message);\n }\n }\n throw error;\n}\n```\n",
35
35
  "excerpt": "Sessions Sessions represent conversations with an agent. They store conversation history, track resources and variables, and enable stateful interactions. Creating Sessions Create a session by...",
36
36
  "order": 2
37
37
  },
@@ -40,7 +40,7 @@
40
40
  "section": "server-sdk",
41
41
  "title": "Tools",
42
42
  "description": "Implementing tool handlers with the Server SDK.",
43
- "content": "\n# Tools\n\nTools extend what agents can do. In Octavus, tools execute on your server, giving you full control over data access and authentication.\n\n## Why Tools Run on Your Server\n\nUnlike traditional AI platforms where tools run in a sandbox, Octavus tools execute in your backend:\n\n- ✅ **Full data access** — Query your database directly\n- ✅ **Your authentication** — Use your existing auth context\n- ✅ **No data exposure** — Sensitive data never leaves your infrastructure\n- ✅ **Custom logic** — Any complexity you need\n\n## Defining Tool Handlers\n\nTool handlers are async functions that receive arguments and return results:\n\n```typescript\nimport type { ToolHandlers } from '@octavus/server-sdk';\n\nconst tools: ToolHandlers = {\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n \n // Query your database\n const user = await db.users.findById(userId);\n \n return {\n name: user.name,\n email: user.email,\n plan: user.subscription.plan,\n createdAt: user.createdAt.toISOString(),\n };\n },\n \n 'create-support-ticket': async (args) => {\n const summary = args.summary as string;\n const priority = args.priority as string;\n \n // Create ticket in your system\n const ticket = await ticketService.create({\n summary,\n priority,\n source: 'ai-chat',\n });\n \n return {\n ticketId: ticket.id,\n estimatedResponse: getEstimatedResponse(priority),\n };\n },\n};\n```\n\n## Using Tools in Sessions\n\nPass tool handlers when attaching to a session:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n // Implementation\n },\n 'create-support-ticket': async (args) => {\n // Implementation\n },\n },\n});\n```\n\n## Tool Handler Signature\n\n```typescript\ntype ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;\ntype ToolHandlers = Record<string, ToolHandler>;\n```\n\n### Arguments\n\nArguments are passed as a `Record<string, unknown>`. Type-check as needed:\n\n```typescript\n'search-products': async (args) => {\n const query = args.query as string;\n const category = args.category as string | undefined;\n const maxPrice = args.maxPrice as number | undefined;\n \n return await productSearch({ query, category, maxPrice });\n}\n```\n\n### Return Values\n\nReturn any JSON-serializable value. The result is:\n1. Sent back to the LLM as context\n2. Stored in session state\n3. Optionally stored in a variable for protocol use\n\n```typescript\n// Return object\nreturn { id: '123', status: 'created' };\n\n// Return array\nreturn [{ id: '1' }, { id: '2' }];\n\n// Return primitive\nreturn 42;\n\n// Return null for \"no result\"\nreturn null;\n```\n\n## Error Handling\n\nThrow errors for failures. They're captured and sent to the LLM:\n\n```typescript\n'get-user-account': async (args) => {\n const userId = args.userId as string;\n \n const user = await db.users.findById(userId);\n \n if (!user) {\n throw new Error(`User not found: ${userId}`);\n }\n \n return user;\n}\n```\n\nThe LLM receives the error message and can respond appropriately (e.g., \"I couldn't find that account\").\n\n## Tool Execution Flow\n\nWhen the LLM calls a tool:\n\n```mermaid\nsequenceDiagram\n participant LLM\n participant Platform as Octavus Platform\n participant SDK as Server SDK\n participant UI as Your Frontend\n\n LLM->>Platform: 1. Decides to call tool\n Platform-->>UI: tool-input-start, tool-input-delta\n Platform-->>UI: tool-input-available\n Platform-->>SDK: 2. tool-request (stream pauses)\n \n Note over SDK: 3. Execute handler<br/>tools['get-user']()\n \n SDK-->>UI: 4. tool-output-available\n SDK->>Platform: 5. POST /trigger with results\n Platform->>LLM: 6. Continue with results\n LLM-->>Platform: Response\n Platform-->>UI: text-delta events\n \n Note over LLM,UI: 7. Repeat if more tools needed\n```\n\n## Accessing Request Context\n\nFor request-specific data (auth, headers), create handlers dynamically:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// In your API route\nexport async function POST(request: Request) {\n const authToken = request.headers.get('Authorization');\n const user = await validateToken(authToken);\n \n const session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n // Use request context\n return await db.users.findById(user.id);\n },\n 'create-order': async (args) => {\n // Create with user context\n return await orderService.create({\n ...args,\n userId: user.id,\n createdBy: user.email,\n });\n },\n },\n });\n \n const events = session.trigger(triggerName, input);\n return new Response(toSSEStream(events));\n}\n```\n\n## Best Practices\n\n### 1. Validate Arguments\n\n```typescript\n'create-ticket': async (args) => {\n const summary = args.summary;\n if (typeof summary !== 'string' || summary.length === 0) {\n throw new Error('Summary is required');\n }\n // ...\n}\n```\n\n### 2. Handle Timeouts\n\n```typescript\n'external-api-call': async (args) => {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10000);\n \n try {\n const response = await fetch(url, { signal: controller.signal });\n return await response.json();\n } finally {\n clearTimeout(timeout);\n }\n}\n```\n\n### 3. Log Tool Calls\n\n```typescript\n'search-products': async (args) => {\n console.log('Tool call: search-products', { args });\n \n const result = await productSearch(args);\n \n console.log('Tool result: search-products', { \n resultCount: result.length \n });\n \n return result;\n}\n```\n\n",
43
+ "content": "\n# Tools\n\nTools extend what agents can do. In Octavus, tools execute on your server, giving you full control over data access and authentication.\n\n## Why Tools Run on Your Server\n\nUnlike traditional AI platforms where tools run in a sandbox, Octavus tools execute in your backend:\n\n- ✅ **Full data access** — Query your database directly\n- ✅ **Your authentication** — Use your existing auth context\n- ✅ **No data exposure** — Sensitive data never leaves your infrastructure\n- ✅ **Custom logic** — Any complexity you need\n\n## Defining Tool Handlers\n\nTool handlers are async functions that receive arguments and return results:\n\n```typescript\nimport type { ToolHandlers } from '@octavus/server-sdk';\n\nconst tools: ToolHandlers = {\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n\n // Query your database\n const user = await db.users.findById(userId);\n\n return {\n name: user.name,\n email: user.email,\n plan: user.subscription.plan,\n createdAt: user.createdAt.toISOString(),\n };\n },\n\n 'create-support-ticket': async (args) => {\n const summary = args.summary as string;\n const priority = args.priority as string;\n\n // Create ticket in your system\n const ticket = await ticketService.create({\n summary,\n priority,\n source: 'ai-chat',\n });\n\n return {\n ticketId: ticket.id,\n estimatedResponse: getEstimatedResponse(priority),\n };\n },\n};\n```\n\n## Using Tools in Sessions\n\nPass tool handlers when attaching to a session:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n // Implementation\n },\n 'create-support-ticket': async (args) => {\n // Implementation\n },\n },\n});\n```\n\n## Tool Handler Signature\n\n```typescript\ntype ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;\ntype ToolHandlers = Record<string, ToolHandler>;\n```\n\n### Arguments\n\nArguments are passed as a `Record<string, unknown>`. Type-check as needed:\n\n```typescript\n'search-products': async (args) => {\n const query = args.query as string;\n const category = args.category as string | undefined;\n const maxPrice = args.maxPrice as number | undefined;\n\n return await productSearch({ query, category, maxPrice });\n}\n```\n\n### Return Values\n\nReturn any JSON-serializable value. The result is:\n\n1. Sent back to the LLM as context\n2. Stored in session state\n3. Optionally stored in a variable for protocol use\n\n```typescript\n// Return object\nreturn { id: '123', status: 'created' };\n\n// Return array\nreturn [{ id: '1' }, { id: '2' }];\n\n// Return primitive\nreturn 42;\n\n// Return null for \"no result\"\nreturn null;\n```\n\n## Error Handling\n\nThrow errors for failures. They're captured and sent to the LLM:\n\n```typescript\n'get-user-account': async (args) => {\n const userId = args.userId as string;\n\n const user = await db.users.findById(userId);\n\n if (!user) {\n throw new Error(`User not found: ${userId}`);\n }\n\n return user;\n}\n```\n\nThe LLM receives the error message and can respond appropriately (e.g., \"I couldn't find that account\").\n\n## Tool Execution Flow\n\nWhen the LLM calls a tool:\n\n```mermaid\nsequenceDiagram\n participant LLM\n participant Platform as Octavus Platform\n participant SDK as Server SDK\n participant UI as Your Frontend\n\n LLM->>Platform: 1. Decides to call tool\n Platform-->>UI: tool-input-start, tool-input-delta\n Platform-->>UI: tool-input-available\n Platform-->>SDK: 2. tool-request (stream pauses)\n\n Note over SDK: 3. Execute handler<br/>tools['get-user']()\n\n SDK-->>UI: 4. tool-output-available\n SDK->>Platform: 5. POST /trigger with results\n Platform->>LLM: 6. Continue with results\n LLM-->>Platform: Response\n Platform-->>UI: text-delta events\n\n Note over LLM,UI: 7. Repeat if more tools needed\n```\n\n## Accessing Request Context\n\nFor request-specific data (auth, headers), create handlers dynamically:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// In your API route\nexport async function POST(request: Request) {\n const authToken = request.headers.get('Authorization');\n const user = await validateToken(authToken);\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n // Use request context\n return await db.users.findById(user.id);\n },\n 'create-order': async (args) => {\n // Create with user context\n return await orderService.create({\n ...args,\n userId: user.id,\n createdBy: user.email,\n });\n },\n },\n });\n\n const events = session.trigger(triggerName, input);\n return new Response(toSSEStream(events));\n}\n```\n\n## Best Practices\n\n### 1. Validate Arguments\n\n```typescript\n'create-ticket': async (args) => {\n const summary = args.summary;\n if (typeof summary !== 'string' || summary.length === 0) {\n throw new Error('Summary is required');\n }\n // ...\n}\n```\n\n### 2. Handle Timeouts\n\n```typescript\n'external-api-call': async (args) => {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10000);\n\n try {\n const response = await fetch(url, { signal: controller.signal });\n return await response.json();\n } finally {\n clearTimeout(timeout);\n }\n}\n```\n\n### 3. Log Tool Calls\n\n```typescript\n'search-products': async (args) => {\n console.log('Tool call: search-products', { args });\n\n const result = await productSearch(args);\n\n console.log('Tool result: search-products', {\n resultCount: result.length\n });\n\n return result;\n}\n```\n",
44
44
  "excerpt": "Tools Tools extend what agents can do. In Octavus, tools execute on your server, giving you full control over data access and authentication. Why Tools Run on Your Server Unlike traditional AI...",
45
45
  "order": 3
46
46
  },
@@ -49,17 +49,26 @@
49
49
  "section": "server-sdk",
50
50
  "title": "Streaming",
51
51
  "description": "Understanding stream events from the Server SDK.",
52
- "content": "\n# Streaming\n\nAll Octavus responses stream in real-time using Server-Sent Events (SSE). This enables responsive UX with incremental updates.\n\n## Stream Response\n\nWhen you trigger an action, you get an async generator of parsed events:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// trigger() returns an async generator of StreamEvent\nconst events = session.trigger('user-message', { \n USER_MESSAGE: 'Hello!' \n});\n\n// For HTTP endpoints, convert to SSE stream\nreturn new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n },\n});\n\n// For sockets, iterate events directly\nfor await (const event of events) {\n conn.write(JSON.stringify(event));\n}\n```\n\n## Event Types\n\nThe stream emits various event types. Octavus events align with the [Vercel AI SDK](https://sdk.vercel.ai/) naming conventions where applicable.\n\n### Lifecycle Events\n\n```typescript\n// Stream started\n{ type: 'start', messageId: '...' }\n\n// Stream completed\n{ type: 'finish', finishReason: 'stop' }\n\n// Possible finish reasons:\n// - 'stop': Normal completion\n// - 'tool-calls': Waiting for tool execution (handled by SDK)\n// - 'length': Max tokens reached\n// - 'content-filter': Content filtered\n// - 'error': Error occurred\n// - 'other': Other reason\n\n// Error event\n{ type: 'error', errorText: 'Something went wrong' }\n```\n\n### Block Events\n\nTrack execution progress:\n\n```typescript\n// Block started\n{ type: 'block-start', blockId: '...', blockName: 'Respond to user', blockType: 'next-message', display: 'stream', thread: 'main' }\n\n// Block completed\n{ type: 'block-end', blockId: '...', summary: 'Generated response' }\n```\n\n### Text Events\n\nStreaming text content:\n\n```typescript\n// Text generation started\n{ type: 'text-start', id: '...' }\n\n// Incremental text (most common event)\n{ type: 'text-delta', id: '...', delta: 'Hello' }\n{ type: 'text-delta', id: '...', delta: '!' }\n{ type: 'text-delta', id: '...', delta: ' How' }\n{ type: 'text-delta', id: '...', delta: ' can' }\n{ type: 'text-delta', id: '...', delta: ' I' }\n{ type: 'text-delta', id: '...', delta: ' help?' }\n\n// Text generation ended\n{ type: 'text-end', id: '...' }\n```\n\n### Reasoning Events\n\nExtended reasoning (for supported models like Claude):\n\n```typescript\n// Reasoning started\n{ type: 'reasoning-start', id: '...' }\n\n// Reasoning content\n{ type: 'reasoning-delta', id: '...', delta: 'Let me analyze this request...' }\n\n// Reasoning ended\n{ type: 'reasoning-end', id: '...' }\n```\n\n### Tool Events\n\nTool call lifecycle:\n\n```typescript\n// Tool input started\n{ type: 'tool-input-start', toolCallId: '...', toolName: 'get-user-account', title: 'Looking up account' }\n\n// Tool input/arguments streaming\n{ type: 'tool-input-delta', toolCallId: '...', inputTextDelta: '{\"userId\":\"user-123\"}' }\n\n// Tool input streaming ended\n{ type: 'tool-input-end', toolCallId: '...' }\n\n// Tool input is complete and available\n{ type: 'tool-input-available', toolCallId: '...', toolName: 'get-user-account', input: { userId: 'user-123' } }\n\n// Tool output available (success)\n{ type: 'tool-output-available', toolCallId: '...', output: { name: 'Demo User', email: '...' } }\n\n// Tool output error (failure)\n{ type: 'tool-output-error', toolCallId: '...', errorText: 'User not found' }\n```\n\n### Resource Events\n\nResource updates:\n\n```typescript\n{ type: 'resource-update', name: 'CONVERSATION_SUMMARY', value: 'User asked about...' }\n```\n\n## Display Modes\n\nEach block/tool specifies how it should appear to users:\n\n| Mode | Description |\n|------|-------------|\n| `hidden` | Not shown to user (background work) |\n| `name` | Shows block/tool name |\n| `description` | Shows description text |\n| `stream` | Streams content to chat |\n\n**Note**: Hidden events are filtered before reaching the client SDK. Your frontend only sees user-facing events.\n\n## Stream Event Type\n\n```typescript\ntype StreamEvent =\n // Lifecycle\n | StartEvent\n | FinishEvent\n | ErrorEvent\n // Text\n | TextStartEvent\n | TextDeltaEvent\n | TextEndEvent\n // Reasoning\n | ReasoningStartEvent\n | ReasoningDeltaEvent\n | ReasoningEndEvent\n // Tool Input/Output\n | ToolInputStartEvent\n | ToolInputDeltaEvent\n | ToolInputEndEvent\n | ToolInputAvailableEvent\n | ToolOutputAvailableEvent\n | ToolOutputErrorEvent\n // Octavus-Specific\n | BlockStartEvent\n | BlockEndEvent\n | ResourceUpdateEvent\n | ToolRequestEvent;\n```\n\n## Error Recovery\n\nThe SDK handles common error scenarios:\n\n```typescript\n// Network errors are caught and emitted\n{ type: 'error', errorText: 'Network request failed' }\n\n// Tool errors are captured per-tool\n{ type: 'tool-output-error', toolCallId: '...', errorText: 'Handler threw exception' }\n\n// The stream always ends with either 'finish' or 'error'\n```\n",
52
+ "content": "\n# Streaming\n\nAll Octavus responses stream in real-time using Server-Sent Events (SSE). This enables responsive UX with incremental updates.\n\n## Stream Response\n\nWhen you trigger an action, you get an async generator of parsed events:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// trigger() returns an async generator of StreamEvent\nconst events = session.trigger('user-message', {\n USER_MESSAGE: 'Hello!',\n});\n\n// For HTTP endpoints, convert to SSE stream\nreturn new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n});\n\n// For sockets, iterate events directly\nfor await (const event of events) {\n conn.write(JSON.stringify(event));\n}\n```\n\n## Event Types\n\nThe stream emits various event types for lifecycle, text, reasoning, and tool interactions.\n\n### Lifecycle Events\n\n```typescript\n// Stream started\n{ type: 'start', messageId: '...' }\n\n// Stream completed\n{ type: 'finish', finishReason: 'stop' }\n\n// Possible finish reasons:\n// - 'stop': Normal completion\n// - 'tool-calls': Waiting for tool execution (handled by SDK)\n// - 'length': Max tokens reached\n// - 'content-filter': Content filtered\n// - 'error': Error occurred\n// - 'other': Other reason\n\n// Error event (see Error Handling docs for full structure)\n{ type: 'error', errorType: 'internal_error', message: 'Something went wrong', source: 'platform', retryable: false }\n```\n\n### Block Events\n\nTrack execution progress:\n\n```typescript\n// Block started\n{ type: 'block-start', blockId: '...', blockName: 'Respond to user', blockType: 'next-message', display: 'stream', thread: 'main' }\n\n// Block completed\n{ type: 'block-end', blockId: '...', summary: 'Generated response' }\n```\n\n### Text Events\n\nStreaming text content:\n\n```typescript\n// Text generation started\n{ type: 'text-start', id: '...' }\n\n// Incremental text (most common event)\n{ type: 'text-delta', id: '...', delta: 'Hello' }\n{ type: 'text-delta', id: '...', delta: '!' }\n{ type: 'text-delta', id: '...', delta: ' How' }\n{ type: 'text-delta', id: '...', delta: ' can' }\n{ type: 'text-delta', id: '...', delta: ' I' }\n{ type: 'text-delta', id: '...', delta: ' help?' }\n\n// Text generation ended\n{ type: 'text-end', id: '...' }\n```\n\n### Reasoning Events\n\nExtended reasoning (for supported models like Claude):\n\n```typescript\n// Reasoning started\n{ type: 'reasoning-start', id: '...' }\n\n// Reasoning content\n{ type: 'reasoning-delta', id: '...', delta: 'Let me analyze this request...' }\n\n// Reasoning ended\n{ type: 'reasoning-end', id: '...' }\n```\n\n### Tool Events\n\nTool call lifecycle:\n\n```typescript\n// Tool input started\n{ type: 'tool-input-start', toolCallId: '...', toolName: 'get-user-account', title: 'Looking up account' }\n\n// Tool input/arguments streaming\n{ type: 'tool-input-delta', toolCallId: '...', inputTextDelta: '{\"userId\":\"user-123\"}' }\n\n// Tool input streaming ended\n{ type: 'tool-input-end', toolCallId: '...' }\n\n// Tool input is complete and available\n{ type: 'tool-input-available', toolCallId: '...', toolName: 'get-user-account', input: { userId: 'user-123' } }\n\n// Tool output available (success)\n{ type: 'tool-output-available', toolCallId: '...', output: { name: 'Demo User', email: '...' } }\n\n// Tool output error (failure)\n{ type: 'tool-output-error', toolCallId: '...', error: 'User not found' }\n```\n\n### Resource Events\n\nResource updates:\n\n```typescript\n{ type: 'resource-update', name: 'CONVERSATION_SUMMARY', value: 'User asked about...' }\n```\n\n## Display Modes\n\nEach block/tool specifies how it should appear to users:\n\n| Mode | Description |\n| ------------- | ----------------------------------- |\n| `hidden` | Not shown to user (background work) |\n| `name` | Shows block/tool name |\n| `description` | Shows description text |\n| `stream` | Streams content to chat |\n\n**Note**: Hidden events are filtered before reaching the client SDK. Your frontend only sees user-facing events.\n\n## Stream Event Type\n\n```typescript\ntype StreamEvent =\n // Lifecycle\n | StartEvent\n | FinishEvent\n | ErrorEvent\n // Text\n | TextStartEvent\n | TextDeltaEvent\n | TextEndEvent\n // Reasoning\n | ReasoningStartEvent\n | ReasoningDeltaEvent\n | ReasoningEndEvent\n // Tool Input/Output\n | ToolInputStartEvent\n | ToolInputDeltaEvent\n | ToolInputEndEvent\n | ToolInputAvailableEvent\n | ToolOutputAvailableEvent\n | ToolOutputErrorEvent\n // Octavus-Specific\n | BlockStartEvent\n | BlockEndEvent\n | ResourceUpdateEvent\n | ToolRequestEvent;\n```\n\n## Error Events\n\nErrors are emitted as structured events with type classification:\n\n```typescript\n{\n type: 'error',\n errorType: 'rate_limit_error', // Error classification\n message: 'Rate limit exceeded', // Human-readable message\n source: 'provider', // 'platform' | 'provider' | 'tool'\n retryable: true, // Whether retry is possible\n retryAfter: 60, // Seconds to wait (rate limits)\n code: 'ANTHROPIC_429', // Machine-readable code\n provider: { // Provider details (when applicable)\n name: 'anthropic',\n statusCode: 429,\n requestId: 'req_...'\n }\n}\n```\n\n### Error Types\n\n| Type | Description |\n| ---------------------- | --------------------- |\n| `rate_limit_error` | Too many requests |\n| `authentication_error` | Invalid API key |\n| `provider_error` | LLM provider issue |\n| `provider_overloaded` | Provider at capacity |\n| `tool_error` | Tool execution failed |\n| `internal_error` | Platform error |\n\n### Tool Errors\n\nTool errors are captured per-tool and don't stop the stream:\n\n```typescript\n{ type: 'tool-output-error', toolCallId: '...', error: 'Handler threw exception' }\n```\n\nThe stream always ends with either `finish` or `error`.\n\nFor client-side error handling patterns, see [Error Handling](/docs/client-sdk/error-handling).\n",
53
53
  "excerpt": "Streaming All Octavus responses stream in real-time using Server-Sent Events (SSE). This enables responsive UX with incremental updates. Stream Response When you trigger an action, you get an async...",
54
54
  "order": 4
55
55
  },
56
+ {
57
+ "slug": "server-sdk/cli",
58
+ "section": "server-sdk",
59
+ "title": "CLI",
60
+ "description": "Command-line interface for validating and syncing agent definitions.",
61
+ "content": "\n# Octavus CLI\n\nThe `@octavus/cli` package provides a command-line interface for validating and syncing agent definitions from your local filesystem to the Octavus platform.\n\n**Current version:** `1.0.0`\n\n## Installation\n\n```bash\nnpm install --save-dev @octavus/cli\n```\n\n## Configuration\n\nThe CLI requires an API key with agent management permissions.\n\n### Environment Variables\n\n| Variable | Description |\n| --------------------- | ------------------------------------------------------- |\n| `OCTAVUS_CLI_API_KEY` | API key with agent management permissions (recommended) |\n| `OCTAVUS_API_KEY` | Fallback if `OCTAVUS_CLI_API_KEY` not set |\n| `OCTAVUS_API_URL` | Optional, defaults to `https://octavus.ai` |\n\n### Two-Key Strategy (Recommended)\n\nFor production deployments, use separate API keys:\n\n```bash\n# CI/CD or .env.local (not committed)\nOCTAVUS_CLI_API_KEY=oct_sk_... # Agent management permissions\n\n# Production .env\nOCTAVUS_API_KEY=oct_sk_... # Session-only permissions\n```\n\nThis ensures production servers only have session permissions (smaller blast radius if leaked), while agent management is restricted to development/CI environments.\n\n### Multiple Environments\n\nUse separate Octavus projects for staging and production, each with their own API keys. The `--env` flag lets you load different environment files:\n\n```bash\n# Local development (default: .env)\noctavus sync ./agents/my-agent\n\n# Staging project\noctavus --env .env.staging sync ./agents/my-agent\n\n# Production project\noctavus --env .env.production sync ./agents/my-agent\n```\n\nExample environment files:\n\n```bash\n# .env.staging (syncs to your staging project)\nOCTAVUS_CLI_API_KEY=oct_sk_staging_project_key...\n\n# .env.production (syncs to your production project)\nOCTAVUS_CLI_API_KEY=oct_sk_production_project_key...\n```\n\nEach project has its own agents, so you'll get different agent IDs per environment.\n\n## Global Options\n\n| Option | Description |\n| -------------- | ------------------------------------------------------- |\n| `--env <file>` | Load environment from a specific file (default: `.env`) |\n| `--help` | Show help |\n| `--version` | Show version |\n\n## Commands\n\n### `octavus sync <path>`\n\nSync an agent definition to the platform. Creates the agent if it doesn't exist, or updates it if it does.\n\n```bash\noctavus sync ./agents/my-agent\n```\n\n**Options:**\n\n- `--json` — Output as JSON (for CI/CD parsing)\n- `--quiet` — Suppress non-essential output\n\n**Example output:**\n\n```\nℹ Reading agent from ./agents/my-agent...\nℹ Syncing support-chat...\n✓ Created: support-chat\n Agent ID: clxyz123abc456\n```\n\n### `octavus validate <path>`\n\nValidate an agent definition without saving. Useful for CI/CD pipelines.\n\n```bash\noctavus validate ./agents/my-agent\n```\n\n**Exit codes:**\n\n- `0` — Validation passed\n- `1` — Validation errors\n- `2` — Configuration errors (missing API key, etc.)\n\n### `octavus list`\n\nList all agents in your project.\n\n```bash\noctavus list\n```\n\n**Example output:**\n\n```\nSLUG NAME FORMAT ID\n────────────────────────────────────────────────────────────────────────────\nsupport-chat Support Chat Agent interactive clxyz123abc456\n\n1 agent(s)\n```\n\n### `octavus get <slug>`\n\nGet details about a specific agent by its slug.\n\n```bash\noctavus get support-chat\n```\n\n## Agent Directory Structure\n\nThe CLI expects agent definitions in a specific directory structure:\n\n```\nmy-agent/\n├── settings.json # Required: Agent metadata\n├── protocol.yaml # Required: Agent protocol\n└── prompts/ # Optional: Prompt templates\n ├── system.md\n └── user-message.md\n```\n\n### settings.json\n\n```json\n{\n \"slug\": \"my-agent\",\n \"name\": \"My Agent\",\n \"description\": \"A helpful assistant\",\n \"format\": \"interactive\"\n}\n```\n\n### protocol.yaml\n\nSee the [Protocol documentation](/docs/protocol/overview) for details on protocol syntax.\n\n## CI/CD Integration\n\n### GitHub Actions\n\n```yaml\nname: Validate and Sync Agents\n\non:\n push:\n branches: [main]\n paths:\n - 'agents/**'\n\njobs:\n sync:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - uses: actions/setup-node@v4\n with:\n node-version: '22'\n\n - run: npm install\n\n - name: Validate agent\n run: npx octavus validate ./agents/support-chat\n env:\n OCTAVUS_CLI_API_KEY: ${{ secrets.OCTAVUS_CLI_API_KEY }}\n\n - name: Sync agent\n run: npx octavus sync ./agents/support-chat\n env:\n OCTAVUS_CLI_API_KEY: ${{ secrets.OCTAVUS_CLI_API_KEY }}\n```\n\n### Package.json Scripts\n\nAdd sync scripts to your `package.json`:\n\n```json\n{\n \"scripts\": {\n \"agents:validate\": \"octavus validate ./agents/my-agent\",\n \"agents:sync\": \"octavus sync ./agents/my-agent\"\n },\n \"devDependencies\": {\n \"@octavus/cli\": \"^0.1.0\"\n }\n}\n```\n\n## Workflow\n\nThe recommended workflow for managing agents:\n\n1. **Define agent locally** — Create `settings.json`, `protocol.yaml`, and prompts\n2. **Validate** — Run `octavus validate ./my-agent` to check for errors\n3. **Sync** — Run `octavus sync ./my-agent` to push to platform\n4. **Store agent ID** — Save the output ID in an environment variable\n5. **Use in app** — Read the ID from env and pass to `client.agentSessions.create()`\n\n```bash\n# After syncing: octavus sync ./agents/support-chat\n# Output: Agent ID: clxyz123abc456\n\n# Add to your .env file\nOCTAVUS_SUPPORT_AGENT_ID=clxyz123abc456\n```\n\n```typescript\nconst agentId = process.env.OCTAVUS_SUPPORT_AGENT_ID;\n\nconst sessionId = await client.agentSessions.create(agentId, {\n COMPANY_NAME: 'Acme Corp',\n});\n```\n",
62
+ "excerpt": "Octavus CLI The package provides a command-line interface for validating and syncing agent definitions from your local filesystem to the Octavus platform. Current version: Installation ...",
63
+ "order": 5
64
+ },
56
65
  {
57
66
  "slug": "client-sdk/overview",
58
67
  "section": "client-sdk",
59
68
  "title": "Overview",
60
69
  "description": "Introduction to the Octavus Client SDKs for building chat interfaces.",
61
- "content": "\n# Client SDK Overview\n\nOctavus provides two packages for frontend integration:\n\n| Package | Purpose | Use When |\n|---------|---------|----------|\n| `@octavus/react` | React hooks and bindings | Building React applications |\n| `@octavus/client-sdk` | Framework-agnostic core | Using Vue, Svelte, vanilla JS, or custom integrations |\n\n**Most users should install `@octavus/react`** — it includes everything from `@octavus/client-sdk` plus React-specific hooks.\n\n## Installation\n\n### React Applications\n\n```bash\nnpm install @octavus/react\n```\n\n**Current version:** `0.0.8`\n\n### Other Frameworks\n\n```bash\nnpm install @octavus/client-sdk\n```\n\n**Current version:** `0.0.8`\n\n## Transport Pattern\n\nThe Client SDK uses a **transport abstraction** to handle communication with your backend. This gives you flexibility in how events are delivered:\n\n| Transport | Use Case | Docs |\n|-----------|----------|------|\n| `createHttpTransport` | HTTP/SSE (Next.js, Express, etc.) | [HTTP Transport](/docs/client-sdk/http-transport) |\n| `createSocketTransport` | WebSocket, SockJS, or other socket protocols | [Socket Transport](/docs/client-sdk/socket-transport) |\n\nWhen the transport changes (e.g., when `sessionId` changes), the `useOctavusChat` hook automatically reinitializes with the new transport.\n\n> **Recommendation**: Use HTTP transport unless you specifically need WebSocket features (custom real-time events, Meteor/Phoenix, etc.).\n\n## React Usage\n\nThe `useOctavusChat` hook provides state management and streaming for React applications:\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n // Create a stable transport instance (memoized on sessionId)\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send(\n 'user-message',\n { USER_MESSAGE: text },\n { userMessage: { content: text } },\n );\n };\n\n return (\n <div>\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n </div>\n );\n}\n```\n\n## Framework-Agnostic Usage\n\nThe `OctavusChat` class can be used with any framework or vanilla JavaScript:\n\n```typescript\nimport { OctavusChat, createHttpTransport } from '@octavus/client-sdk';\n\nconst transport = createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n});\n\nconst chat = new OctavusChat({ transport });\n\n// Subscribe to state changes\nconst unsubscribe = chat.subscribe(() => {\n console.log('Messages:', chat.messages);\n console.log('Status:', chat.status);\n // Update your UI here\n});\n\n// Send a message\nawait chat.send(\n 'user-message',\n { USER_MESSAGE: 'Hello' },\n { userMessage: { content: 'Hello' } },\n);\n\n// Cleanup when done\nunsubscribe();\n```\n\n## Key Features\n\n### Unified Send Function\n\nThe `send` function handles both user message display and agent triggering in one call:\n\n```tsx\nconst { send } = useOctavusChat({ transport });\n\n// Add user message to UI and trigger agent\nawait send(\n 'user-message',\n { USER_MESSAGE: text },\n { userMessage: { content: text } },\n);\n\n// Trigger without adding a user message (e.g., button click)\nawait send('request-human');\n```\n\n### Message Parts\n\nMessages contain ordered `parts` for rich content:\n\n```tsx\nconst { messages } = useOctavusChat({ transport });\n\n// Each message has typed parts\nmessage.parts.map((part) => {\n switch (part.type) {\n case 'text': // Text content\n case 'reasoning': // Extended reasoning/thinking\n case 'tool-call': // Tool execution\n case 'operation': // Internal operations (set-resource, etc.)\n }\n});\n```\n\n### Status Tracking\n\n```tsx\nconst { status } = useOctavusChat({ transport });\n\n// status: 'idle' | 'streaming' | 'error'\n```\n\n### Stop Streaming\n\n```tsx\nconst { stop } = useOctavusChat({ transport });\n\n// Stop current stream and finalize message\nstop();\n```\n\n## Hook Reference (React)\n\n### useOctavusChat\n\n```typescript\nfunction useOctavusChat(options: OctavusChatOptions): UseOctavusChatReturn;\n\ninterface OctavusChatOptions {\n // Required: Transport for streaming events\n transport: Transport;\n\n // Optional: Pre-populate with existing messages (session restore)\n initialMessages?: UIMessage[];\n\n // Optional: Callbacks\n onError?: (error: Error) => void;\n onFinish?: () => void;\n onResourceUpdate?: (name: string, value: unknown) => void;\n}\n\ninterface UseOctavusChatReturn {\n // State\n messages: UIMessage[];\n status: ChatStatus; // 'idle' | 'streaming' | 'error'\n error: Error | null;\n\n // Actions\n send: (\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput }\n ) => Promise<void>;\n stop: () => void;\n}\n\ninterface UserMessageInput {\n content: string;\n}\n```\n\n## Transport Reference\n\n### createHttpTransport\n\nCreates an HTTP/SSE transport using native `fetch()`:\n\n```typescript\nimport { createHttpTransport } from '@octavus/react';\n\nconst transport = createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n});\n```\n\n### createSocketTransport\n\nCreates a WebSocket/SockJS transport for real-time connections:\n\n```typescript\nimport { createSocketTransport } from '@octavus/react';\n\nconst transport = createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const ws = new WebSocket(`wss://api.example.com/stream?sessionId=${sessionId}`);\n ws.onopen = () => resolve(ws);\n ws.onerror = () => reject(new Error('Connection failed'));\n }),\n});\n```\n\nFor detailed WebSocket/SockJS usage including custom events, reconnection patterns, and server-side implementation, see [Socket Transport](/docs/client-sdk/socket-transport).\n\n## Class Reference (Framework-Agnostic)\n\n### OctavusChat\n\n```typescript\nclass OctavusChat {\n constructor(options: OctavusChatOptions);\n\n // State (read-only)\n readonly messages: UIMessage[];\n readonly status: ChatStatus;\n readonly error: Error | null;\n\n // Actions\n send(\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput }\n ): Promise<void>;\n stop(): void;\n\n // Subscription\n subscribe(callback: () => void): () => void; // Returns unsubscribe function\n}\n```\n\n## Next Steps\n\n- [HTTP Transport](/docs/client-sdk/http-transport) — HTTP/SSE integration (recommended)\n- [Socket Transport](/docs/client-sdk/socket-transport) — WebSocket and SockJS integration\n- [Messages](/docs/client-sdk/messages) — Working with message state\n- [Streaming](/docs/client-sdk/streaming) — Building streaming UIs\n- [Operations](/docs/client-sdk/execution-blocks) — Showing agent progress\n- [Examples](/docs/examples/overview) — Complete working examples\n",
62
- "excerpt": "Client SDK Overview Octavus provides two packages for frontend integration: | Package | Purpose | Use When | |---------|---------|----------| | | React hooks and bindings | Building React...",
70
+ "content": "\n# Client SDK Overview\n\nOctavus provides two packages for frontend integration:\n\n| Package | Purpose | Use When |\n| --------------------- | ------------------------ | ----------------------------------------------------- |\n| `@octavus/react` | React hooks and bindings | Building React applications |\n| `@octavus/client-sdk` | Framework-agnostic core | Using Vue, Svelte, vanilla JS, or custom integrations |\n\n**Most users should install `@octavus/react`** — it includes everything from `@octavus/client-sdk` plus React-specific hooks.\n\n## Installation\n\n### React Applications\n\n```bash\nnpm install @octavus/react\n```\n\n**Current version:** `1.0.0`\n\n### Other Frameworks\n\n```bash\nnpm install @octavus/client-sdk\n```\n\n**Current version:** `1.0.0`\n\n## Transport Pattern\n\nThe Client SDK uses a **transport abstraction** to handle communication with your backend. This gives you flexibility in how events are delivered:\n\n| Transport | Use Case | Docs |\n| ----------------------- | -------------------------------------------- | ----------------------------------------------------- |\n| `createHttpTransport` | HTTP/SSE (Next.js, Express, etc.) | [HTTP Transport](/docs/client-sdk/http-transport) |\n| `createSocketTransport` | WebSocket, SockJS, or other socket protocols | [Socket Transport](/docs/client-sdk/socket-transport) |\n\nWhen the transport changes (e.g., when `sessionId` changes), the `useOctavusChat` hook automatically reinitializes with the new transport.\n\n> **Recommendation**: Use HTTP transport unless you specifically need WebSocket features (custom real-time events, Meteor/Phoenix, etc.).\n\n## React Usage\n\nThe `useOctavusChat` hook provides state management and streaming for React applications:\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n // Create a stable transport instance (memoized on sessionId)\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n };\n\n return (\n <div>\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n </div>\n );\n}\n```\n\n## Framework-Agnostic Usage\n\nThe `OctavusChat` class can be used with any framework or vanilla JavaScript:\n\n```typescript\nimport { OctavusChat, createHttpTransport } from '@octavus/client-sdk';\n\nconst transport = createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n});\n\nconst chat = new OctavusChat({ transport });\n\n// Subscribe to state changes\nconst unsubscribe = chat.subscribe(() => {\n console.log('Messages:', chat.messages);\n console.log('Status:', chat.status);\n // Update your UI here\n});\n\n// Send a message\nawait chat.send('user-message', { USER_MESSAGE: 'Hello' }, { userMessage: { content: 'Hello' } });\n\n// Cleanup when done\nunsubscribe();\n```\n\n## Key Features\n\n### Unified Send Function\n\nThe `send` function handles both user message display and agent triggering in one call:\n\n```tsx\nconst { send } = useOctavusChat({ transport });\n\n// Add user message to UI and trigger agent\nawait send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n\n// Trigger without adding a user message (e.g., button click)\nawait send('request-human');\n```\n\n### Message Parts\n\nMessages contain ordered `parts` for rich content:\n\n```tsx\nconst { messages } = useOctavusChat({ transport });\n\n// Each message has typed parts\nmessage.parts.map((part) => {\n switch (part.type) {\n case 'text': // Text content\n case 'reasoning': // Extended reasoning/thinking\n case 'tool-call': // Tool execution\n case 'operation': // Internal operations (set-resource, etc.)\n }\n});\n```\n\n### Status Tracking\n\n```tsx\nconst { status } = useOctavusChat({ transport });\n\n// status: 'idle' | 'streaming' | 'error'\n```\n\n### Stop Streaming\n\n```tsx\nconst { stop } = useOctavusChat({ transport });\n\n// Stop current stream and finalize message\nstop();\n```\n\n## Hook Reference (React)\n\n### useOctavusChat\n\n```typescript\nfunction useOctavusChat(options: OctavusChatOptions): UseOctavusChatReturn;\n\ninterface OctavusChatOptions {\n // Required: Transport for streaming events\n transport: Transport;\n\n // Optional: Function to request upload URLs for file uploads\n requestUploadUrls?: (\n files: { filename: string; mediaType: string; size: number }[],\n ) => Promise<UploadUrlsResponse>;\n\n // Optional: Pre-populate with existing messages (session restore)\n initialMessages?: UIMessage[];\n\n // Optional: Callbacks\n onError?: (error: OctavusError) => void; // Structured error with type, source, retryable\n onFinish?: () => void;\n onStop?: () => void; // Called when user stops generation\n onResourceUpdate?: (name: string, value: unknown) => void;\n}\n\ninterface UseOctavusChatReturn {\n // State\n messages: UIMessage[];\n status: ChatStatus; // 'idle' | 'streaming' | 'error'\n error: OctavusError | null; // Structured error with type, source, retryable\n\n // Connection (socket transport only - undefined for HTTP)\n connectionState: ConnectionState | undefined; // 'disconnected' | 'connecting' | 'connected' | 'error'\n connectionError: Error | undefined;\n\n // Actions\n send: (\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput },\n ) => Promise<void>;\n stop: () => void;\n\n // Connection management (socket transport only - undefined for HTTP)\n connect: (() => Promise<void>) | undefined;\n disconnect: (() => void) | undefined;\n\n // File uploads (requires requestUploadUrls)\n uploadFiles: (\n files: FileList | File[],\n onProgress?: (fileIndex: number, progress: number) => void,\n ) => Promise<FileReference[]>;\n}\n\ninterface UserMessageInput {\n content?: string;\n files?: FileList | File[] | FileReference[];\n}\n```\n\n## Transport Reference\n\n### createHttpTransport\n\nCreates an HTTP/SSE transport using native `fetch()`:\n\n```typescript\nimport { createHttpTransport } from '@octavus/react';\n\nconst transport = createHttpTransport({\n triggerRequest: (triggerName, input, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n signal: options?.signal,\n }),\n});\n```\n\n### createSocketTransport\n\nCreates a WebSocket/SockJS transport for real-time connections:\n\n```typescript\nimport { createSocketTransport } from '@octavus/react';\n\nconst transport = createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const ws = new WebSocket(`wss://api.example.com/stream?sessionId=${sessionId}`);\n ws.onopen = () => resolve(ws);\n ws.onerror = () => reject(new Error('Connection failed'));\n }),\n});\n```\n\nSocket transport provides additional connection management:\n\n```typescript\n// Access connection state directly\ntransport.connectionState; // 'disconnected' | 'connecting' | 'connected' | 'error'\n\n// Subscribe to state changes\ntransport.onConnectionStateChange((state, error) => {\n /* ... */\n});\n\n// Eager connection (instead of lazy on first send)\nawait transport.connect();\n\n// Manual disconnect\ntransport.disconnect();\n```\n\nFor detailed WebSocket/SockJS usage including custom events, reconnection patterns, and server-side implementation, see [Socket Transport](/docs/client-sdk/socket-transport).\n\n## Class Reference (Framework-Agnostic)\n\n### OctavusChat\n\n```typescript\nclass OctavusChat {\n constructor(options: OctavusChatOptions);\n\n // State (read-only)\n readonly messages: UIMessage[];\n readonly status: ChatStatus;\n readonly error: OctavusError | null; // Structured error\n\n // Actions\n send(\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput },\n ): Promise<void>;\n stop(): void;\n\n // Subscription\n subscribe(callback: () => void): () => void; // Returns unsubscribe function\n}\n```\n\n## Next Steps\n\n- [HTTP Transport](/docs/client-sdk/http-transport) — HTTP/SSE integration (recommended)\n- [Socket Transport](/docs/client-sdk/socket-transport) — WebSocket and SockJS integration\n- [Messages](/docs/client-sdk/messages) — Working with message state\n- [Streaming](/docs/client-sdk/streaming) — Building streaming UIs\n- [Operations](/docs/client-sdk/execution-blocks) — Showing agent progress\n- [Error Handling](/docs/client-sdk/error-handling) — Handling errors with type guards\n- [File Uploads](/docs/client-sdk/file-uploads) — Uploading images and documents\n- [Examples](/docs/examples/overview) — Complete working examples\n",
71
+ "excerpt": "Client SDK Overview Octavus provides two packages for frontend integration: | Package | Purpose | Use When | |...",
63
72
  "order": 1
64
73
  },
65
74
  {
@@ -67,7 +76,7 @@
67
76
  "section": "client-sdk",
68
77
  "title": "Messages",
69
78
  "description": "Working with message state in the Client SDK.",
70
- "content": "\n# Messages\n\nMessages represent the conversation history. The Client SDK tracks messages automatically and provides structured access to their content through typed parts.\n\n## Message Structure\n\n```typescript\ninterface UIMessage {\n id: string;\n role: 'user' | 'assistant';\n parts: UIMessagePart[];\n status: 'streaming' | 'done';\n createdAt: Date;\n}\n```\n\n### Message Parts\n\nMessages contain ordered `parts` that preserve content ordering:\n\n```typescript\ntype UIMessagePart =\n | UITextPart\n | UIReasoningPart\n | UIToolCallPart\n | UIOperationPart;\n\n// Text content\ninterface UITextPart {\n type: 'text';\n text: string;\n status: 'streaming' | 'done';\n thread?: string; // For named threads (e.g., \"summary\")\n}\n\n// Extended reasoning/thinking\ninterface UIReasoningPart {\n type: 'reasoning';\n text: string;\n status: 'streaming' | 'done';\n thread?: string;\n}\n\n// Tool execution\ninterface UIToolCallPart {\n type: 'tool-call';\n toolCallId: string;\n toolName: string;\n displayName?: string; // Human-readable name\n args: Record<string, unknown>;\n result?: unknown;\n error?: string;\n status: 'pending' | 'running' | 'done' | 'error';\n thread?: string;\n}\n\n// Internal operations (set-resource, serialize-thread)\ninterface UIOperationPart {\n type: 'operation';\n operationId: string;\n name: string;\n operationType: string;\n status: 'running' | 'done';\n thread?: string;\n}\n```\n\n## Sending Messages\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { send } = useOctavusChat({ transport });\n\n async function handleSend(text: string) {\n // Add user message to UI and trigger agent\n await send(\n 'user-message',\n { USER_MESSAGE: text },\n { userMessage: { content: text } },\n );\n }\n\n // ...\n}\n```\n\nThe `send` function:\n1. Adds the user message to the UI immediately (if `userMessage` is provided)\n2. Triggers the agent with the specified trigger name and input\n3. Streams the assistant's response back\n\n## Rendering Messages\n\n### Basic Rendering\n\n```tsx\nfunction MessageList({ messages }: { messages: UIMessage[] }) {\n return (\n <div className=\"space-y-4\">\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n const isUser = message.role === 'user';\n\n return (\n <div className={isUser ? 'text-right' : 'text-left'}>\n <div className=\"inline-block p-3 rounded-lg\">\n {message.parts.map((part, i) => (\n <PartRenderer key={i} part={part} />\n ))}\n </div>\n </div>\n );\n}\n```\n\n### Rendering Parts\n\n```tsx\nimport { isOtherThread, type UIMessagePart } from '@octavus/react';\n\nfunction PartRenderer({ part }: { part: UIMessagePart }) {\n // Check if part belongs to a named thread (e.g., \"summary\")\n if (isOtherThread(part)) {\n return <OtherThreadPart part={part} />;\n }\n\n switch (part.type) {\n case 'text':\n return <TextPart part={part} />;\n\n case 'reasoning':\n return (\n <details className=\"text-gray-500\">\n <summary>Thinking...</summary>\n <pre className=\"text-sm\">{part.text}</pre>\n </details>\n );\n\n case 'tool-call':\n return (\n <div className=\"bg-gray-100 p-2 rounded text-sm\">\n 🔧 {part.displayName || part.toolName}\n {part.status === 'done' && ' ✓'}\n {part.status === 'error' && ` ✗ ${part.error}`}\n </div>\n );\n\n case 'operation':\n return (\n <div className=\"text-gray-500 text-sm\">\n {part.name}\n {part.status === 'done' && ' ✓'}\n </div>\n );\n\n default:\n return null;\n }\n}\n\nfunction TextPart({ part }: { part: UITextPart }) {\n return (\n <p>\n {part.text}\n {part.status === 'streaming' && (\n <span className=\"inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1\" />\n )}\n </p>\n );\n}\n```\n\n## Named Threads\n\nContent from named threads (like \"summary\") is identified by the `thread` property. Use the `isOtherThread` helper:\n\n```tsx\nimport { isOtherThread } from '@octavus/react';\n\nfunction PartRenderer({ part }: { part: UIMessagePart }) {\n if (isOtherThread(part)) {\n // Render differently for named threads\n return (\n <div className=\"bg-amber-50 p-2 rounded border border-amber-200\">\n <span className=\"text-amber-600 text-sm\">\n {part.thread}: {part.type === 'text' && part.text}\n </span>\n </div>\n );\n }\n\n // Regular rendering for main thread\n // ...\n}\n```\n\n## Session Restore\n\nWhen restoring a session, fetch messages from your backend and pass them to the hook:\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n initialMessages: UIMessage[];\n}\n\nfunction Chat({ sessionId, initialMessages }: ChatProps) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n // Pass existing messages to restore the conversation\n const { messages } = useOctavusChat({\n transport,\n initialMessages,\n });\n\n // ...\n}\n```\n\nOn your backend, use `agentSessions.getMessages()` to fetch UI-ready messages:\n\n```typescript\n// Server-side\nconst session = await client.agentSessions.getMessages(sessionId);\n// session.messages is UIMessage[] ready for the client\n```\n\n## Callbacks\n\n```tsx\nuseOctavusChat({\n transport,\n onFinish: () => {\n console.log('Stream completed');\n // Scroll to bottom, play sound, etc.\n },\n onError: (error) => {\n console.error('Error:', error);\n toast.error('Failed to get response');\n },\n onResourceUpdate: (name, value) => {\n console.log('Resource updated:', name, value);\n },\n});\n```\n",
79
+ "content": "\n# Messages\n\nMessages represent the conversation history. The Client SDK tracks messages automatically and provides structured access to their content through typed parts.\n\n## Message Structure\n\n```typescript\ninterface UIMessage {\n id: string;\n role: 'user' | 'assistant';\n parts: UIMessagePart[];\n status: 'streaming' | 'done';\n createdAt: Date;\n}\n```\n\n### Message Parts\n\nMessages contain ordered `parts` that preserve content ordering:\n\n```typescript\ntype UIMessagePart =\n | UITextPart\n | UIReasoningPart\n | UIToolCallPart\n | UIOperationPart\n | UISourcePart\n | UIFilePart\n | UIObjectPart;\n\n// Text content\ninterface UITextPart {\n type: 'text';\n text: string;\n status: 'streaming' | 'done';\n thread?: string; // For named threads (e.g., \"summary\")\n}\n\n// Extended reasoning/thinking\ninterface UIReasoningPart {\n type: 'reasoning';\n text: string;\n status: 'streaming' | 'done';\n thread?: string;\n}\n\n// Tool execution\ninterface UIToolCallPart {\n type: 'tool-call';\n toolCallId: string;\n toolName: string;\n displayName?: string; // Human-readable name\n args: Record<string, unknown>;\n result?: unknown;\n error?: string;\n status: 'pending' | 'running' | 'done' | 'error';\n thread?: string;\n}\n\n// Internal operations (set-resource, serialize-thread)\ninterface UIOperationPart {\n type: 'operation';\n operationId: string;\n name: string;\n operationType: string;\n status: 'running' | 'done';\n thread?: string;\n}\n\n// Source references (from web search, document processing)\ninterface UISourcePart {\n type: 'source';\n sourceType: 'url' | 'document';\n id: string;\n url?: string; // For URL sources\n title?: string;\n mediaType?: string; // For document sources\n filename?: string;\n thread?: string;\n}\n\n// Generated files (from image generation, skills, code execution)\ninterface UIFilePart {\n type: 'file';\n id: string;\n mediaType: string; // MIME type (e.g., 'image/png', 'image/webp')\n url: string; // Download/display URL (presigned S3 URL)\n filename?: string;\n size?: number;\n toolCallId?: string; // Present if from a tool call\n thread?: string;\n}\n\n// Structured output (when responseType is used)\ninterface UIObjectPart {\n type: 'object';\n id: string;\n typeName: string; // Type name from protocol (e.g., \"ChatResponse\")\n partial?: unknown; // Partial object while streaming\n object?: unknown; // Final object when done\n status: 'streaming' | 'done' | 'error';\n error?: string;\n thread?: string;\n}\n```\n\n## Sending Messages\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { send } = useOctavusChat({ transport });\n\n async function handleSend(text: string) {\n // Add user message to UI and trigger agent\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n }\n\n // ...\n}\n```\n\nThe `send` function:\n\n1. Adds the user message to the UI immediately (if `userMessage` is provided)\n2. Triggers the agent with the specified trigger name and input\n3. Streams the assistant's response back\n\n### Message Content Types\n\nThe `content` field in `userMessage` accepts both strings and objects:\n\n```tsx\n// Text content → creates a text part\nawait send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n\n// Object content → creates an object part (uses `type` field as typeName)\nconst selection = { type: 'product_selection', productId: 'abc123', action: 'select' };\nawait send('user-message', { USER_INPUT: selection }, { userMessage: { content: selection } });\n```\n\nWhen passing an object as `content`:\n\n- The SDK creates a `UIObjectPart` instead of a `UITextPart`\n- The object's `type` field is used as the `typeName` (defaults to `'object'` if not present)\n- This is useful for rich UI interactions like product selections, quick replies, etc.\n\n### Sending with Files\n\nInclude file attachments with messages:\n\n```tsx\nimport type { FileReference } from '@octavus/react';\n\nasync function handleSend(text: string, files?: FileReference[]) {\n await send(\n 'user-message',\n {\n USER_MESSAGE: text,\n FILES: files, // Array of FileReference\n },\n {\n userMessage: {\n content: text,\n files: files, // Shows files in user message bubble\n },\n },\n );\n}\n```\n\nSee [File Uploads](/docs/client-sdk/file-uploads) for complete upload flow.\n\n## Rendering Messages\n\n### Basic Rendering\n\n```tsx\nfunction MessageList({ messages }: { messages: UIMessage[] }) {\n return (\n <div className=\"space-y-4\">\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n const isUser = message.role === 'user';\n\n return (\n <div className={isUser ? 'text-right' : 'text-left'}>\n <div className=\"inline-block p-3 rounded-lg\">\n {message.parts.map((part, i) => (\n <PartRenderer key={i} part={part} />\n ))}\n </div>\n </div>\n );\n}\n```\n\n### Rendering Parts\n\n```tsx\nimport { isOtherThread, type UIMessagePart } from '@octavus/react';\n\nfunction PartRenderer({ part }: { part: UIMessagePart }) {\n // Check if part belongs to a named thread (e.g., \"summary\")\n if (isOtherThread(part)) {\n return <OtherThreadPart part={part} />;\n }\n\n switch (part.type) {\n case 'text':\n return <TextPart part={part} />;\n\n case 'reasoning':\n return (\n <details className=\"text-gray-500\">\n <summary>Thinking...</summary>\n <pre className=\"text-sm\">{part.text}</pre>\n </details>\n );\n\n case 'tool-call':\n return (\n <div className=\"bg-gray-100 p-2 rounded text-sm\">\n 🔧 {part.displayName || part.toolName}\n {part.status === 'done' && ' ✓'}\n {part.status === 'error' && ` ✗ ${part.error}`}\n </div>\n );\n\n case 'operation':\n return (\n <div className=\"text-gray-500 text-sm\">\n {part.name}\n {part.status === 'done' && ' ✓'}\n </div>\n );\n\n case 'source':\n return (\n <div className=\"text-blue-500 text-sm\">📎 {part.title || part.url || part.filename}</div>\n );\n\n case 'file':\n // Render images inline, other files as download links\n if (part.mediaType.startsWith('image/')) {\n return (\n <img\n src={part.url}\n alt={part.filename || 'Generated image'}\n className=\"max-w-full rounded-lg\"\n />\n );\n }\n return (\n <a href={part.url} className=\"text-blue-500 text-sm underline\">\n 📄 {part.filename || 'Download file'}\n </a>\n );\n\n case 'object':\n // For structured output, render custom UI based on typeName\n // See Structured Output guide for more details\n return <ObjectPartRenderer part={part} />;\n\n default:\n return null;\n }\n}\n\nfunction TextPart({ part }: { part: UITextPart }) {\n return (\n <p>\n {part.text}\n {part.status === 'streaming' && (\n <span className=\"inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1\" />\n )}\n </p>\n );\n}\n```\n\n## Named Threads\n\nContent from named threads (like \"summary\") is identified by the `thread` property. Use the `isOtherThread` helper:\n\n```tsx\nimport { isOtherThread } from '@octavus/react';\n\nfunction PartRenderer({ part }: { part: UIMessagePart }) {\n if (isOtherThread(part)) {\n // Render differently for named threads\n return (\n <div className=\"bg-amber-50 p-2 rounded border border-amber-200\">\n <span className=\"text-amber-600 text-sm\">\n {part.thread}: {part.type === 'text' && part.text}\n </span>\n </div>\n );\n }\n\n // Regular rendering for main thread\n // ...\n}\n```\n\n## Session Restore\n\nWhen restoring a session, fetch messages from your backend and pass them to the hook:\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n initialMessages: UIMessage[];\n}\n\nfunction Chat({ sessionId, initialMessages }: ChatProps) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n // Pass existing messages to restore the conversation\n const { messages } = useOctavusChat({\n transport,\n initialMessages,\n });\n\n // ...\n}\n```\n\nOn your backend, use `agentSessions.getMessages()` to fetch UI-ready messages:\n\n```typescript\n// Server-side\nconst session = await client.agentSessions.getMessages(sessionId);\n// session.messages is UIMessage[] ready for the client\n```\n\n## Callbacks\n\n```tsx\nuseOctavusChat({\n transport,\n onFinish: () => {\n console.log('Stream completed');\n // Scroll to bottom, play sound, etc.\n },\n onError: (error) => {\n console.error('Error:', error);\n toast.error('Failed to get response');\n },\n onResourceUpdate: (name, value) => {\n console.log('Resource updated:', name, value);\n },\n});\n```\n",
71
80
  "excerpt": "Messages Messages represent the conversation history. The Client SDK tracks messages automatically and provides structured access to their content through typed parts. Message Structure Message...",
72
81
  "order": 2
73
82
  },
@@ -76,7 +85,7 @@
76
85
  "section": "client-sdk",
77
86
  "title": "Streaming",
78
87
  "description": "Building streaming UIs with the Client SDK.",
79
- "content": "\n# Streaming\n\nThe Client SDK provides real-time access to streaming content through the message `parts` array. Each part has its own status, enabling responsive UIs that update as the agent generates responses.\n\n## Streaming State\n\n```tsx\nconst { messages, status, error } = useOctavusChat({ transport });\n\n// status: 'idle' | 'streaming' | 'error'\n// Each message has status: 'streaming' | 'done'\n// Each part has its own status too\n```\n\n## Building a Streaming UI\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, error, send, stop } = useOctavusChat({ transport });\n\n return (\n <div>\n {/* Messages with streaming parts */}\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n\n {/* Error state */}\n {error && <div className=\"text-red-500\">{error.message}</div>}\n\n {/* Stop button during streaming */}\n {status === 'streaming' && <button onClick={stop}>Stop</button>}\n </div>\n );\n}\n```\n\n## Rendering Streaming Parts\n\nParts update in real-time during streaming. Use the part's `status` to show appropriate UI:\n\n```tsx\nimport type { UITextPart, UIReasoningPart } from '@octavus/react';\n\nfunction TextPart({ part }: { part: UITextPart }) {\n return (\n <div>\n {part.text}\n {part.status === 'streaming' && (\n <span className=\"inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1\" />\n )}\n </div>\n );\n}\n\nfunction ReasoningPart({ part }: { part: UIReasoningPart }) {\n // Expand while streaming, collapse when done\n const [expanded, setExpanded] = useState(part.status === 'streaming');\n\n return (\n <div className=\"bg-purple-50 p-3 rounded-lg\">\n <button onClick={() => setExpanded(!expanded)}>\n {part.status === 'streaming' ? '💭 Thinking...' : '💭 Thought process'}\n {expanded ? '▼' : '▶'}\n </button>\n\n {expanded && (\n <pre className=\"mt-2 text-sm text-gray-600\">{part.text}</pre>\n )}\n </div>\n );\n}\n```\n\n## Tool Call States\n\nTool calls progress through multiple states:\n\n```tsx\nimport type { UIToolCallPart } from '@octavus/react';\n\nfunction ToolCallPart({ part }: { part: UIToolCallPart }) {\n return (\n <div className=\"border rounded p-3\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-lg\">🔧</span>\n <span className=\"font-medium\">{part.displayName || part.toolName}</span>\n <StatusBadge status={part.status} />\n </div>\n\n {/* Show result when done */}\n {part.status === 'done' && part.result && (\n <pre className=\"mt-2 text-xs bg-gray-50 p-2 rounded\">\n {JSON.stringify(part.result, null, 2)}\n </pre>\n )}\n\n {/* Show error if failed */}\n {part.status === 'error' && (\n <p className=\"mt-2 text-red-500 text-sm\">{part.error}</p>\n )}\n </div>\n );\n}\n\nfunction StatusBadge({ status }: { status: UIToolCallPart['status'] }) {\n switch (status) {\n case 'pending':\n return <span className=\"text-gray-400\">○</span>;\n case 'running':\n return <span className=\"text-blue-500 animate-spin\">◐</span>;\n case 'done':\n return <span className=\"text-green-500\">✓</span>;\n case 'error':\n return <span className=\"text-red-500\">✗</span>;\n }\n}\n```\n\n## Status Indicator\n\n```tsx\nfunction StatusIndicator({ status }: { status: ChatStatus }) {\n switch (status) {\n case 'idle':\n return null;\n case 'streaming':\n return <div>Agent is responding...</div>;\n case 'error':\n return <div className=\"text-red-500\">Something went wrong</div>;\n }\n}\n```\n\n## Handling Completion\n\n```tsx\nuseOctavusChat({\n transport,\n onFinish: () => {\n console.log('Stream completed');\n // Scroll to bottom, play sound, etc.\n },\n onError: (error) => {\n console.error('Stream error:', error);\n toast.error('Failed to get response');\n },\n});\n```\n\n## Stop Function\n\nStop the current stream and finalize any partial message:\n\n```tsx\nconst { status, stop } = useOctavusChat({ transport });\n\n// Stop button\n{status === 'streaming' && (\n <button onClick={stop} className=\"text-gray-500\">\n Stop generating\n </button>\n)}\n```\n\nWhen `stop()` is called:\n1. The current request is aborted\n2. Any partial message is finalized with current content\n3. Status changes to `'idle'`\n\n## Named Thread Content\n\nContent from named threads (like \"summary\") streams separately and is identified by the `thread` property:\n\n```tsx\nimport { isOtherThread, type UIMessage } from '@octavus/react';\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n // Separate main thread from named threads\n const mainParts = message.parts.filter((p) => !isOtherThread(p));\n const otherParts = message.parts.filter((p) => isOtherThread(p));\n\n return (\n <div>\n {/* Main conversation */}\n {mainParts.map((part, i) => (\n <PartRenderer key={i} part={part} />\n ))}\n\n {/* Named thread content (e.g., summarization) */}\n {otherParts.length > 0 && (\n <div className=\"bg-amber-50 p-3 rounded mt-4 border border-amber-200\">\n <div className=\"text-amber-600 font-medium mb-2\">\n Background processing\n </div>\n {otherParts.map((part, i) => (\n <PartRenderer key={i} part={part} />\n ))}\n </div>\n )}\n </div>\n );\n}\n```\n",
88
+ "content": "\n# Streaming\n\nThe Client SDK provides real-time access to streaming content through the message `parts` array. Each part has its own status, enabling responsive UIs that update as the agent generates responses.\n\n## Streaming State\n\n```tsx\nconst { messages, status, error } = useOctavusChat({ transport });\n\n// status: 'idle' | 'streaming' | 'error'\n// Each message has status: 'streaming' | 'done'\n// Each part has its own status too\n```\n\n## Building a Streaming UI\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, error, send, stop } = useOctavusChat({ transport });\n\n return (\n <div>\n {/* Messages with streaming parts */}\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n\n {/* Error state */}\n {error && <div className=\"text-red-500\">{error.message}</div>}\n\n {/* Stop button during streaming */}\n {status === 'streaming' && <button onClick={stop}>Stop</button>}\n </div>\n );\n}\n```\n\n## Rendering Streaming Parts\n\nParts update in real-time during streaming. Use the part's `status` to show appropriate UI:\n\n```tsx\nimport type { UITextPart, UIReasoningPart } from '@octavus/react';\n\nfunction TextPart({ part }: { part: UITextPart }) {\n return (\n <div>\n {part.text}\n {part.status === 'streaming' && (\n <span className=\"inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1\" />\n )}\n </div>\n );\n}\n\nfunction ReasoningPart({ part }: { part: UIReasoningPart }) {\n // Expand while streaming, collapse when done\n const [expanded, setExpanded] = useState(part.status === 'streaming');\n\n return (\n <div className=\"bg-purple-50 p-3 rounded-lg\">\n <button onClick={() => setExpanded(!expanded)}>\n {part.status === 'streaming' ? '💭 Thinking...' : '💭 Thought process'}\n {expanded ? '▼' : '▶'}\n </button>\n\n {expanded && <pre className=\"mt-2 text-sm text-gray-600\">{part.text}</pre>}\n </div>\n );\n}\n```\n\n## Tool Call States\n\nTool calls progress through multiple states:\n\n```tsx\nimport type { UIToolCallPart } from '@octavus/react';\n\nfunction ToolCallPart({ part }: { part: UIToolCallPart }) {\n return (\n <div className=\"border rounded p-3\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-lg\">🔧</span>\n <span className=\"font-medium\">{part.displayName || part.toolName}</span>\n <StatusBadge status={part.status} />\n </div>\n\n {/* Show result when done */}\n {part.status === 'done' && part.result && (\n <pre className=\"mt-2 text-xs bg-gray-50 p-2 rounded\">\n {JSON.stringify(part.result, null, 2)}\n </pre>\n )}\n\n {/* Show error if failed */}\n {part.status === 'error' && <p className=\"mt-2 text-red-500 text-sm\">{part.error}</p>}\n\n {/* Show cancelled state */}\n {part.status === 'cancelled' && <p className=\"mt-2 text-amber-500 text-sm\">Cancelled</p>}\n </div>\n );\n}\n\nfunction StatusBadge({ status }: { status: UIToolCallPart['status'] }) {\n switch (status) {\n case 'pending':\n return <span className=\"text-gray-400\">○</span>;\n case 'running':\n return <span className=\"text-blue-500 animate-spin\">◐</span>;\n case 'done':\n return <span className=\"text-green-500\">✓</span>;\n case 'error':\n return <span className=\"text-red-500\">✗</span>;\n case 'cancelled':\n return <span className=\"text-amber-500\">◼</span>;\n }\n}\n```\n\n## Status Indicator\n\n```tsx\nfunction StatusIndicator({ status }: { status: ChatStatus }) {\n switch (status) {\n case 'idle':\n return null;\n case 'streaming':\n return <div>Agent is responding...</div>;\n case 'error':\n return <div className=\"text-red-500\">Something went wrong</div>;\n }\n}\n```\n\n## Handling Completion\n\n```tsx\nimport { isRateLimitError, type OctavusError } from '@octavus/react';\n\nuseOctavusChat({\n transport,\n onFinish: () => {\n console.log('Stream completed successfully');\n // Scroll to bottom, play sound, etc.\n },\n onStop: () => {\n console.log('User stopped generation');\n // Handle stop - content is preserved\n },\n onError: (error: OctavusError) => {\n console.error('Stream error:', error.errorType, error.message);\n\n if (isRateLimitError(error)) {\n toast.error(`Rate limited. Retry in ${error.retryAfter}s`);\n } else {\n toast.error('Failed to get response');\n }\n },\n});\n```\n\nSee [Error Handling](/docs/client-sdk/error-handling) for comprehensive error handling patterns.\n\n````\n\n## Stop Function\n\nStop the current stream and finalize any partial message:\n\n```tsx\nconst { status, stop } = useOctavusChat({ transport });\n\n// Stop button\n{status === 'streaming' && (\n <button onClick={stop} className=\"text-gray-500\">\n Stop generating\n </button>\n)}\n````\n\nWhen `stop()` is called:\n\n1. The HTTP request is aborted (requires `signal` in transport)\n2. Any partial text/reasoning is finalized with `done` status\n3. In-progress tool calls are marked as `cancelled`\n4. The `onStop` callback is invoked\n5. Status changes to `idle`\n\nPartial content is preserved in the message, so users don't lose what was already generated.\n\n## Named Thread Content\n\nContent from named threads (like \"summary\") streams separately and is identified by the `thread` property:\n\n```tsx\nimport { isOtherThread, type UIMessage } from '@octavus/react';\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n // Separate main thread from named threads\n const mainParts = message.parts.filter((p) => !isOtherThread(p));\n const otherParts = message.parts.filter((p) => isOtherThread(p));\n\n return (\n <div>\n {/* Main conversation */}\n {mainParts.map((part, i) => (\n <PartRenderer key={i} part={part} />\n ))}\n\n {/* Named thread content (e.g., summarization) */}\n {otherParts.length > 0 && (\n <div className=\"bg-amber-50 p-3 rounded mt-4 border border-amber-200\">\n <div className=\"text-amber-600 font-medium mb-2\">Background processing</div>\n {otherParts.map((part, i) => (\n <PartRenderer key={i} part={part} />\n ))}\n </div>\n )}\n </div>\n );\n}\n```\n",
80
89
  "excerpt": "Streaming The Client SDK provides real-time access to streaming content through the message array. Each part has its own status, enabling responsive UIs that update as the agent generates responses....",
81
90
  "order": 3
82
91
  },
@@ -85,7 +94,7 @@
85
94
  "section": "client-sdk",
86
95
  "title": "Operations",
87
96
  "description": "Showing agent operations and progress with the Client SDK.",
88
- "content": "\n# Operations\n\nOperations represent internal agent activities like setting resources or serializing threads. They appear as `operation` parts in messages and help users understand what the agent is doing.\n\n## Operation Structure\n\n```typescript\ninterface UIOperationPart {\n type: 'operation';\n operationId: string;\n name: string; // Human-readable name\n operationType: string; // e.g., 'set-resource', 'serialize-thread'\n status: 'running' | 'done';\n thread?: string; // For named threads\n}\n```\n\n## Rendering Operations\n\nOperations are typically shown as compact status indicators:\n\n```tsx\nimport type { UIOperationPart } from '@octavus/react';\n\nfunction OperationCard({ operation }: { operation: UIOperationPart }) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-gray-500\">\n {operation.status === 'running' ? (\n <span className=\"h-2 w-2 animate-pulse rounded-full bg-blue-500\" />\n ) : (\n <span className=\"text-green-500\">✓</span>\n )}\n <span>{operation.name}</span>\n </div>\n );\n}\n```\n\n## Operations in Messages\n\nOperations appear alongside text, reasoning, and tool calls in the message's `parts` array:\n\n```tsx\nimport type { UIMessage, UIMessagePart } from '@octavus/react';\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => (\n <PartRenderer key={i} part={part} />\n ))}\n </div>\n );\n}\n\nfunction PartRenderer({ part }: { part: UIMessagePart }) {\n switch (part.type) {\n case 'text':\n return <TextPart part={part} />;\n case 'reasoning':\n return <ReasoningPart part={part} />;\n case 'tool-call':\n return <ToolCallCard part={part} />;\n case 'operation':\n return <OperationCard operation={part} />;\n default:\n return null;\n }\n}\n```\n\n## Common Operation Types\n\n| Type | Description |\n|------|-------------|\n| `set-resource` | Updating a resource value |\n| `serialize-thread` | Converting thread messages to text |\n\n## Example: Progress During Escalation\n\nWhen a user clicks \"Talk to Human\", multiple operations may occur:\n\n```tsx\nfunction EscalationProgress({ message }: { message: UIMessage }) {\n const operations = message.parts.filter(\n (p): p is UIOperationPart => p.type === 'operation'\n );\n\n return (\n <div className=\"space-y-2\">\n {operations.map((op) => (\n <div key={op.operationId} className=\"flex items-center gap-2 text-sm\">\n {op.status === 'running' ? '⏳' : '✓'}\n <span>{op.name}</span>\n </div>\n ))}\n </div>\n );\n}\n\n// Example output during escalation:\n// ✓ Serialize conversation\n// ✓ Save conversation summary\n// ⏳ Creating support ticket...\n```\n\n## Display Modes\n\nOperations are only sent to the client if their protocol block has a visible display mode (`name`, `description`, or `stream`). Hidden operations (`display: hidden`) are filtered out by the platform before reaching the client.\n\nThis means you can safely render all operations without checking display mode — hidden ones won't be in the message parts.\n\n## Named Thread Operations\n\nOperations can belong to named threads. Use the `thread` property to identify them:\n\n```tsx\nfunction OperationCard({ operation }: { operation: UIOperationPart }) {\n return (\n <div className=\"flex items-center gap-2 text-sm\">\n {operation.thread && (\n <span className=\"text-amber-500\">[{operation.thread}]</span>\n )}\n <span>{operation.name}</span>\n {operation.status === 'done' && <span className=\"text-green-500\">✓</span>}\n </div>\n );\n}\n```\n",
97
+ "content": "\n# Operations\n\nOperations represent internal agent activities like setting resources or serializing threads. They appear as `operation` parts in messages and help users understand what the agent is doing.\n\n## Operation Structure\n\n```typescript\ninterface UIOperationPart {\n type: 'operation';\n operationId: string;\n name: string; // Human-readable name\n operationType: string; // e.g., 'set-resource', 'serialize-thread'\n status: 'running' | 'done';\n thread?: string; // For named threads\n}\n```\n\n## Rendering Operations\n\nOperations are typically shown as compact status indicators:\n\n```tsx\nimport type { UIOperationPart } from '@octavus/react';\n\nfunction OperationCard({ operation }: { operation: UIOperationPart }) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-gray-500\">\n {operation.status === 'running' ? (\n <span className=\"h-2 w-2 animate-pulse rounded-full bg-blue-500\" />\n ) : (\n <span className=\"text-green-500\">✓</span>\n )}\n <span>{operation.name}</span>\n </div>\n );\n}\n```\n\n## Operations in Messages\n\nOperations appear alongside text, reasoning, and tool calls in the message's `parts` array:\n\n```tsx\nimport type { UIMessage, UIMessagePart } from '@octavus/react';\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => (\n <PartRenderer key={i} part={part} />\n ))}\n </div>\n );\n}\n\nfunction PartRenderer({ part }: { part: UIMessagePart }) {\n switch (part.type) {\n case 'text':\n return <TextPart part={part} />;\n case 'reasoning':\n return <ReasoningPart part={part} />;\n case 'tool-call':\n return <ToolCallCard part={part} />;\n case 'operation':\n return <OperationCard operation={part} />;\n default:\n return null;\n }\n}\n```\n\n## Common Operation Types\n\n| Type | Description |\n| ------------------ | ---------------------------------- |\n| `set-resource` | Updating a resource value |\n| `serialize-thread` | Converting thread messages to text |\n\n## Example: Progress During Escalation\n\nWhen a user clicks \"Talk to Human\", multiple operations may occur:\n\n```tsx\nfunction EscalationProgress({ message }: { message: UIMessage }) {\n const operations = message.parts.filter((p): p is UIOperationPart => p.type === 'operation');\n\n return (\n <div className=\"space-y-2\">\n {operations.map((op) => (\n <div key={op.operationId} className=\"flex items-center gap-2 text-sm\">\n {op.status === 'running' ? '⏳' : '✓'}\n <span>{op.name}</span>\n </div>\n ))}\n </div>\n );\n}\n\n// Example output during escalation:\n// ✓ Serialize conversation\n// ✓ Save conversation summary\n// ⏳ Creating support ticket...\n```\n\n## Display Modes\n\nOperations are only sent to the client if their protocol block has a visible display mode (`name`, `description`, or `stream`). Hidden operations (`display: hidden`) are filtered out by the platform before reaching the client.\n\nThis means you can safely render all operations without checking display mode — hidden ones won't be in the message parts.\n\n## Named Thread Operations\n\nOperations can belong to named threads. Use the `thread` property to identify them:\n\n```tsx\nfunction OperationCard({ operation }: { operation: UIOperationPart }) {\n return (\n <div className=\"flex items-center gap-2 text-sm\">\n {operation.thread && <span className=\"text-amber-500\">[{operation.thread}]</span>}\n <span>{operation.name}</span>\n {operation.status === 'done' && <span className=\"text-green-500\">✓</span>}\n </div>\n );\n}\n```\n",
89
98
  "excerpt": "Operations Operations represent internal agent activities like setting resources or serializing threads. They appear as parts in messages and help users understand what the agent is doing. ...",
90
99
  "order": 4
91
100
  },
@@ -94,7 +103,7 @@
94
103
  "section": "client-sdk",
95
104
  "title": "Socket Transport",
96
105
  "description": "Using WebSocket or SockJS for real-time streaming with Octavus.",
97
- "content": "\n# Socket Transport\n\nThe socket transport enables real-time bidirectional communication using WebSocket or SockJS. Use this when you need persistent connections, custom server events, or when HTTP/SSE isn't suitable for your infrastructure.\n\n## When to Use Socket Transport\n\n| Use Case | Recommended Transport |\n|----------|----------------------|\n| Standard web apps (Next.js, etc.) | HTTP (`createHttpTransport`) |\n| Real-time apps with custom events | Socket (`createSocketTransport`) |\n| Apps behind proxies that don't support SSE | Socket |\n| Need for typing indicators, presence, etc. | Socket |\n| Meteor, Phoenix, or socket-based frameworks | Socket |\n\n## Patterns Overview\n\nThere are two main patterns for socket-based integrations:\n\n| Pattern | When to Use |\n|---------|-------------|\n| [Server-Managed Sessions](#server-managed-sessions-recommended) | **Recommended.** Server creates sessions lazily. Client doesn't need sessionId. |\n| [Client-Provided Session ID](#client-provided-session-id) | When client must control session creation or pass sessionId from URL. |\n\n## Server-Managed Sessions (Recommended)\n\nThe cleanest pattern is to have the server manage session lifecycle. The client never needs to know about `sessionId` — the server creates it lazily on first message.\n\n### Client Setup\n\n```typescript\nimport { useMemo } from 'react';\nimport SockJS from 'sockjs-client';\nimport { useOctavusChat, createSocketTransport, type SocketLike } from '@octavus/react';\n\nfunction connectSocket(): Promise<SocketLike> {\n return new Promise((resolve, reject) => {\n const sock = new SockJS('/octavus');\n sock.onopen = () => resolve(sock);\n sock.onerror = () => reject(new Error('Connection failed'));\n });\n}\n\nfunction Chat() {\n // Transport is stable — no dependencies on sessionId\n const transport = useMemo(\n () => createSocketTransport({ connect: connectSocket }),\n [],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send(\n 'user-message',\n { USER_MESSAGE: text },\n { userMessage: { content: text } },\n );\n };\n\n // ... render messages\n}\n```\n\n### Server Setup (Express + SockJS)\n\nThe server creates a session on first trigger message:\n\n```typescript\nimport sockjs from 'sockjs';\nimport { OctavusClient, type AgentSession } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nfunction createSocketHandler() {\n return (conn: sockjs.Connection) => {\n let session: AgentSession | null = null;\n let abortController: AbortController | null = null;\n\n conn.on('data', (rawData: string) => {\n void handleMessage(rawData);\n });\n\n async function handleMessage(rawData: string) {\n const msg = JSON.parse(rawData);\n\n if (msg.type === 'stop') {\n abortController?.abort();\n return;\n }\n\n if (msg.type === 'trigger') {\n // Create session lazily on first trigger\n if (!session) {\n const sessionId = await client.agentSessions.create('your-agent-id', {\n // Initial input variables\n COMPANY_NAME: 'Acme Corp',\n });\n session = client.agentSessions.attach(sessionId, {\n tools: {\n // Your tool handlers\n },\n });\n }\n\n abortController = new AbortController();\n\n // Iterate events directly — no SSE parsing needed\n const events = session.trigger(msg.triggerName, msg.input);\n\n try {\n for await (const event of events) {\n if (abortController.signal.aborted) break;\n conn.write(JSON.stringify(event));\n }\n } catch {\n // Handle errors\n }\n }\n }\n\n conn.on('close', () => abortController?.abort());\n };\n}\n\nconst sockServer = sockjs.createServer({ prefix: '/octavus' });\nsockServer.on('connection', createSocketHandler());\nsockServer.installHandlers(httpServer);\n```\n\n**Benefits of this pattern:**\n- Client code is simple — no sessionId management\n- No transport caching issues\n- Session is created only when needed\n- Server controls session configuration\n\n## Client-Provided Session ID\n\nIf you need the client to control the session (e.g., resuming a specific session from URL), pass the sessionId in an init message after connecting:\n\n```typescript\nimport { useMemo } from 'react';\nimport SockJS from 'sockjs-client';\nimport { useOctavusChat, createSocketTransport, type SocketLike } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const sock = new SockJS('/octavus');\n sock.onopen = () => {\n // Send init message with sessionId\n sock.send(JSON.stringify({ type: 'init', sessionId }));\n resolve(sock);\n };\n sock.onerror = () => reject(new Error('Connection failed'));\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n // ... render chat\n}\n```\n\nWhen `sessionId` changes, the hook automatically reinitializes with the new transport.\n\n### Server Handler with Init Message\n\nWhen using client-provided sessionId, the server must handle an `init` message:\n\n```typescript\nsockServer.on('connection', (conn) => {\n let session: AgentSession | null = null;\n let abortController: AbortController | null = null;\n\n conn.on('data', (rawData: string) => {\n void handleMessage(rawData);\n });\n\n async function handleMessage(rawData: string) {\n const msg = JSON.parse(rawData);\n\n // Handle session initialization\n if (msg.type === 'init') {\n session = client.agentSessions.attach(msg.sessionId, {\n tools: { /* ... */ },\n });\n return;\n }\n\n if (msg.type === 'stop') {\n abortController?.abort();\n return;\n }\n\n // All other messages require initialized session\n if (!session) {\n conn.write(JSON.stringify({\n type: 'error',\n errorText: 'Session not initialized. Send init message first.',\n }));\n return;\n }\n\n if (msg.type === 'trigger') {\n // ... handle trigger (same as server-managed pattern)\n }\n }\n});\n```\n\n## Async Session ID\n\nWhen the session ID is fetched asynchronously (e.g., from an API), you have two options:\n\n### Option 1: Conditionally Render (Recommended)\n\nDon't render the chat component until `sessionId` is available:\n\n```tsx\nfunction ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n api.createSession().then(res => setSessionId(res.sessionId));\n }, []);\n\n // Don't render until sessionId is ready\n if (!sessionId) {\n return <LoadingSpinner />;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\nThis is the cleanest approach — the `Chat` component always receives a valid `sessionId`.\n\n### Option 2: Server-Managed Sessions\n\nUse the [server-managed sessions pattern](#server-managed-sessions-recommended) where the server creates the session lazily. The client never needs to know about `sessionId`.\n\n## Native WebSocket\n\nIf you're using native WebSocket instead of SockJS, you can pass sessionId via URL:\n\n```typescript\nconst transport = useMemo(\n () =>\n createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const ws = new WebSocket(\n `wss://your-server.com/octavus?sessionId=${sessionId}`\n );\n ws.onopen = () => resolve(ws);\n ws.onerror = () => reject(new Error('WebSocket connection failed'));\n }),\n }),\n [sessionId],\n);\n```\n\nWhen `sessionId` changes, the hook automatically reinitializes with the new transport.\n\n## Custom Events\n\nHandle custom events alongside Octavus stream events:\n\n```typescript\nconst transport = createSocketTransport({\n connect: connectSocket,\n\n onMessage: (data) => {\n const msg = data as { type: string; [key: string]: unknown };\n\n switch (msg.type) {\n case 'typing-indicator':\n setAgentTyping(msg.isTyping as boolean);\n break;\n\n case 'presence-update':\n setOnlineUsers(msg.users as string[]);\n break;\n\n case 'notification':\n showToast(msg.message as string);\n break;\n\n // Octavus events (text-delta, finish, etc.) are handled automatically\n }\n },\n});\n```\n\n## Connection Management\n\n### Handling Disconnections\n\n```typescript\nconst transport = createSocketTransport({\n connect: connectSocket,\n\n onClose: () => {\n console.log('Socket disconnected');\n setConnectionStatus('disconnected');\n },\n});\n```\n\n### Reconnection with Exponential Backoff\n\n```typescript\nimport { useRef, useCallback, useMemo } from 'react';\nimport { createSocketTransport, type SocketLike } from '@octavus/react';\n\nfunction useReconnectingTransport() {\n const reconnectAttempts = useRef(0);\n const maxAttempts = 5;\n\n const connect = useCallback((): Promise<SocketLike> => {\n return new Promise((resolve, reject) => {\n const sock = new SockJS('/octavus');\n\n sock.onopen = () => {\n reconnectAttempts.current = 0;\n resolve(sock);\n };\n\n sock.onerror = () => {\n if (reconnectAttempts.current < maxAttempts) {\n reconnectAttempts.current++;\n const delay = Math.min(1000 * 2 ** reconnectAttempts.current, 30000);\n console.log(`Reconnecting in ${delay}ms...`);\n setTimeout(() => connect().then(resolve).catch(reject), delay);\n } else {\n reject(new Error('Max reconnection attempts reached'));\n }\n };\n });\n }, []);\n\n return useMemo(\n () => createSocketTransport({ connect }),\n [connect],\n );\n}\n```\n\n## Framework Notes\n\n### Meteor\n\nMeteor's bundler may have issues with ES6 imports of `sockjs-client`. Use `require()` instead:\n\n```typescript\n// ❌ May fail in Meteor\nimport SockJS from 'sockjs-client';\n\n// ✅ Works in Meteor\nconst SockJS: typeof import('sockjs-client') = require('sockjs-client');\n```\n\n### SockJS vs WebSocket\n\n| Feature | WebSocket | SockJS |\n|---------|-----------|--------|\n| Browser support | Modern browsers | All browsers (with fallbacks) |\n| Session ID | Via URL query params | Via init message |\n| Proxy compatibility | Varies | Excellent (polling fallback) |\n| Setup complexity | Lower | Higher (requires server library) |\n\n## Protocol Reference\n\n### Client → Server Messages\n\n```typescript\n// Initialize session (only for client-provided sessionId pattern)\n{ type: 'init', sessionId: string }\n\n// Trigger an action\n{ type: 'trigger', triggerName: string, input?: Record<string, unknown> }\n\n// Stop current stream\n{ type: 'stop' }\n```\n\n### Server → Client Messages\n\nThe server sends Octavus `StreamEvent` objects as JSON. See [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list.\n\n```typescript\n// Examples\n{ type: 'start', messageId: '...' }\n{ type: 'text-delta', id: '...', delta: 'Hello' }\n{ type: 'tool-input-start', toolCallId: '...', toolName: 'get-user' }\n{ type: 'finish', finishReason: 'stop' }\n{ type: 'error', errorText: 'Something went wrong' }\n```\n\n## Full Example\n\nFor a complete walkthrough of building a chat interface with SockJS, see the [Socket Chat Example](/docs/examples/socket-chat).\n",
106
+ "content": "\n# Socket Transport\n\nThe socket transport enables real-time bidirectional communication using WebSocket or SockJS. Use this when you need persistent connections, custom server events, or when HTTP/SSE isn't suitable for your infrastructure.\n\n## When to Use Socket Transport\n\n| Use Case | Recommended Transport |\n| ------------------------------------------- | -------------------------------- |\n| Standard web apps (Next.js, etc.) | HTTP (`createHttpTransport`) |\n| Real-time apps with custom events | Socket (`createSocketTransport`) |\n| Apps behind proxies that don't support SSE | Socket |\n| Need for typing indicators, presence, etc. | Socket |\n| Meteor, Phoenix, or socket-based frameworks | Socket |\n\n## Connection Lifecycle\n\nBy default, socket transport uses **lazy connection** — the socket connects only when you first call `send()`. This is efficient but can be surprising if you want to show connection status.\n\nFor UI indicators, use **eager connection**:\n\n```typescript\nimport { useEffect, useMemo } from 'react';\nimport SockJS from 'sockjs-client';\nimport { useOctavusChat, createSocketTransport, type SocketLike } from '@octavus/react';\n\nfunction Chat() {\n const transport = useMemo(\n () => createSocketTransport({\n connect: () => new Promise((resolve, reject) => {\n const sock = new SockJS('/octavus');\n sock.onopen = () => resolve(sock);\n sock.onerror = () => reject(new Error('Connection failed'));\n }),\n }),\n [],\n );\n\n const {\n messages,\n status,\n send,\n // Socket-specific connection state\n connectionState, // 'disconnected' | 'connecting' | 'connected' | 'error'\n connectionError, // Error object if connectionState is 'error'\n connect, // () => Promise<void>\n disconnect, // () => void\n } = useOctavusChat({ transport });\n\n // Eagerly connect on mount\n useEffect(() => {\n connect?.();\n return () => disconnect?.();\n }, [connect, disconnect]);\n\n return (\n <div>\n <ConnectionIndicator state={connectionState} />\n {/* Chat UI */}\n </div>\n );\n}\n```\n\n### Connection States\n\n| State | Description |\n| -------------- | ----------------------------------------------------- |\n| `disconnected` | Not connected (initial state or after `disconnect()`) |\n| `connecting` | Connection attempt in progress |\n| `connected` | Socket is open and ready |\n| `error` | Connection failed (check `connectionError`) |\n\n## Patterns Overview\n\nThere are two main patterns for socket-based integrations:\n\n| Pattern | When to Use |\n| --------------------------------------------------------------- | ------------------------------------------------------------------------------- |\n| [Server-Managed Sessions](#server-managed-sessions-recommended) | **Recommended.** Server creates sessions lazily. Client doesn't need sessionId. |\n| [Client-Provided Session ID](#client-provided-session-id) | When client must control session creation or pass sessionId from URL. |\n\n## Server-Managed Sessions (Recommended)\n\nThe cleanest pattern is to have the server manage session lifecycle. The client never needs to know about `sessionId` — the server creates it lazily on first message.\n\n### Client Setup\n\n```typescript\nimport { useEffect, useMemo } from 'react';\nimport SockJS from 'sockjs-client';\nimport { useOctavusChat, createSocketTransport, type SocketLike } from '@octavus/react';\n\nfunction connectSocket(): Promise<SocketLike> {\n return new Promise((resolve, reject) => {\n const sock = new SockJS('/octavus');\n sock.onopen = () => resolve(sock);\n sock.onerror = () => reject(new Error('Connection failed'));\n });\n}\n\nfunction Chat() {\n // Transport is stable — no dependencies on sessionId\n const transport = useMemo(() => createSocketTransport({ connect: connectSocket }), []);\n\n const { messages, status, send, stop, connectionState, connect, disconnect } = useOctavusChat({\n transport,\n });\n\n // Eagerly connect for UI status indicator\n useEffect(() => {\n connect?.();\n return () => disconnect?.();\n }, [connect, disconnect]);\n\n const sendMessage = async (text: string) => {\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n };\n\n // ... render chat UI\n}\n```\n\n### Server Setup (Express + SockJS)\n\nThe server creates a session on first trigger message:\n\n```typescript\nimport sockjs from 'sockjs';\nimport { OctavusClient, type AgentSession } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nfunction createSocketHandler() {\n return (conn: sockjs.Connection) => {\n let session: AgentSession | null = null;\n let abortController: AbortController | null = null;\n\n conn.on('data', (rawData: string) => {\n void handleMessage(rawData);\n });\n\n async function handleMessage(rawData: string) {\n const msg = JSON.parse(rawData);\n\n if (msg.type === 'stop') {\n abortController?.abort();\n return;\n }\n\n if (msg.type === 'trigger') {\n // Create session lazily on first trigger\n if (!session) {\n const sessionId = await client.agentSessions.create('your-agent-id', {\n // Initial input variables\n COMPANY_NAME: 'Acme Corp',\n });\n session = client.agentSessions.attach(sessionId, {\n tools: {\n // Your tool handlers\n },\n });\n }\n\n abortController = new AbortController();\n\n // Iterate events directly — no SSE parsing needed\n const events = session.trigger(msg.triggerName, msg.input, {\n signal: abortController.signal,\n });\n\n try {\n for await (const event of events) {\n conn.write(JSON.stringify(event));\n }\n } catch {\n // Handle errors\n }\n }\n }\n\n conn.on('close', () => abortController?.abort());\n };\n}\n\nconst sockServer = sockjs.createServer({ prefix: '/octavus' });\nsockServer.on('connection', createSocketHandler());\nsockServer.installHandlers(httpServer);\n```\n\n**Benefits of this pattern:**\n\n- Client code is simple — no sessionId management\n- No transport caching issues\n- Session is created only when needed\n- Server controls session configuration\n\n## Client-Provided Session ID\n\nIf you need the client to control the session (e.g., resuming a specific session from URL), pass the sessionId in an init message after connecting:\n\n```typescript\nimport { useMemo } from 'react';\nimport SockJS from 'sockjs-client';\nimport { useOctavusChat, createSocketTransport, type SocketLike } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const sock = new SockJS('/octavus');\n sock.onopen = () => {\n // Send init message with sessionId\n sock.send(JSON.stringify({ type: 'init', sessionId }));\n resolve(sock);\n };\n sock.onerror = () => reject(new Error('Connection failed'));\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n // ... render chat\n}\n```\n\nWhen `sessionId` changes, the hook automatically reinitializes with the new transport.\n\n### Server Handler with Init Message\n\nWhen using client-provided sessionId, the server must handle an `init` message:\n\n```typescript\nsockServer.on('connection', (conn) => {\n let session: AgentSession | null = null;\n let abortController: AbortController | null = null;\n\n conn.on('data', (rawData: string) => {\n void handleMessage(rawData);\n });\n\n async function handleMessage(rawData: string) {\n const msg = JSON.parse(rawData);\n\n // Handle session initialization\n if (msg.type === 'init') {\n session = client.agentSessions.attach(msg.sessionId, {\n tools: {\n /* ... */\n },\n });\n return;\n }\n\n if (msg.type === 'stop') {\n abortController?.abort();\n return;\n }\n\n // All other messages require initialized session\n if (!session) {\n conn.write(\n JSON.stringify({\n type: 'error',\n errorType: 'validation_error',\n message: 'Session not initialized. Send init message first.',\n source: 'platform',\n retryable: false,\n }),\n );\n return;\n }\n\n if (msg.type === 'trigger') {\n // ... handle trigger (same as server-managed pattern)\n }\n }\n});\n```\n\n## Async Session ID\n\nWhen the session ID is fetched asynchronously (e.g., from an API), you have two options:\n\n### Option 1: Conditionally Render (Recommended)\n\nDon't render the chat component until `sessionId` is available:\n\n```tsx\nfunction ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n api.createSession().then((res) => setSessionId(res.sessionId));\n }, []);\n\n // Don't render until sessionId is ready\n if (!sessionId) {\n return <LoadingSpinner />;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\nThis is the cleanest approach — the `Chat` component always receives a valid `sessionId`.\n\n### Option 2: Server-Managed Sessions\n\nUse the [server-managed sessions pattern](#server-managed-sessions-recommended) where the server creates the session lazily. The client never needs to know about `sessionId`.\n\n## Native WebSocket\n\nIf you're using native WebSocket instead of SockJS, you can pass sessionId via URL:\n\n```typescript\nconst transport = useMemo(\n () =>\n createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const ws = new WebSocket(`wss://your-server.com/octavus?sessionId=${sessionId}`);\n ws.onopen = () => resolve(ws);\n ws.onerror = () => reject(new Error('WebSocket connection failed'));\n }),\n }),\n [sessionId],\n);\n```\n\nWhen `sessionId` changes, the hook automatically reinitializes with the new transport.\n\n## Custom Events\n\nHandle custom events alongside Octavus stream events:\n\n```typescript\nconst transport = createSocketTransport({\n connect: connectSocket,\n\n onMessage: (data) => {\n const msg = data as { type: string; [key: string]: unknown };\n\n switch (msg.type) {\n case 'typing-indicator':\n setAgentTyping(msg.isTyping as boolean);\n break;\n\n case 'presence-update':\n setOnlineUsers(msg.users as string[]);\n break;\n\n case 'notification':\n showToast(msg.message as string);\n break;\n\n // Octavus events (text-delta, finish, etc.) are handled automatically\n }\n },\n});\n```\n\n## Connection Management\n\n### Connection State API\n\nThe socket transport provides full connection lifecycle control:\n\n```typescript\nconst transport = createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const sock = new SockJS('/octavus');\n sock.onopen = () => resolve(sock);\n sock.onerror = () => reject(new Error('Connection failed'));\n }),\n});\n\n// Access connection state\nconsole.log(transport.connectionState); // 'disconnected' | 'connecting' | 'connected' | 'error'\n\n// Subscribe to state changes\nconst unsubscribe = transport.onConnectionStateChange((state, error) => {\n console.log('Connection state:', state);\n if (error) console.error('Error:', error);\n});\n\n// Eager connection\nawait transport.connect();\n\n// Manual disconnect\ntransport.disconnect();\n```\n\n### Using with useOctavusChat\n\nThe React hook exposes connection state automatically for socket transports:\n\n```typescript\nconst {\n messages,\n status,\n send,\n // Socket-specific (undefined for HTTP transport)\n connectionState,\n connectionError,\n connect,\n disconnect,\n} = useOctavusChat({ transport });\n\n// Eagerly connect on mount\nuseEffect(() => {\n connect?.();\n return () => disconnect?.();\n}, [connect, disconnect]);\n```\n\n### Handling Disconnections\n\n```typescript\nconst transport = createSocketTransport({\n connect: connectSocket,\n\n onClose: () => {\n console.log('Socket disconnected');\n // Connection state is automatically updated to 'disconnected'\n },\n});\n```\n\n### Reconnection with Exponential Backoff\n\n```typescript\nimport { useRef, useCallback, useMemo } from 'react';\nimport { createSocketTransport, type SocketLike } from '@octavus/react';\n\nfunction useReconnectingTransport() {\n const reconnectAttempts = useRef(0);\n const maxAttempts = 5;\n\n const connect = useCallback((): Promise<SocketLike> => {\n return new Promise((resolve, reject) => {\n const sock = new SockJS('/octavus');\n\n sock.onopen = () => {\n reconnectAttempts.current = 0;\n resolve(sock);\n };\n\n sock.onerror = () => {\n if (reconnectAttempts.current < maxAttempts) {\n reconnectAttempts.current++;\n const delay = Math.min(1000 * 2 ** reconnectAttempts.current, 30000);\n console.log(`Reconnecting in ${delay}ms...`);\n setTimeout(() => connect().then(resolve).catch(reject), delay);\n } else {\n reject(new Error('Max reconnection attempts reached'));\n }\n };\n });\n }, []);\n\n return useMemo(() => createSocketTransport({ connect }), [connect]);\n}\n```\n\n## Framework Notes\n\n### Meteor\n\nMeteor's bundler may have issues with ES6 imports of `sockjs-client`. Use `require()` instead:\n\n```typescript\n// ❌ May fail in Meteor\nimport SockJS from 'sockjs-client';\n\n// ✅ Works in Meteor\nconst SockJS: typeof import('sockjs-client') = require('sockjs-client');\n```\n\n### SockJS vs WebSocket\n\n| Feature | WebSocket | SockJS |\n| ------------------- | -------------------- | -------------------------------- |\n| Browser support | Modern browsers | All browsers (with fallbacks) |\n| Session ID | Via URL query params | Via init message |\n| Proxy compatibility | Varies | Excellent (polling fallback) |\n| Setup complexity | Lower | Higher (requires server library) |\n\n## Protocol Reference\n\n### Client → Server Messages\n\n```typescript\n// Initialize session (only for client-provided sessionId pattern)\n{ type: 'init', sessionId: string }\n\n// Trigger an action\n{ type: 'trigger', triggerName: string, input?: Record<string, unknown> }\n\n// Stop current stream\n{ type: 'stop' }\n```\n\n### Server → Client Messages\n\nThe server sends Octavus `StreamEvent` objects as JSON. See [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list.\n\n```typescript\n// Examples\n{ type: 'start', messageId: '...' }\n{ type: 'text-delta', id: '...', delta: 'Hello' }\n{ type: 'tool-input-start', toolCallId: '...', toolName: 'get-user' }\n{ type: 'finish', finishReason: 'stop' }\n{ type: 'error', errorType: 'internal_error', message: 'Something went wrong', source: 'platform', retryable: false }\n```\n\n## Full Example\n\nFor a complete walkthrough of building a chat interface with SockJS, see the [Socket Chat Example](/docs/examples/socket-chat).\n",
98
107
  "excerpt": "Socket Transport The socket transport enables real-time bidirectional communication using WebSocket or SockJS. Use this when you need persistent connections, custom server events, or when HTTP/SSE...",
99
108
  "order": 5
100
109
  },
@@ -103,16 +112,43 @@
103
112
  "section": "client-sdk",
104
113
  "title": "HTTP Transport",
105
114
  "description": "Using HTTP/SSE for streaming with Octavus in Next.js, Express, and other frameworks.",
106
- "content": "\n# HTTP Transport\n\nThe HTTP transport uses standard HTTP requests with Server-Sent Events (SSE) for streaming. This is the simplest and most compatible transport option.\n\n## When to Use HTTP Transport\n\n| Use Case | Recommendation |\n|----------|----------------|\n| Next.js, Remix, or similar frameworks | ✅ Use HTTP |\n| Standard web apps without special requirements | ✅ Use HTTP |\n| Serverless deployments (Vercel, etc.) | ✅ Use HTTP |\n| Need custom real-time events | Consider [Socket Transport](/docs/client-sdk/socket-transport) |\n\n## Basic Setup\n\n### Client\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, error, send, stop } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send(\n 'user-message',\n { USER_MESSAGE: text },\n { userMessage: { content: text } },\n );\n };\n\n // ... render chat\n}\n```\n\n### Server (Next.js API Route)\n\n```typescript\n// app/api/trigger/route.ts\nimport { OctavusClient, toSSEStream } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nexport async function POST(request: Request) {\n const { sessionId, triggerName, input } = await request.json();\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n return { name: 'Demo User', plan: 'pro' };\n },\n },\n });\n\n // trigger() returns parsed events, toSSEStream() converts to SSE format\n const events = session.trigger(triggerName, input);\n\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n```\n\n## Session Creation\n\nSessions should be created server-side before rendering the chat. There are two patterns:\n\n### Pattern 1: Create Session on Page Load\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/Chat';\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n fetch('/api/sessions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentId: 'your-agent-id',\n input: { COMPANY_NAME: 'Acme Corp' },\n }),\n })\n .then(res => res.json())\n .then(data => setSessionId(data.sessionId));\n }, []);\n\n if (!sessionId) {\n return <LoadingSpinner />;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n### Pattern 2: Server-Side Session Creation (App Router)\n\n```tsx\n// app/chat/page.tsx\nimport { octavus } from '@/lib/octavus';\nimport { Chat } from '@/components/Chat';\n\nexport default async function ChatPage() {\n // Create session server-side\n const sessionId = await octavus.agentSessions.create('your-agent-id', {\n COMPANY_NAME: 'Acme Corp',\n });\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\nThis pattern is cleaner as the session is ready before the component renders.\n\n## Error Handling\n\nHandle errors in both the transport and the hook:\n\n```tsx\nconst { messages, status, error, send } = useOctavusChat({\n transport,\n onError: (err) => {\n console.error('Stream error:', err);\n // Show toast, update UI, etc.\n },\n});\n\n// Also check error state\nif (error) {\n return <ErrorMessage error={error} />;\n}\n```\n\n## Stop Streaming\n\nAllow users to cancel ongoing streams:\n\n```tsx\nconst { send, stop, status } = useOctavusChat({ transport });\n\nreturn (\n <button\n onClick={status === 'streaming' ? stop : () => sendMessage()}\n disabled={status === 'streaming' && !inputValue}\n >\n {status === 'streaming' ? 'Stop' : 'Send'}\n </button>\n);\n```\n\n## Express Server\n\nFor non-Next.js backends:\n\n```typescript\nimport express from 'express';\nimport { OctavusClient, toSSEStream } from '@octavus/server-sdk';\n\nconst app = express();\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\napp.post('/api/trigger', async (req, res) => {\n const { sessionId, triggerName, input } = req.body;\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n // Your tool handlers\n },\n });\n\n const events = session.trigger(triggerName, input);\n const stream = toSSEStream(events);\n\n // Set SSE headers\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n\n // Pipe the stream to the response\n const reader = stream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n res.write(value);\n }\n } finally {\n reader.releaseLock();\n res.end();\n }\n});\n```\n\n## Transport Options\n\n```typescript\ninterface HttpTransportOptions {\n // Function that makes the HTTP request\n triggerRequest: (\n triggerName: string,\n input?: Record<string, unknown>\n ) => Promise<Response>;\n}\n```\n\n## Protocol\n\n### Request Format\n\nThe `triggerRequest` function should send a POST request with:\n\n```json\n{\n \"sessionId\": \"sess_abc123\",\n \"triggerName\": \"user-message\",\n \"input\": {\n \"USER_MESSAGE\": \"Hello\"\n }\n}\n```\n\n### Response Format\n\nThe server responds with an SSE stream:\n\n```\ndata: {\"type\":\"start\",\"messageId\":\"msg_xyz\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"msg_xyz\",\"delta\":\"Hello\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"msg_xyz\",\"delta\":\" there!\"}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"stop\"}\n\ndata: [DONE]\n```\n\nSee [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list of event types.\n\n## Next Steps\n\n- [Quick Start](/docs/getting-started/quickstart) — Complete Next.js integration guide\n- [Messages](/docs/client-sdk/messages) — Working with message state\n- [Streaming](/docs/client-sdk/streaming) — Building streaming UIs\n\n",
115
+ "content": "\n# HTTP Transport\n\nThe HTTP transport uses standard HTTP requests with Server-Sent Events (SSE) for streaming. This is the simplest and most compatible transport option.\n\n## When to Use HTTP Transport\n\n| Use Case | Recommendation |\n| ---------------------------------------------- | -------------------------------------------------------------- |\n| Next.js, Remix, or similar frameworks | ✅ Use HTTP |\n| Standard web apps without special requirements | ✅ Use HTTP |\n| Serverless deployments (Vercel, etc.) | ✅ Use HTTP |\n| Need custom real-time events | Consider [Socket Transport](/docs/client-sdk/socket-transport) |\n\n## Basic Setup\n\n### Client\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, error, send, stop } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n };\n\n // ... render chat\n}\n```\n\n### Server (Next.js API Route)\n\n```typescript\n// app/api/trigger/route.ts\nimport { OctavusClient, toSSEStream } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nexport async function POST(request: Request) {\n const { sessionId, triggerName, input } = await request.json();\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n return { name: 'Demo User', plan: 'pro' };\n },\n },\n });\n\n // trigger() returns an async generator, toSSEStream() converts to SSE format\n const events = session.trigger(triggerName, input, { signal: request.signal });\n\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n```\n\n## Session Creation\n\nSessions should be created server-side before rendering the chat. There are two patterns:\n\n### Pattern 1: Create Session on Page Load\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/Chat';\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n fetch('/api/sessions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentId: 'your-agent-id',\n input: { COMPANY_NAME: 'Acme Corp' },\n }),\n })\n .then((res) => res.json())\n .then((data) => setSessionId(data.sessionId));\n }, []);\n\n if (!sessionId) {\n return <LoadingSpinner />;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n### Pattern 2: Server-Side Session Creation (App Router)\n\n```tsx\n// app/chat/page.tsx\nimport { octavus } from '@/lib/octavus';\nimport { Chat } from '@/components/Chat';\n\nexport default async function ChatPage() {\n // Create session server-side\n const sessionId = await octavus.agentSessions.create('your-agent-id', {\n COMPANY_NAME: 'Acme Corp',\n });\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\nThis pattern is cleaner as the session is ready before the component renders.\n\n## Error Handling\n\nHandle errors with structured error information:\n\n```tsx\nimport { isRateLimitError, isProviderError } from '@octavus/react';\n\nconst { messages, status, error, send } = useOctavusChat({\n transport,\n onError: (err) => {\n console.error('Stream error:', err.errorType, err.message);\n\n if (isRateLimitError(err)) {\n toast.error(`Rate limited. Try again in ${err.retryAfter}s`);\n } else if (isProviderError(err)) {\n toast.error('AI service temporarily unavailable');\n } else {\n toast.error('Something went wrong');\n }\n },\n});\n\n// Also check error state\nif (error) {\n return <ErrorMessage error={error} />;\n}\n```\n\nSee [Error Handling](/docs/client-sdk/error-handling) for comprehensive error handling patterns.\n\n## Stop Streaming\n\nAllow users to cancel ongoing streams. When `stop()` is called:\n\n1. The HTTP request is aborted via the signal\n2. Any partial content is preserved in the message\n3. Tool calls in progress are marked as `cancelled`\n4. Status changes to `idle`\n\n```tsx\nconst { send, stop, status } = useOctavusChat({\n transport,\n onStop: () => {\n console.log('User stopped generation');\n },\n});\n\nreturn (\n <button\n onClick={status === 'streaming' ? stop : () => sendMessage()}\n disabled={status === 'streaming' && !inputValue}\n >\n {status === 'streaming' ? 'Stop' : 'Send'}\n </button>\n);\n```\n\n> **Important**: For stop to work end-to-end, pass the `options.signal` to your `fetch()` call and forward `request.signal` to `session.trigger()` on the server.\n\n## Express Server\n\nFor non-Next.js backends:\n\n```typescript\nimport express from 'express';\nimport { OctavusClient, toSSEStream } from '@octavus/server-sdk';\n\nconst app = express();\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\napp.post('/api/trigger', async (req, res) => {\n const { sessionId, triggerName, input } = req.body;\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n // Your tool handlers\n },\n });\n\n const events = session.trigger(triggerName, input);\n const stream = toSSEStream(events);\n\n // Set SSE headers\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n\n // Pipe the stream to the response\n const reader = stream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n res.write(value);\n }\n } finally {\n reader.releaseLock();\n res.end();\n }\n});\n```\n\n## Transport Options\n\n```typescript\ninterface HttpTransportOptions {\n triggerRequest: (\n triggerName: string,\n input?: Record<string, unknown>,\n options?: TriggerRequestOptions,\n ) => Promise<Response>;\n}\n\ninterface TriggerRequestOptions {\n signal?: AbortSignal;\n}\n```\n\n## Protocol\n\n### Request Format\n\nThe `triggerRequest` function should send a POST request with:\n\n```json\n{\n \"sessionId\": \"sess_abc123\",\n \"triggerName\": \"user-message\",\n \"input\": {\n \"USER_MESSAGE\": \"Hello\"\n }\n}\n```\n\n### Response Format\n\nThe server responds with an SSE stream:\n\n```\ndata: {\"type\":\"start\",\"messageId\":\"msg_xyz\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"msg_xyz\",\"delta\":\"Hello\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"msg_xyz\",\"delta\":\" there!\"}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"stop\"}\n\ndata: [DONE]\n```\n\nSee [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list of event types.\n\n## Next Steps\n\n- [Quick Start](/docs/getting-started/quickstart) — Complete Next.js integration guide\n- [Messages](/docs/client-sdk/messages) — Working with message state\n- [Streaming](/docs/client-sdk/streaming) — Building streaming UIs\n- [Error Handling](/docs/client-sdk/error-handling) — Handling errors with type guards\n",
107
116
  "excerpt": "HTTP Transport The HTTP transport uses standard HTTP requests with Server-Sent Events (SSE) for streaming. This is the simplest and most compatible transport option. When to Use HTTP Transport | Use...",
108
117
  "order": 6
109
118
  },
119
+ {
120
+ "slug": "client-sdk/structured-output",
121
+ "section": "client-sdk",
122
+ "title": "Structured Output",
123
+ "description": "Rendering structured object responses with custom UI components.",
124
+ "content": "\n# Structured Output\n\nWhen an agent uses `responseType` on a `next-message` block, the client receives a `UIObjectPart` instead of a `UITextPart`. This enables rich, custom UI for typed responses.\n\n## How It Works\n\n1. The protocol defines a type and uses it as `responseType`:\n\n```yaml\ntypes:\n ChatResponse:\n content:\n type: string\n description: The main response text\n suggestions:\n type: array\n items:\n type: string\n description: Follow-up suggestions\n\nhandlers:\n user-message:\n Respond:\n block: next-message\n responseType: ChatResponse\n```\n\n2. The agent generates a JSON response matching the schema\n3. The client SDK receives a `UIObjectPart` with progressive JSON parsing\n4. Your app renders custom UI based on the `typeName`\n\n## The UIObjectPart\n\n```typescript\ninterface UIObjectPart {\n type: 'object';\n id: string;\n typeName: string; // Type name from protocol (e.g., \"ChatResponse\")\n partial?: unknown; // Partial object while streaming\n object?: unknown; // Final validated object when done\n status: 'streaming' | 'done' | 'error';\n error?: string; // Error message if parsing failed\n thread?: string;\n}\n```\n\nDuring streaming, `partial` contains the progressively parsed object. When streaming completes, `object` contains the final validated result.\n\n## Building a Renderer\n\nCreate a renderer component for each type you want to customize:\n\n```tsx\nimport type { UIObjectPart } from '@octavus/react';\n\ninterface ChatResponse {\n content?: string;\n suggestions?: string[];\n}\n\nfunction ChatResponseRenderer({ part }: { part: UIObjectPart }) {\n // Use final object if available, otherwise partial\n const data = (part.object ?? part.partial) as ChatResponse | undefined;\n const isStreaming = part.status === 'streaming';\n\n if (!data) {\n return <LoadingIndicator />;\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Main content */}\n {data.content && (\n <p>\n {data.content}\n {isStreaming && <span className=\"animate-pulse\">▌</span>}\n </p>\n )}\n\n {/* Suggestions */}\n {data.suggestions && data.suggestions.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {data.suggestions.map((suggestion, i) => (\n <button key={i} className=\"px-3 py-1 bg-blue-100 rounded-full text-sm\">\n {suggestion}\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n```\n\n## Renderer Registry Pattern\n\nFor apps with multiple response types, use a registry to map type names to renderers:\n\n```tsx\nimport type { ComponentType } from 'react';\nimport type { UIObjectPart } from '@octavus/react';\n\n// Define props interface\ninterface ObjectRendererProps {\n part: UIObjectPart;\n onSuggestionClick?: (suggestion: string) => void;\n}\n\n// Registry type\ntype ObjectRendererRegistry = Record<string, ComponentType<ObjectRendererProps>>;\n\n// Create registry for each agent\nconst productAdvisorRenderers: ObjectRendererRegistry = {\n ChatResponse: ChatResponseRenderer,\n ProductList: ProductListRenderer,\n};\n\n// Map agents to their renderers\nconst AGENT_RENDERERS: Record<string, ObjectRendererRegistry> = {\n 'product-advisor': productAdvisorRenderers,\n 'support-chat': supportChatRenderers,\n};\n\n// Get renderers for an agent\nfunction getRenderers(agentSlug: string): ObjectRendererRegistry {\n return AGENT_RENDERERS[agentSlug] ?? {};\n}\n```\n\n## Using in Part Renderer\n\nIntegrate with your part renderer:\n\n```tsx\nfunction PartRenderer({ part, agentSlug }: { part: UIMessagePart; agentSlug: string }) {\n if (part.type === 'object') {\n const renderers = getRenderers(agentSlug);\n const Renderer = renderers[part.typeName];\n\n if (Renderer) {\n return <Renderer part={part} />;\n }\n\n // Fallback: render as formatted JSON\n return (\n <pre className=\"text-sm bg-gray-100 p-3 rounded overflow-auto\">\n {JSON.stringify(part.object ?? part.partial, null, 2)}\n </pre>\n );\n }\n\n // Handle other part types...\n}\n```\n\n## Handling Streaming State\n\nDuring streaming, the object is progressively parsed. Handle incomplete data gracefully:\n\n```tsx\nfunction ProductListRenderer({ part }: { part: UIObjectPart }) {\n const data = (part.object ?? part.partial) as ProductList | undefined;\n const isStreaming = part.status === 'streaming';\n\n return (\n <div className=\"grid grid-cols-2 gap-4\">\n {data?.products?.map((product, i) => (\n <ProductCard\n key={product.id ?? i}\n product={product}\n // Show loading state for incomplete products\n isLoading={isStreaming && !product.name}\n />\n ))}\n\n {isStreaming && <div className=\"animate-pulse bg-gray-200 rounded h-32\" />}\n </div>\n );\n}\n```\n\n## Error Handling\n\nIf JSON parsing fails, `status` will be `'error'` with details in `error`:\n\n```tsx\nfunction ObjectPartRenderer({ part }: { part: UIObjectPart }) {\n if (part.status === 'error') {\n return (\n <div className=\"text-red-500 p-3 bg-red-50 rounded\">\n <p className=\"font-medium\">Failed to parse response</p>\n <p className=\"text-sm\">{part.error}</p>\n </div>\n );\n }\n\n // Normal rendering...\n}\n```\n\n## Complete Example\n\nHere's a complete chat interface with structured output support:\n\n```tsx\nimport { useMemo } from 'react';\nimport {\n useOctavusChat,\n createHttpTransport,\n type UIMessage,\n type UIMessagePart,\n type UIObjectPart,\n} from '@octavus/react';\n\n// Renderers for ChatResponse type\nfunction ChatResponseRenderer({ part }: { part: UIObjectPart }) {\n const data = part.object ?? part.partial;\n const isStreaming = part.status === 'streaming';\n\n if (!data) {\n return <div className=\"animate-pulse h-20 bg-gray-200 rounded\" />;\n }\n\n const { content, suggestions } = data as {\n content?: string;\n suggestions?: string[];\n };\n\n return (\n <div className=\"space-y-3\">\n {content && (\n <p className=\"text-gray-800\">\n {content}\n {isStreaming && <span className=\"animate-pulse ml-1\">▌</span>}\n </p>\n )}\n\n {suggestions && suggestions.length > 0 && (\n <div className=\"flex flex-wrap gap-2 pt-2 border-t\">\n {suggestions.map((s, i) => (\n <span key={i} className=\"px-2 py-1 bg-blue-50 text-blue-700 rounded text-sm\">\n {s}\n </span>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n// Part renderer with object support\nfunction PartRenderer({ part }: { part: UIMessagePart }) {\n switch (part.type) {\n case 'text':\n return <p>{part.text}</p>;\n\n case 'object':\n if (part.typeName === 'ChatResponse') {\n return <ChatResponseRenderer part={part} />;\n }\n return <pre>{JSON.stringify(part.object ?? part.partial, null, 2)}</pre>;\n\n default:\n return null;\n }\n}\n\n// Message component\nfunction Message({ message }: { message: UIMessage }) {\n return (\n <div className={message.role === 'user' ? 'text-right' : 'text-left'}>\n <div className=\"inline-block p-3 rounded-lg max-w-[80%]\">\n {message.parts.map((part, i) => (\n <PartRenderer key={i} part={part} />\n ))}\n </div>\n </div>\n );\n}\n\n// Chat component\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex-1 overflow-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <Message key={msg.id} message={msg} />\n ))}\n </div>\n\n {/* Input form... */}\n </div>\n );\n}\n```\n\n## Best Practices\n\n**Design types for progressive rendering:**\n\nStructure your types so the most important fields stream first. Property order in YAML is preserved during streaming.\n\n```yaml\ntypes:\n ChatResponse:\n content: # Streams first - show immediately\n type: string\n suggestions: # Streams after content\n type: array\n items:\n type: string\n```\n\n**Keep renderers resilient:**\n\nHandle missing fields gracefully since partial objects may have undefined properties:\n\n```tsx\n// Good - handles missing data\nconst name = product?.name ?? 'Loading...';\n\n// Avoid - might crash on partial data\nconst name = product.name; // Error if product is undefined\n```\n\n**Use TypeScript for type safety:**\n\nDefine TypeScript interfaces matching your protocol types:\n\n```typescript\n// Match your protocol types\ninterface ChatResponse {\n content?: string;\n suggestions?: string[];\n recommendedProducts?: ProductSummary[];\n}\n\ninterface ProductSummary {\n id?: string;\n name?: string;\n price?: number;\n}\n```\n\n**Test with slow connections:**\n\nStreaming is more noticeable on slow connections. Test your UI with network throttling to ensure a good experience.\n\n## Type Requirements\n\nThe `responseType` in your protocol must be an **object type** (regular custom type with properties).\n\nThe following cannot be used directly as `responseType`:\n\n- **Discriminated unions** — LLM providers don't allow `anyOf` at the schema root\n- **Array types** — Must be wrapped in an object\n- **Primitives** — `string`, `number`, etc. are not valid\n\nIf you need variant responses, wrap the discriminated union in an object:\n\n```yaml\ntypes:\n # ❌ Cannot use union directly as responseType\n ChatResponseUnion:\n anyOf: [ContentResponse, ProductResponse]\n discriminator: responseType\n\n # ✅ Wrap the union in an object\n ChatResponseWrapper:\n response:\n type: ChatResponseUnion\n```\n\nIf you need the LLM to return an array, wrap it in an object:\n\n```yaml\ntypes:\n # ❌ Cannot use array type as responseType\n ProductList:\n type: array\n items:\n type: Product\n\n # ✅ Wrap the array in an object\n ProductListResponse:\n products:\n type: array\n items:\n type: Product\n```\n\nSee [Types - Structured Output](/docs/protocol/types#structured-output) for more details on defining response types.\n",
125
+ "excerpt": "Structured Output When an agent uses on a block, the client receives a instead of a . This enables rich, custom UI for typed responses. How It Works 1. The protocol defines a type and uses it as...",
126
+ "order": 7
127
+ },
128
+ {
129
+ "slug": "client-sdk/file-uploads",
130
+ "section": "client-sdk",
131
+ "title": "File Uploads",
132
+ "description": "Uploading images and files for vision models and document processing.",
133
+ "content": "\n# File Uploads\n\nThe Client SDK supports uploading images and documents that can be sent with messages. This enables vision model capabilities (analyzing images) and document processing.\n\n## Overview\n\nFile uploads follow a two-step flow:\n\n1. **Request upload URLs** from the platform via your backend\n2. **Upload files directly to S3** using presigned URLs\n3. **Send file references** with your message\n\nThis architecture keeps your API key secure on the server while enabling fast, direct uploads.\n\n## Setup\n\n### Backend: Upload URLs Endpoint\n\nCreate an endpoint that proxies upload URL requests to the Octavus platform:\n\n```typescript\n// app/api/upload-urls/route.ts (Next.js)\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { sessionId, files } = await request.json();\n\n // Get presigned URLs from Octavus\n const result = await octavus.files.getUploadUrls(sessionId, files);\n\n return NextResponse.json(result);\n}\n```\n\n### Client: Configure File Uploads\n\nPass `requestUploadUrls` to the chat hook:\n\n```tsx\nimport { useMemo, useCallback } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n // Request upload URLs from your backend\n const requestUploadUrls = useCallback(\n async (files: { filename: string; mediaType: string; size: number }[]) => {\n const response = await fetch('/api/upload-urls', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, files }),\n });\n return response.json();\n },\n [sessionId],\n );\n\n const { messages, status, send, uploadFiles } = useOctavusChat({\n transport,\n requestUploadUrls,\n });\n\n // ...\n}\n```\n\n## Uploading Files\n\n### Method 1: Upload Before Sending\n\nFor the best UX (showing upload progress), upload files first, then send:\n\n```tsx\nimport { useState, useRef } from 'react';\nimport type { FileReference } from '@octavus/react';\n\nfunction ChatInput({ sessionId }: { sessionId: string }) {\n const [pendingFiles, setPendingFiles] = useState<FileReference[]>([]);\n const [uploading, setUploading] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const { send, uploadFiles } = useOctavusChat({\n transport,\n requestUploadUrls,\n });\n\n async function handleFileSelect(event: React.ChangeEvent<HTMLInputElement>) {\n const files = event.target.files;\n if (!files?.length) return;\n\n setUploading(true);\n try {\n // Upload files with progress tracking\n const fileRefs = await uploadFiles(files, (fileIndex, progress) => {\n console.log(`File ${fileIndex}: ${progress}%`);\n });\n setPendingFiles((prev) => [...prev, ...fileRefs]);\n } finally {\n setUploading(false);\n }\n }\n\n async function handleSend(message: string) {\n await send(\n 'user-message',\n {\n USER_MESSAGE: message,\n FILES: pendingFiles.length > 0 ? pendingFiles : undefined,\n },\n {\n userMessage: {\n content: message,\n files: pendingFiles.length > 0 ? pendingFiles : undefined,\n },\n },\n );\n setPendingFiles([]);\n }\n\n return (\n <div>\n {/* File preview */}\n {pendingFiles.map((file) => (\n <img key={file.id} src={file.url} alt={file.filename} className=\"h-16\" />\n ))}\n\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*,.pdf\"\n multiple\n onChange={handleFileSelect}\n className=\"hidden\"\n />\n\n <button onClick={() => fileInputRef.current?.click()} disabled={uploading}>\n {uploading ? 'Uploading...' : 'Attach'}\n </button>\n </div>\n );\n}\n```\n\n### Method 2: Upload on Send (Automatic)\n\nFor simpler implementations, pass `File` objects directly:\n\n```tsx\nasync function handleSend(message: string, files?: File[]) {\n await send(\n 'user-message',\n { USER_MESSAGE: message, FILES: files },\n { userMessage: { content: message, files } },\n );\n}\n```\n\nThe SDK automatically uploads the files before sending. Note: This doesn't provide upload progress.\n\n## FileReference Type\n\nFile references contain metadata and URLs:\n\n```typescript\ninterface FileReference {\n /** Unique file ID (platform-generated) */\n id: string;\n /** IANA media type (e.g., 'image/png', 'application/pdf') */\n mediaType: string;\n /** Presigned download URL (S3) */\n url: string;\n /** Original filename */\n filename?: string;\n /** File size in bytes */\n size?: number;\n}\n```\n\n## Protocol Integration\n\nTo accept files in your agent protocol, use the `file[]` type:\n\n```yaml\ntriggers:\n user-message:\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n FILES:\n type: file[]\n optional: true\n description: User-attached images for vision analysis\n\nhandlers:\n user-message:\n Add user message:\n block: add-message\n role: user\n prompt: user-message\n input:\n - USER_MESSAGE\n files:\n - FILES # Attach files to the message\n display: hidden\n\n Respond to user:\n block: next-message\n```\n\nThe `file` type is a built-in type representing uploaded files. Use `file[]` for arrays of files.\n\n## Supported File Types\n\n| Type | Media Types |\n| --------- | -------------------------------------------------------------------- |\n| Images | `image/jpeg`, `image/png`, `image/gif`, `image/webp` |\n| Documents | `application/pdf`, `text/plain`, `text/markdown`, `application/json` |\n\n## File Limits\n\n| Limit | Value |\n| --------------------- | ---------- |\n| Max file size | 10 MB |\n| Max total per request | 50 MB |\n| Max files per request | 20 |\n| Upload URL expiry | 15 minutes |\n| Download URL expiry | 24 hours |\n\n## Rendering User Files\n\nUser-uploaded files appear as `UIFilePart` in user messages:\n\n```tsx\nfunction UserMessage({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => {\n if (part.type === 'file') {\n if (part.mediaType.startsWith('image/')) {\n return (\n <img\n key={i}\n src={part.url}\n alt={part.filename || 'Uploaded image'}\n className=\"max-h-48 rounded-lg\"\n />\n );\n }\n return (\n <a key={i} href={part.url} className=\"text-blue-500\">\n 📄 {part.filename}\n </a>\n );\n }\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n </div>\n );\n}\n```\n\n## Server SDK: Files API\n\nThe Server SDK provides direct access to the Files API:\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: 'https://octavus.ai',\n apiKey: 'your-api-key',\n});\n\n// Get presigned upload URLs\nconst { files } = await client.files.getUploadUrls(sessionId, [\n { filename: 'photo.jpg', mediaType: 'image/jpeg', size: 102400 },\n { filename: 'doc.pdf', mediaType: 'application/pdf', size: 204800 },\n]);\n\n// files[0].id - Use in FileReference\n// files[0].uploadUrl - PUT to this URL to upload\n// files[0].downloadUrl - Use as FileReference.url\n```\n\n## Complete Example\n\nHere's a full chat input component with file upload:\n\n```tsx\n'use client';\n\nimport { useState, useRef, useMemo, useCallback } from 'react';\nimport { useOctavusChat, createHttpTransport, type FileReference } from '@octavus/react';\n\ninterface PendingFile {\n file: File;\n id: string;\n status: 'uploading' | 'done' | 'error';\n progress: number;\n fileRef?: FileReference;\n error?: string;\n}\n\nexport function Chat({ sessionId }: { sessionId: string }) {\n const [input, setInput] = useState('');\n const [pendingFiles, setPendingFiles] = useState<PendingFile[]>([]);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const fileIdCounter = useRef(0);\n\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const requestUploadUrls = useCallback(\n async (files: { filename: string; mediaType: string; size: number }[]) => {\n const res = await fetch('/api/upload-urls', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, files }),\n });\n return res.json();\n },\n [sessionId],\n );\n\n const { messages, status, send, uploadFiles } = useOctavusChat({\n transport,\n requestUploadUrls,\n });\n\n const isUploading = pendingFiles.some((f) => f.status === 'uploading');\n const hasErrors = pendingFiles.some((f) => f.status === 'error');\n const allReady = pendingFiles.every((f) => f.status === 'done');\n\n async function handleFileSelect(e: React.ChangeEvent<HTMLInputElement>) {\n const files = Array.from(e.target.files ?? []);\n if (!files.length) return;\n e.target.value = '';\n\n const newPending: PendingFile[] = files.map((file) => ({\n file,\n id: `pending-${++fileIdCounter.current}`,\n status: 'uploading',\n progress: 0,\n }));\n\n setPendingFiles((prev) => [...prev, ...newPending]);\n\n for (const pending of newPending) {\n try {\n const [fileRef] = await uploadFiles([pending.file], (_, progress) => {\n setPendingFiles((prev) =>\n prev.map((f) => (f.id === pending.id ? { ...f, progress } : f)),\n );\n });\n setPendingFiles((prev) =>\n prev.map((f) => (f.id === pending.id ? { ...f, status: 'done', fileRef } : f)),\n );\n } catch (err) {\n setPendingFiles((prev) =>\n prev.map((f) =>\n f.id === pending.id ? { ...f, status: 'error', error: String(err) } : f,\n ),\n );\n }\n }\n }\n\n async function handleSubmit() {\n if ((!input.trim() && !pendingFiles.length) || !allReady) return;\n\n const fileRefs = pendingFiles.filter((f) => f.fileRef).map((f) => f.fileRef!);\n\n await send(\n 'user-message',\n {\n USER_MESSAGE: input,\n FILES: fileRefs.length > 0 ? fileRefs : undefined,\n },\n {\n userMessage: {\n content: input,\n files: fileRefs.length > 0 ? fileRefs : undefined,\n },\n },\n );\n\n setInput('');\n setPendingFiles([]);\n }\n\n return (\n <div>\n {/* Messages */}\n {messages.map((msg) => (\n <div key={msg.id}>{/* ... render message */}</div>\n ))}\n\n {/* Pending files */}\n {pendingFiles.length > 0 && (\n <div className=\"flex gap-2\">\n {pendingFiles.map((f) => (\n <div key={f.id} className=\"relative\">\n <img\n src={URL.createObjectURL(f.file)}\n alt={f.file.name}\n className=\"h-16 w-16 object-cover rounded\"\n />\n {f.status === 'uploading' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/50\">\n <span className=\"text-white text-xs\">{f.progress}%</span>\n </div>\n )}\n <button\n onClick={() => setPendingFiles((prev) => prev.filter((p) => p.id !== f.id))}\n className=\"absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-5 h-5\"\n >\n ×\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* Input */}\n <div className=\"flex gap-2\">\n <input type=\"file\" ref={fileInputRef} onChange={handleFileSelect} hidden />\n <button onClick={() => fileInputRef.current?.click()}>📎</button>\n <input\n value={input}\n onChange={(e) => setInput(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1\"\n />\n <button onClick={handleSubmit} disabled={isUploading || hasErrors}>\n {isUploading ? 'Uploading...' : 'Send'}\n </button>\n </div>\n </div>\n );\n}\n```\n",
134
+ "excerpt": "File Uploads The Client SDK supports uploading images and documents that can be sent with messages. This enables vision model capabilities (analyzing images) and document processing. Overview File...",
135
+ "order": 8
136
+ },
137
+ {
138
+ "slug": "client-sdk/error-handling",
139
+ "section": "client-sdk",
140
+ "title": "Error Handling",
141
+ "description": "Handling errors in streaming responses with structured error types.",
142
+ "content": "\n# Error Handling\n\nOctavus provides structured error handling across all transports. Errors are categorized by type and source, enabling you to build appropriate UI responses and monitoring.\n\n## Error Types\n\nThe `onError` callback receives an `OctavusError` with structured information:\n\n```typescript\nimport { useOctavusChat, type OctavusError } from '@octavus/react';\n\nconst { error, status } = useOctavusChat({\n transport,\n onError: (err: OctavusError) => {\n console.error('Chat error:', {\n type: err.errorType, // Error classification\n message: err.message, // Human-readable message\n source: err.source, // Where the error originated\n retryable: err.retryable, // Can be retried\n retryAfter: err.retryAfter, // Seconds to wait (rate limits)\n code: err.code, // Machine-readable code\n provider: err.provider, // Provider details (if applicable)\n });\n },\n});\n```\n\n## Error Classification\n\n### Error Types\n\n| Type | Description | Typical Response |\n| ---------------------- | --------------------- | ------------------- |\n| `rate_limit_error` | Too many requests | Show retry timer |\n| `quota_exceeded_error` | Usage quota exceeded | Show upgrade prompt |\n| `authentication_error` | Invalid API key | Check configuration |\n| `permission_error` | No access to resource | Check permissions |\n| `validation_error` | Invalid request | Fix request data |\n| `provider_error` | LLM provider issue | Retry or show error |\n| `provider_overloaded` | Provider at capacity | Retry with backoff |\n| `provider_timeout` | Provider timed out | Retry |\n| `tool_error` | Tool execution failed | Show tool error |\n| `internal_error` | Platform error | Show generic error |\n\n### Error Sources\n\n| Source | Description |\n| ---------- | -------------------------------------------- |\n| `platform` | Octavus platform error |\n| `provider` | LLM provider error (OpenAI, Anthropic, etc.) |\n| `tool` | Tool execution error |\n| `client` | Client-side error (network, parsing) |\n\n## Type Guards\n\nUse type guards to handle specific error types:\n\n```typescript\nimport {\n useOctavusChat,\n isRateLimitError,\n isAuthenticationError,\n isProviderError,\n isToolError,\n isRetryableError,\n} from '@octavus/react';\n\nconst { error } = useOctavusChat({\n transport,\n onError: (err) => {\n if (isRateLimitError(err)) {\n // Show countdown timer\n showRetryTimer(err.retryAfter ?? 60);\n return;\n }\n\n if (isAuthenticationError(err)) {\n // Configuration issue - shouldn't happen in production\n reportConfigError(err);\n return;\n }\n\n if (isProviderError(err)) {\n // LLM service issue\n showProviderError(err.provider?.name ?? 'AI service');\n return;\n }\n\n if (isToolError(err)) {\n // Tool failed - already shown inline\n return;\n }\n\n if (isRetryableError(err)) {\n // Generic retryable error\n showRetryButton();\n return;\n }\n\n // Non-retryable error\n showGenericError(err.message);\n },\n});\n```\n\n## Provider Error Details\n\nWhen errors come from LLM providers, additional details are available:\n\n```typescript\nif (isProviderError(error) && error.provider) {\n console.log({\n name: error.provider.name, // 'anthropic', 'openai', 'google'\n model: error.provider.model, // Model that caused the error\n statusCode: error.provider.statusCode, // HTTP status code\n errorType: error.provider.errorType, // Provider's error type\n requestId: error.provider.requestId, // For support tickets\n });\n}\n```\n\n## Building Error UI\n\n```tsx\nimport {\n useOctavusChat,\n isRateLimitError,\n isAuthenticationError,\n isProviderError,\n} from '@octavus/react';\n\nfunction Chat() {\n const { error, status } = useOctavusChat({ transport });\n\n return (\n <div>\n {/* Error display */}\n {error && (\n <div className=\"bg-red-50 border border-red-200 rounded-lg p-4\">\n <div className=\"font-medium text-red-800\">{getErrorTitle(error)}</div>\n <p className=\"text-red-600 text-sm mt-1\">{error.message}</p>\n {isRateLimitError(error) && error.retryAfter && (\n <p className=\"text-red-500 text-sm mt-2\">\n Please try again in {error.retryAfter} seconds\n </p>\n )}\n {error.retryable && <button className=\"mt-3 text-red-700 underline\">Try again</button>}\n </div>\n )}\n </div>\n );\n}\n\nfunction getErrorTitle(error: OctavusError): string {\n if (isRateLimitError(error)) return 'Service is busy';\n if (isAuthenticationError(error)) return 'Configuration error';\n if (isProviderError(error)) return 'AI service unavailable';\n return 'Something went wrong';\n}\n```\n\n## Monitoring & Logging\n\nLog errors for monitoring and debugging:\n\n```typescript\nuseOctavusChat({\n transport,\n onError: (err) => {\n // Send to your monitoring service\n analytics.track('octavus_error', {\n errorType: err.errorType,\n source: err.source,\n retryable: err.retryable,\n code: err.code,\n provider: err.provider?.name,\n });\n\n // Log for debugging\n console.error('[Octavus]', {\n type: err.errorType,\n message: err.message,\n source: err.source,\n provider: err.provider,\n });\n },\n});\n```\n\n## Error State\n\nThe hook exposes error state directly:\n\n```typescript\nconst { error, status } = useOctavusChat({ transport });\n\n// status is 'error' when an error occurred\n// error contains the OctavusError object\n\n// Clear error by sending a new message\nawait send('user-message', { USER_MESSAGE: 'Try again' });\n```\n\n## Rate Limit Handling\n\nRate limits include retry information:\n\n```typescript\nif (isRateLimitError(error)) {\n const waitTime = error.retryAfter ?? 60; // Default to 60 seconds\n\n // Show countdown\n setCountdown(waitTime);\n const timer = setInterval(() => {\n setCountdown((c) => {\n if (c <= 1) {\n clearInterval(timer);\n return 0;\n }\n return c - 1;\n });\n }, 1000);\n}\n```\n\n## Error Event Structure\n\nFor custom transports or direct event handling, errors follow this structure:\n\n```typescript\ninterface ErrorEvent {\n type: 'error';\n errorType: ErrorType;\n message: string;\n source: ErrorSource;\n retryable: boolean;\n retryAfter?: number;\n code?: string;\n provider?: {\n name: string;\n model?: string;\n statusCode?: number;\n errorType?: string;\n requestId?: string;\n };\n tool?: {\n name: string;\n callId?: string;\n };\n}\n```\n\n## Tool Errors\n\nTool errors are handled differently—they appear inline on the tool call:\n\n```tsx\nfunction ToolCallPart({ part }: { part: UIToolCallPart }) {\n return (\n <div>\n <span>{part.toolName}</span>\n\n {part.status === 'error' && <div className=\"text-red-500 text-sm mt-1\">{part.error}</div>}\n </div>\n );\n}\n```\n\nTool errors don't trigger `onError`—they're captured on the tool call part itself.\n",
143
+ "excerpt": "Error Handling Octavus provides structured error handling across all transports. Errors are categorized by type and source, enabling you to build appropriate UI responses and monitoring. Error Types...",
144
+ "order": 9
145
+ },
110
146
  {
111
147
  "slug": "protocol/overview",
112
148
  "section": "protocol",
113
149
  "title": "Overview",
114
150
  "description": "Introduction to Octavus agent protocols.",
115
- "content": "\n# Protocol Overview\n\nAgent protocols define how an AI agent behaves. They're written in YAML and specify inputs, triggers, tools, and execution handlers.\n\n## Why Protocols?\n\nProtocols provide:\n\n- **Declarative definition** — Define behavior, not implementation\n- **Portable agents** — Move agents between projects\n- **Versioning** — Track changes with git\n- **Validation** — Catch errors before runtime\n- **Visualization** — Debug execution flows\n\n## Protocol Structure\n\n```yaml\n# Agent inputs (provided when creating a session)\ninput:\n COMPANY_NAME: { type: string }\n USER_ID: { type: string, optional: true }\n\n# Persistent resources the agent can read/write\nresources:\n CONVERSATION_SUMMARY:\n description: Summary for handoff\n default: \"\"\n\n# How the agent can be invoked\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n request-human:\n description: User clicks \"Talk to Human\"\n\n# Temporary variables for execution (with types)\nvariables:\n SUMMARY:\n type: string\n TICKET:\n type: unknown\n\n# Tools the agent can use\ntools:\n get-user-account:\n description: Looking up your account\n parameters:\n userId: { type: string }\n\n# Agent configuration (model, tools, etc.)\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system # References prompts/system.md\n tools: [get-user-account]\n agentic: true # Allow multiple tool calls\n thinking: medium # Extended reasoning\n\n# What happens when triggers fire\nhandlers:\n user-message:\n Add user message:\n type: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n \n Respond to user:\n type: next-message\n```\n\n## File Structure\n\nEach agent is a folder with:\n\n```\nmy-agent/\n├── protocol.yaml # Main logic (required)\n├── settings.json # Agent metadata (required)\n└── prompts/ # Prompt templates\n ├── system.md\n ├── user-message.md\n └── escalation-summary.md\n```\n\n### settings.json\n\n```json\n{\n \"slug\": \"my-agent\",\n \"name\": \"My Agent\",\n \"description\": \"What this agent does\",\n \"format\": \"interactive\"\n}\n```\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `slug` | Yes | URL-safe identifier (lowercase, digits, dashes) |\n| `name` | Yes | Human-readable name |\n| `description` | No | Brief description |\n| `format` | Yes | `interactive` (chat) or `generation` (background) |\n\n## Naming Conventions\n\n- **Slugs**: `lowercase-with-dashes`\n- **Variables**: `UPPERCASE_SNAKE_CASE`\n- **Prompts**: `lowercase-with-dashes.md`\n- **Tools**: `lowercase-with-dashes`\n- **Triggers**: `lowercase-with-dashes`\n\n## Variables in Prompts\n\nReference variables with `{{VARIABLE_NAME}}`:\n\n```markdown\n<!-- prompts/system.md -->\nYou are a support agent for {{COMPANY_NAME}}.\n\nHelp users with their {{PRODUCT_NAME}} questions.\n\n## Support Policies\n\n{{SUPPORT_POLICIES}}\n```\n\nVariables are replaced with their values at runtime. If a variable is not provided, it's replaced with an empty string.\n\n## Next Steps\n\n- [Input & Resources](/docs/protocol/input-resources) — Defining agent inputs\n- [Triggers](/docs/protocol/triggers) — How agents are invoked\n- [Tools](/docs/protocol/tools) — External capabilities\n- [Handlers](/docs/protocol/handlers) — Execution blocks\n- [Agent Config](/docs/protocol/agent-config) — Model and settings\n\n",
151
+ "content": "\n# Protocol Overview\n\nAgent protocols define how an AI agent behaves. They're written in YAML and specify inputs, triggers, tools, and execution handlers.\n\n## Why Protocols?\n\nProtocols provide:\n\n- **Declarative definition** — Define behavior, not implementation\n- **Portable agents** — Move agents between projects\n- **Versioning** — Track changes with git\n- **Validation** — Catch errors before runtime\n- **Visualization** — Debug execution flows\n\n## Protocol Structure\n\n```yaml\n# Agent inputs (provided when creating a session)\ninput:\n COMPANY_NAME: { type: string }\n USER_ID: { type: string, optional: true }\n\n# Persistent resources the agent can read/write\nresources:\n CONVERSATION_SUMMARY:\n description: Summary for handoff\n default: ''\n\n# How the agent can be invoked\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n request-human:\n description: User clicks \"Talk to Human\"\n\n# Temporary variables for execution (with types)\nvariables:\n SUMMARY:\n type: string\n TICKET:\n type: unknown\n\n# Tools the agent can use\ntools:\n get-user-account:\n description: Looking up your account\n parameters:\n userId: { type: string }\n\n# Octavus skills (provider-agnostic code execution)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\n# Agent configuration (model, tools, etc.)\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system # References prompts/system.md\n tools: [get-user-account]\n skills: [qr-code] # Enable skills\n imageModel: google/gemini-2.5-flash-image # Enable image generation\n agentic: true # Allow multiple tool calls\n thinking: medium # Extended reasoning\n\n# What happens when triggers fire\nhandlers:\n user-message:\n Add user message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Respond to user:\n block: next-message\n```\n\n## File Structure\n\nEach agent is a folder with:\n\n```\nmy-agent/\n├── protocol.yaml # Main logic (required)\n├── settings.json # Agent metadata (required)\n└── prompts/ # Prompt templates\n ├── system.md\n ├── user-message.md\n └── escalation-summary.md\n```\n\n### settings.json\n\n```json\n{\n \"slug\": \"my-agent\",\n \"name\": \"My Agent\",\n \"description\": \"What this agent does\",\n \"format\": \"interactive\"\n}\n```\n\n| Field | Required | Description |\n| ------------- | -------- | ----------------------------------------------- |\n| `slug` | Yes | URL-safe identifier (lowercase, digits, dashes) |\n| `name` | Yes | Human-readable name |\n| `description` | No | Brief description |\n| `format` | Yes | `interactive` (chat) or `worker` (background) |\n\n## Naming Conventions\n\n- **Slugs**: `lowercase-with-dashes`\n- **Variables**: `UPPERCASE_SNAKE_CASE`\n- **Prompts**: `lowercase-with-dashes.md`\n- **Tools**: `lowercase-with-dashes`\n- **Triggers**: `lowercase-with-dashes`\n\n## Variables in Prompts\n\nReference variables with `{{VARIABLE_NAME}}`:\n\n```markdown\n<!-- prompts/system.md -->\n\nYou are a support agent for {{COMPANY_NAME}}.\n\nHelp users with their {{PRODUCT_NAME}} questions.\n\n## Support Policies\n\n{{SUPPORT_POLICIES}}\n```\n\nVariables are replaced with their values at runtime. If a variable is not provided, it's replaced with an empty string.\n\n## Next Steps\n\n- [Input & Resources](/docs/protocol/input-resources) — Defining agent inputs\n- [Triggers](/docs/protocol/triggers) — How agents are invoked\n- [Tools](/docs/protocol/tools) — External capabilities\n- [Skills](/docs/protocol/skills) — Code execution and knowledge packages\n- [Handlers](/docs/protocol/handlers) — Execution blocks\n- [Agent Config](/docs/protocol/agent-config) — Model and settings\n- [Provider Options](/docs/protocol/provider-options) — Provider-specific features\n- [Types](/docs/protocol/types) — Custom type definitions\n",
116
152
  "excerpt": "Protocol Overview Agent protocols define how an AI agent behaves. They're written in YAML and specify inputs, triggers, tools, and execution handlers. Why Protocols? Protocols provide: - Declarative...",
117
153
  "order": 1
118
154
  },
@@ -121,7 +157,7 @@
121
157
  "section": "protocol",
122
158
  "title": "Input & Resources",
123
159
  "description": "Defining agent inputs and persistent resources.",
124
- "content": "\n# Input & Resources\n\nInputs are provided when creating a session. Resources are persistent state the agent can read and write.\n\n## Input Variables\n\nDefine inputs that consumers must (or may) provide:\n\n```yaml\ninput:\n # Required input\n COMPANY_NAME:\n type: string\n description: The company name to use in responses\n\n # Required input with description\n PRODUCT_NAME:\n type: string\n description: Product being supported\n\n # Optional input (defaults to \"NONE\")\n SUPPORT_POLICIES:\n type: string\n description: Company policies for support\n optional: true\n\n # Optional input with custom default\n USER_ID:\n type: string\n description: Current user's ID\n optional: true\n default: \"\"\n```\n\n### Input Definition\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `type` | Yes | Data type: `string`, `number`, `boolean`, `unknown` |\n| `description` | No | Describes what this input is for |\n| `optional` | No | If true, consumer doesn't have to provide it |\n| `default` | No | Default value if not provided (defaults to `\"NONE\"`) |\n\n### Using Inputs\n\nWhen creating a session, pass input values:\n\n```typescript\nconst sessionId = await client.agentSessions.create('support-chat', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n SUPPORT_POLICIES: 'Refunds within 30 days...',\n // USER_ID is optional, not provided\n});\n```\n\nIn prompts, reference with `{{INPUT_NAME}}`:\n\n```markdown\nYou are a support agent for {{COMPANY_NAME}}.\n```\n\n> **Note:** Variables must be `UPPER_SNAKE_CASE`. Nested properties (dot notation like `{{VAR.property}}`) are not supported. Objects are serialized as JSON when interpolated.\n\n## Resources\n\nResources are persistent state that:\n- Survive across triggers\n- Can be read and written by the agent\n- Are synced to the consumer's application\n\n```yaml\nresources:\n # String resource with default\n CONVERSATION_SUMMARY:\n type: string\n description: Running summary of the conversation\n default: \"\"\n\n # Resource with unknown type (for complex data)\n USER_CONTEXT:\n type: unknown\n description: Cached user information\n default: {}\n\n # Read-only resource (agent can read but not write)\n SYSTEM_CONFIG:\n type: unknown\n description: System configuration\n readonly: true\n default:\n maxRetries: 3\n timeout: 30000\n```\n\n### Resource Definition\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `type` | Yes | Data type: `string`, `number`, `boolean`, `unknown` |\n| `description` | No | Describes the resource purpose |\n| `default` | No | Initial value |\n| `readonly` | No | If true, agent cannot write to it |\n\n### Writing Resources\n\nUse the `set-resource` block in handlers:\n\n```yaml\nhandlers:\n request-human:\n # ... generate summary ...\n \n Save summary:\n type: set-resource\n resource: CONVERSATION_SUMMARY\n value: SUMMARY # Variable containing the value\n```\n\n### Resource Events\n\nWhen a resource is updated, the client SDK receives a `resource-update` event:\n\n```typescript\nuseOctavusChat({\n onResourceUpdate: (name, value) => {\n if (name === 'CONVERSATION_SUMMARY') {\n console.log('Summary updated:', value);\n }\n },\n});\n```\n\n## Variables\n\nVariables are internal state managed by block outputs. They persist across triggers but are not synced to the consumer (unlike resources).\n\n```yaml\nvariables:\n SUMMARY:\n type: string\n description: Generated summary text\n TICKET:\n type: unknown\n description: Ticket creation result\n CONVERSATION_TEXT:\n type: string\n description: Serialized conversation\n```\n\n### Variable Definition\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `type` | Yes | Data type: `string`, `number`, `boolean`, `unknown` |\n| `description` | No | Describes what this variable stores |\n| `default` | No | Initial value |\n\n### Using Variables\n\nSet variables as output from blocks:\n\n```yaml\nhandlers:\n request-human:\n Serialize conversation:\n type: serialize-thread\n format: markdown\n output: CONVERSATION_TEXT # Stores result in variable\n \n Generate summary:\n type: next-message\n output: SUMMARY # LLM output stored in variable\n \n Create ticket:\n type: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY # Use variable as input\n output: TICKET\n```\n\n## Scoping\n\n| Type | Scope | Persistence | Synced to Consumer |\n|------|-------|-------------|---------------------|\n| `input` | Session | Immutable | Yes (at creation) |\n| `resources` | Session | Persists across triggers | Yes (via callbacks) |\n| `variables` | Session | Persists across triggers | No (internal only) |\n\n",
160
+ "content": "\n# Input & Resources\n\nInputs are provided when creating a session. Resources are persistent state the agent can read and write.\n\n## Input Variables\n\nDefine inputs that consumers must (or may) provide:\n\n```yaml\ninput:\n # Required input\n COMPANY_NAME:\n type: string\n description: The company name to use in responses\n\n # Required input with description\n PRODUCT_NAME:\n type: string\n description: Product being supported\n\n # Optional input (defaults to \"NONE\")\n SUPPORT_POLICIES:\n type: string\n description: Company policies for support\n optional: true\n\n # Optional input with custom default\n USER_ID:\n type: string\n description: Current user's ID\n optional: true\n default: ''\n```\n\n### Input Definition\n\n| Field | Required | Description |\n| ------------- | -------- | -------------------------------------------------------------------------------------------------------- |\n| `type` | Yes | Data type: `string`, `number`, `integer`, `boolean`, `unknown`, or a [custom type](/docs/protocol/types) |\n| `description` | No | Describes what this input is for |\n| `optional` | No | If true, consumer doesn't have to provide it |\n| `default` | No | Default value if not provided (defaults to `\"NONE\"`) |\n\n### Using Inputs\n\nWhen creating a session, pass input values:\n\n```typescript\nconst sessionId = await client.agentSessions.create('support-chat', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n SUPPORT_POLICIES: 'Refunds within 30 days...',\n // USER_ID is optional, not provided\n});\n```\n\nIn prompts, reference with `{{INPUT_NAME}}`:\n\n```markdown\nYou are a support agent for {{COMPANY_NAME}}.\n```\n\n> **Note:** Variables must be `UPPER_SNAKE_CASE`. Nested properties (dot notation like `{{VAR.property}}`) are not supported. Objects are serialized as JSON when interpolated.\n\n## Resources\n\nResources are persistent state that:\n\n- Survive across triggers\n- Can be read and written by the agent\n- Are synced to the consumer's application\n\n```yaml\nresources:\n # String resource with default\n CONVERSATION_SUMMARY:\n type: string\n description: Running summary of the conversation\n default: ''\n\n # Resource with unknown type (for complex data)\n USER_CONTEXT:\n type: unknown\n description: Cached user information\n default: {}\n\n # Read-only resource (agent can read but not write)\n SYSTEM_CONFIG:\n type: unknown\n description: System configuration\n readonly: true\n default:\n maxRetries: 3\n timeout: 30000\n```\n\n### Resource Definition\n\n| Field | Required | Description |\n| ------------- | -------- | -------------------------------------------------------------------------------------------------------- |\n| `type` | Yes | Data type: `string`, `number`, `integer`, `boolean`, `unknown`, or a [custom type](/docs/protocol/types) |\n| `description` | No | Describes the resource purpose |\n| `default` | No | Initial value |\n| `readonly` | No | If true, agent cannot write to it |\n\n### Writing Resources\n\nUse the `set-resource` block in handlers:\n\n```yaml\nhandlers:\n request-human:\n # ... generate summary ...\n\n Save summary:\n block: set-resource\n resource: CONVERSATION_SUMMARY\n value: SUMMARY # Variable containing the value\n```\n\n### Resource Events\n\nWhen a resource is updated, the client SDK receives a `resource-update` event:\n\n```typescript\nuseOctavusChat({\n onResourceUpdate: (name, value) => {\n if (name === 'CONVERSATION_SUMMARY') {\n console.log('Summary updated:', value);\n }\n },\n});\n```\n\n## Variables\n\nVariables are internal state managed by block outputs. They persist across triggers but are not synced to the consumer (unlike resources).\n\n```yaml\nvariables:\n SUMMARY:\n type: string\n description: Generated summary text\n TICKET:\n type: unknown\n description: Ticket creation result\n CONVERSATION_TEXT:\n type: string\n description: Serialized conversation\n```\n\n### Variable Definition\n\n| Field | Required | Description |\n| ------------- | -------- | -------------------------------------------------------------------------------------------------------- |\n| `type` | Yes | Data type: `string`, `number`, `integer`, `boolean`, `unknown`, or a [custom type](/docs/protocol/types) |\n| `description` | No | Describes what this variable stores |\n| `default` | No | Initial value |\n\n### Using Variables\n\nSet variables as output from blocks:\n\n```yaml\nhandlers:\n request-human:\n Serialize conversation:\n block: serialize-thread\n format: markdown\n output: CONVERSATION_TEXT # Stores result in variable\n\n Generate summary:\n block: next-message\n output: SUMMARY # LLM output stored in variable\n\n Create ticket:\n block: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY # Use variable as input\n output: TICKET\n```\n\n## Scoping\n\n| Type | Scope | Persistence | Synced to Consumer |\n| ----------- | ------- | ------------------------ | ------------------- |\n| `input` | Session | Immutable | Yes (at creation) |\n| `resources` | Session | Persists across triggers | Yes (via callbacks) |\n| `variables` | Session | Persists across triggers | No (internal only) |\n",
125
161
  "excerpt": "Input & Resources Inputs are provided when creating a session. Resources are persistent state the agent can read and write. Input Variables Define inputs that consumers must (or may) provide: Input...",
126
162
  "order": 2
127
163
  },
@@ -130,7 +166,7 @@
130
166
  "section": "protocol",
131
167
  "title": "Triggers",
132
168
  "description": "Defining how agents are invoked.",
133
- "content": "\n# Triggers\n\nTriggers define how an agent can be invoked. Each trigger has a name, optional inputs, and a corresponding handler.\n\n## Trigger Types\n\n### User Message\n\nThe most common trigger — when a user sends a chat message:\n\n```yaml\ntriggers:\n user-message:\n description: User sends a chat message\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n```\n\n### User Action\n\nFor UI interactions like button clicks:\n\n```yaml\ntriggers:\n request-human:\n description: User clicks \"Talk to Human\" button\n # No input needed - action is implicit\n\n submit-feedback:\n description: User submits feedback form\n input:\n RATING:\n type: number\n description: Rating from 1-5\n COMMENT:\n type: string\n description: Optional comment\n optional: true\n```\n\n### API Trigger\n\nDirect invocation through the SDK:\n\n```yaml\ntriggers:\n analyze-document:\n description: Analyze an uploaded document\n input:\n DOCUMENT_URL:\n type: string\n ANALYSIS_TYPE:\n type: string\n description: Type of analysis (summary, sentiment, extraction)\n```\n\n## Trigger Definition\n\n```yaml\ntriggers:\n trigger-name:\n description: Optional description\n input:\n VARIABLE_NAME:\n type: string | number | boolean | unknown\n description: What this input is for\n optional: true | false # defaults to false\n default: value # default if optional and not provided\n```\n\n## Invoking Triggers\n\n### From Client SDK\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { send } = useOctavusChat({ transport });\n\n // User message trigger with UI message\n await send(\n 'user-message',\n { USER_MESSAGE: text },\n { userMessage: { content: text } },\n );\n\n // User action trigger (no input, no UI message)\n await send('request-human');\n\n // Action with input\n await send('submit-feedback', { RATING: 5, COMMENT: 'Great help!' });\n}\n```\n\n### From Server SDK\n\n```typescript\n// trigger() returns an async generator of events\nconst events = session.trigger('user-message', {\n USER_MESSAGE: 'Help me with billing',\n});\n\n// Iterate events directly\nfor await (const event of events) {\n console.log(event);\n}\n\n// Or convert to SSE for HTTP responses\nimport { toSSEStream } from '@octavus/server-sdk';\nreturn new Response(toSSEStream(events), { headers: { 'Content-Type': 'text/event-stream' } });\n```\n\n## Handlers\n\nEach trigger must have a corresponding handler:\n\n```yaml\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n\n request-human:\n description: Escalate to human support\n\nhandlers:\n user-message:\n # Blocks executed when user-message is triggered\n Add user message:\n type: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Respond:\n type: next-message\n\n request-human:\n # Blocks executed when request-human is triggered\n Summarize:\n type: serialize-thread\n # ...\n```\n\n## Trigger Input Naming\n\nTrigger inputs use `UPPERCASE_SNAKE_CASE`:\n\n```yaml\ntriggers:\n search-products:\n input:\n SEARCH_QUERY: { type: string }\n MAX_RESULTS: { type: number, optional: true, default: 10 }\n FILTER_CATEGORY: { type: string, optional: true }\n```\n\n## Best Practices\n\n### 1. Use Descriptive Names\n\n```yaml\n# Good\ntriggers:\n user-message: # Clear - user sends chat message\n request-human: # Clear - wants human support\n cancel-subscription: # Clear - specific action\n\n# Avoid\ntriggers:\n trigger1: # Unclear\n msg: # Too abbreviated\n do-thing: # Vague\n```\n\n### 2. Document Triggers\n\n```yaml\ntriggers:\n escalate-to-tier2:\n description: >\n Escalate the conversation to tier 2 support.\n Should be called when the issue cannot be resolved\n at tier 1 level.\n input:\n REASON:\n type: string\n description: Why escalation is needed\n```\n\n### 3. Keep Triggers Focused\n\nEach trigger should do one thing:\n\n```yaml\n# Good - focused triggers\ntriggers:\n send-message:\n input: { MESSAGE: { type: string } }\n\n upload-file:\n input: { FILE_URL: { type: string } }\n\n request-callback:\n input: { PHONE: { type: string } }\n\n# Avoid - overloaded trigger\ntriggers:\n user-action:\n input:\n ACTION_TYPE: { type: string } # \"message\" | \"file\" | \"callback\"\n PAYLOAD: { type: unknown } # Different structure per type\n```\n",
169
+ "content": "\n# Triggers\n\nTriggers define how an agent can be invoked. Each trigger has a name, optional inputs, and a corresponding handler.\n\n## Trigger Types\n\n### User Message\n\nThe most common trigger — when a user sends a chat message:\n\n```yaml\ntriggers:\n user-message:\n description: User sends a chat message\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n```\n\n### User Action\n\nFor UI interactions like button clicks:\n\n```yaml\ntriggers:\n request-human:\n description: User clicks \"Talk to Human\" button\n # No input needed - action is implicit\n\n submit-feedback:\n description: User submits feedback form\n input:\n RATING:\n type: number\n description: Rating from 1-5\n COMMENT:\n type: string\n description: Optional comment\n optional: true\n```\n\n### API Trigger\n\nDirect invocation through the SDK:\n\n```yaml\ntriggers:\n analyze-document:\n description: Analyze an uploaded document\n input:\n DOCUMENT_URL:\n type: string\n ANALYSIS_TYPE:\n type: string\n description: Type of analysis (summary, sentiment, extraction)\n```\n\n## Trigger Definition\n\n```yaml\ntriggers:\n trigger-name:\n description: Optional description\n input:\n VARIABLE_NAME:\n type: string | number | integer | boolean | unknown | CustomType\n description: What this input is for\n optional: true | false # defaults to false\n default: value # default if optional and not provided\n```\n\n> **Tip**: You can use [custom types](/docs/protocol/types) for complex trigger inputs.\n\n## Invoking Triggers\n\n### From Client SDK\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { send } = useOctavusChat({ transport });\n\n // User message trigger with UI message\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n\n // User action trigger (no input, no UI message)\n await send('request-human');\n\n // Action with input\n await send('submit-feedback', { RATING: 5, COMMENT: 'Great help!' });\n}\n```\n\n### From Server SDK\n\n```typescript\n// trigger() returns an async generator of events\nconst events = session.trigger('user-message', {\n USER_MESSAGE: 'Help me with billing',\n});\n\n// Iterate events directly\nfor await (const event of events) {\n console.log(event);\n}\n\n// Or convert to SSE for HTTP responses\nimport { toSSEStream } from '@octavus/server-sdk';\nreturn new Response(toSSEStream(events), { headers: { 'Content-Type': 'text/event-stream' } });\n```\n\n## Handlers\n\nEach trigger must have a corresponding handler:\n\n```yaml\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n\n request-human:\n description: Escalate to human support\n\nhandlers:\n user-message:\n # Blocks executed when user-message is triggered\n Add user message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Respond:\n block: next-message\n\n request-human:\n # Blocks executed when request-human is triggered\n Summarize:\n block: serialize-thread\n # ...\n```\n\n## Trigger Input Naming\n\nTrigger inputs use `UPPERCASE_SNAKE_CASE`:\n\n```yaml\ntriggers:\n search-products:\n input:\n SEARCH_QUERY: { type: string }\n MAX_RESULTS: { type: number, optional: true, default: 10 }\n FILTER_CATEGORY: { type: string, optional: true }\n```\n\n## Best Practices\n\n### 1. Use Descriptive Names\n\n```yaml\n# Good\ntriggers:\n user-message: # Clear - user sends chat message\n request-human: # Clear - wants human support\n cancel-subscription: # Clear - specific action\n\n# Avoid\ntriggers:\n trigger1: # Unclear\n msg: # Too abbreviated\n do-thing: # Vague\n```\n\n### 2. Document Triggers\n\n```yaml\ntriggers:\n escalate-to-tier2:\n description: >\n Escalate the conversation to tier 2 support.\n Should be called when the issue cannot be resolved\n at tier 1 level.\n input:\n REASON:\n type: string\n description: Why escalation is needed\n```\n\n### 3. Keep Triggers Focused\n\nEach trigger should do one thing:\n\n```yaml\n# Good - focused triggers\ntriggers:\n send-message:\n input: { MESSAGE: { type: string } }\n\n upload-file:\n input: { FILE_URL: { type: string } }\n\n request-callback:\n input: { PHONE: { type: string } }\n\n# Avoid - overloaded trigger\ntriggers:\n user-action:\n input:\n ACTION_TYPE: { type: string } # \"message\" | \"file\" | \"callback\"\n PAYLOAD: { type: unknown } # Different structure per type\n```\n",
134
170
  "excerpt": "Triggers Triggers define how an agent can be invoked. Each trigger has a name, optional inputs, and a corresponding handler. Trigger Types User Message The most common trigger — when a user sends a...",
135
171
  "order": 3
136
172
  },
@@ -138,35 +174,71 @@
138
174
  "slug": "protocol/tools",
139
175
  "section": "protocol",
140
176
  "title": "Tools",
141
- "description": "Defining external tools agents can use.",
142
- "content": "\n# Tools\n\nTools extend what agents can do. They're defined in the protocol and implemented in your backend.\n\n## Tool Definition\n\n```yaml\ntools:\n get-user-account:\n description: Looking up your account information\n display: description\n parameters:\n userId:\n type: string\n description: The user ID to look up\n```\n\n### Tool Fields\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `description` | Yes | What the tool does (shown to LLM and optionally user) |\n| `display` | No | How to show in UI: `hidden`, `name`, `description`, `stream` |\n| `parameters` | No | Input parameters the tool accepts |\n\n### Display Modes\n\n| Mode | Behavior |\n|------|----------|\n| `hidden` | Tool runs silently, user doesn't see it |\n| `name` | Shows tool name while executing |\n| `description` | Shows description while executing (default) |\n| `stream` | Streams tool progress if available |\n\n## Parameters\n\n### Parameter Fields\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `type` | Yes | Data type: `string`, `number`, `boolean`, `unknown` |\n| `description` | No | Describes what this parameter is for |\n| `optional` | No | If true, parameter is not required (default: false) |\n\n### Optional Parameters\n\nParameters are **required by default**. Use `optional: true` to make a parameter optional:\n\n```yaml\ntools:\n search-products:\n description: Search the product catalog\n parameters:\n query:\n type: string\n description: Search query\n\n category:\n type: string\n description: Filter by category\n optional: true\n\n maxPrice:\n type: number\n description: Maximum price filter\n optional: true\n\n inStock:\n type: boolean\n description: Only show in-stock items\n optional: true\n```\n\n## Making Tools Available\n\nTools defined in `tools:` are available. To make them usable by the LLM, add them to `agent.tools`:\n\n```yaml\ntools:\n get-user-account:\n description: Look up user account\n parameters:\n userId: { type: string }\n\n create-support-ticket:\n description: Create a support ticket\n parameters:\n summary: { type: string }\n priority: { type: string } # low, medium, high, urgent\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools:\n - get-user-account\n - create-support-ticket # LLM can decide when to call these\n agentic: true\n```\n\n## Tool Invocation Modes\n\n### LLM-Decided (Agentic)\n\nThe LLM decides when to call tools based on the conversation:\n\n```yaml\nagent:\n tools: [get-user-account, create-support-ticket]\n agentic: true # Allow multiple tool calls\n maxSteps: 10 # Max tool call cycles\n```\n\n### Deterministic (Block-Based)\n\nForce tool calls at specific points in the handler:\n\n```yaml\nhandlers:\n request-human:\n # Always create a ticket when escalating\n Create support ticket:\n type: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY # From variable\n priority: medium # Literal value\n output: TICKET # Store result\n```\n\n## Tool Results\n\n### In Prompts\n\nTool results are stored in variables. Reference the variable in prompts:\n\n```markdown\n<!-- prompts/ticket-directive.md -->\nA support ticket has been created:\n{{TICKET}}\n\nLet the user know their ticket has been created.\n```\n\nWhen the `TICKET` variable contains an object, it's automatically serialized as JSON in the prompt:\n\n```\nA support ticket has been created:\n{\n \"ticketId\": \"TKT-123ABC\",\n \"estimatedResponse\": \"24 hours\"\n}\n\nLet the user know their ticket has been created.\n```\n\n> **Note**: Variables use `{{VARIABLE_NAME}}` syntax with `UPPERCASE_SNAKE_CASE`. Dot notation (like `{{TICKET.ticketId}}`) is not supported. Objects are automatically JSON-serialized.\n\n### In Variables\n\nStore tool results for later use:\n\n```yaml\nhandlers:\n request-human:\n Get account:\n type: tool-call\n tool: get-user-account\n input:\n userId: USER_ID\n output: ACCOUNT # Result stored here\n\n Create ticket:\n type: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY\n priority: medium\n output: TICKET\n```\n\n## Implementing Tools\n\nTools are implemented in your backend:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n const user = await db.users.findById(userId);\n\n return {\n name: user.name,\n email: user.email,\n plan: user.subscription.plan,\n createdAt: user.createdAt.toISOString(),\n };\n },\n\n 'create-support-ticket': async (args) => {\n const ticket = await ticketService.create({\n summary: args.summary as string,\n priority: args.priority as string,\n });\n\n return {\n ticketId: ticket.id,\n estimatedResponse: getEstimatedTime(args.priority),\n };\n },\n },\n});\n```\n\n## Tool Best Practices\n\n### 1. Clear Descriptions\n\n```yaml\ntools:\n # Good - clear and specific\n get-user-account:\n description: >\n Retrieves the user's account information including name, email,\n subscription plan, and account creation date. Use this when the\n user asks about their account or you need to verify their identity.\n\n # Avoid - vague\n get-data:\n description: Gets some data\n```\n\n### 2. Document Constrained Values\n\n```yaml\ntools:\n create-support-ticket:\n parameters:\n priority:\n type: string\n description: Ticket priority level (low, medium, high, urgent)\n```\n",
143
- "excerpt": "Tools Tools extend what agents can do. They're defined in the protocol and implemented in your backend. Tool Definition Tool Fields | Field | Required | Description |...",
177
+ "description": "Defining external tools implemented in your backend.",
178
+ "content": "\n# Tools\n\nTools extend what agents can do. Octavus supports multiple types:\n\n1. **External Tools** — Defined in the protocol, implemented in your backend (this page)\n2. **Provider Tools** — Built-in tools executed server-side by the provider (e.g., Anthropic's web search)\n3. **Skills** — Code execution and knowledge packages (see [Skills](/docs/protocol/skills))\n\nThis page covers external tools. For provider tools, see [Provider Options](/docs/protocol/provider-options). For code execution capabilities, see [Skills](/docs/protocol/skills).\n\n## External Tools\n\nExternal tools are defined in the `tools:` section and implemented in your backend.\n\n## Defining Tools\n\n```yaml\ntools:\n get-user-account:\n description: Looking up your account information\n display: description\n parameters:\n userId:\n type: string\n description: The user ID to look up\n```\n\n### Tool Fields\n\n| Field | Required | Description |\n| ------------- | -------- | ------------------------------------------------------------ |\n| `description` | Yes | What the tool does (shown to LLM and optionally user) |\n| `display` | No | How to show in UI: `hidden`, `name`, `description`, `stream` |\n| `parameters` | No | Input parameters the tool accepts |\n\n### Display Modes\n\n| Mode | Behavior |\n| ------------- | ------------------------------------------- |\n| `hidden` | Tool runs silently, user doesn't see it |\n| `name` | Shows tool name while executing |\n| `description` | Shows description while executing (default) |\n| `stream` | Streams tool progress if available |\n\n## Parameters\n\nTool calls are always objects where each parameter name maps to a value. The LLM generates: `{ param1: value1, param2: value2, ... }`\n\n### Parameter Fields\n\n| Field | Required | Description |\n| ------------- | -------- | -------------------------------------------------------------------------------- |\n| `type` | Yes | Data type: `string`, `number`, `integer`, `boolean`, `unknown`, or a custom type |\n| `description` | No | Describes what this parameter is for |\n| `optional` | No | If true, parameter is not required (default: false) |\n\n> **Tip**: You can use [custom types](/docs/protocol/types) for complex parameters like `type: ProductFilter` or `type: SearchOptions`.\n\n### Array Parameters\n\nFor array parameters, define a [top-level array type](/docs/protocol/types#top-level-array-types) and use it:\n\n```yaml\ntypes:\n CartItem:\n productId:\n type: string\n quantity:\n type: integer\n\n CartItemList:\n type: array\n items:\n type: CartItem\n\ntools:\n add-to-cart:\n description: Add items to cart\n parameters:\n items:\n type: CartItemList\n description: Items to add\n```\n\nThe tool receives: `{ items: [{ productId: \"...\", quantity: 1 }, ...] }`\n\n### Optional Parameters\n\nParameters are **required by default**. Use `optional: true` to make a parameter optional:\n\n```yaml\ntools:\n search-products:\n description: Search the product catalog\n parameters:\n query:\n type: string\n description: Search query\n\n category:\n type: string\n description: Filter by category\n optional: true\n\n maxPrice:\n type: number\n description: Maximum price filter\n optional: true\n\n inStock:\n type: boolean\n description: Only show in-stock items\n optional: true\n```\n\n## Making Tools Available\n\nTools defined in `tools:` are available. To make them usable by the LLM, add them to `agent.tools`:\n\n```yaml\ntools:\n get-user-account:\n description: Look up user account\n parameters:\n userId: { type: string }\n\n create-support-ticket:\n description: Create a support ticket\n parameters:\n summary: { type: string }\n priority: { type: string } # low, medium, high, urgent\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools:\n - get-user-account\n - create-support-ticket # LLM can decide when to call these\n agentic: true\n```\n\n## Tool Invocation Modes\n\n### LLM-Decided (Agentic)\n\nThe LLM decides when to call tools based on the conversation:\n\n```yaml\nagent:\n tools: [get-user-account, create-support-ticket]\n agentic: true # Allow multiple tool calls\n maxSteps: 10 # Max tool call cycles\n```\n\n### Deterministic (Block-Based)\n\nForce tool calls at specific points in the handler:\n\n```yaml\nhandlers:\n request-human:\n # Always create a ticket when escalating\n Create support ticket:\n block: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY # From variable\n priority: medium # Literal value\n output: TICKET # Store result\n```\n\n## Tool Results\n\n### In Prompts\n\nTool results are stored in variables. Reference the variable in prompts:\n\n```markdown\n<!-- prompts/ticket-directive.md -->\n\nA support ticket has been created:\n{{TICKET}}\n\nLet the user know their ticket has been created.\n```\n\nWhen the `TICKET` variable contains an object, it's automatically serialized as JSON in the prompt:\n\n```\nA support ticket has been created:\n{\n \"ticketId\": \"TKT-123ABC\",\n \"estimatedResponse\": \"24 hours\"\n}\n\nLet the user know their ticket has been created.\n```\n\n> **Note**: Variables use `{{VARIABLE_NAME}}` syntax with `UPPERCASE_SNAKE_CASE`. Dot notation (like `{{TICKET.ticketId}}`) is not supported. Objects are automatically JSON-serialized.\n\n### In Variables\n\nStore tool results for later use:\n\n```yaml\nhandlers:\n request-human:\n Get account:\n block: tool-call\n tool: get-user-account\n input:\n userId: USER_ID\n output: ACCOUNT # Result stored here\n\n Create ticket:\n block: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY\n priority: medium\n output: TICKET\n```\n\n## Implementing Tools\n\nTools are implemented in your backend:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n const user = await db.users.findById(userId);\n\n return {\n name: user.name,\n email: user.email,\n plan: user.subscription.plan,\n createdAt: user.createdAt.toISOString(),\n };\n },\n\n 'create-support-ticket': async (args) => {\n const ticket = await ticketService.create({\n summary: args.summary as string,\n priority: args.priority as string,\n });\n\n return {\n ticketId: ticket.id,\n estimatedResponse: getEstimatedTime(args.priority),\n };\n },\n },\n});\n```\n\n## Tool Best Practices\n\n### 1. Clear Descriptions\n\n```yaml\ntools:\n # Good - clear and specific\n get-user-account:\n description: >\n Retrieves the user's account information including name, email,\n subscription plan, and account creation date. Use this when the\n user asks about their account or you need to verify their identity.\n\n # Avoid - vague\n get-data:\n description: Gets some data\n```\n\n### 2. Document Constrained Values\n\n```yaml\ntools:\n create-support-ticket:\n parameters:\n priority:\n type: string\n description: Ticket priority level (low, medium, high, urgent)\n```\n",
179
+ "excerpt": "Tools Tools extend what agents can do. Octavus supports multiple types: 1. External Tools — Defined in the protocol, implemented in your backend (this page) 2. Provider Tools Built-in tools...",
144
180
  "order": 4
145
181
  },
182
+ {
183
+ "slug": "protocol/skills",
184
+ "section": "protocol",
185
+ "title": "Skills",
186
+ "description": "Using Octavus skills for code execution and specialized capabilities.",
187
+ "content": "\n# Skills\n\nSkills are knowledge packages that enable agents to execute code and generate files in isolated sandbox environments. Unlike external tools (which you implement in your backend), skills are self-contained packages with documentation and scripts that run in secure sandboxes.\n\n## Overview\n\nOctavus Skills provide **provider-agnostic** code execution. They work with any LLM provider (Anthropic, OpenAI, Google) by using explicit tool calls and system prompt injection.\n\n### How Skills Work\n\n1. **Skill Definition**: Skills are defined in the protocol's `skills:` section\n2. **Skill Resolution**: Skills are resolved from available sources (see below)\n3. **Sandbox Execution**: When a skill is used, code runs in an isolated sandbox environment\n4. **File Generation**: Files saved to `/output/` are automatically captured and made available for download\n\n### Skill Sources\n\nSkills come from two sources, visible in the Skills tab of your organization:\n\n| Source | Badge in UI | Visibility | Example |\n| ----------- | ----------- | ------------------------------ | ------------------ |\n| **Octavus** | `Octavus` | Available to all organizations | `qr-code` |\n| **Custom** | None | Private to your organization | `my-company-skill` |\n\nWhen you reference a skill in your protocol, Octavus resolves it from your available skills. If you create a custom skill with the same name as an Octavus skill, your custom skill takes precedence.\n\n## Defining Skills\n\nDefine skills in the protocol's `skills:` section:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data and generating reports\n```\n\n### Skill Fields\n\n| Field | Required | Description |\n| ------------- | -------- | ------------------------------------------------------------------------------------- |\n| `display` | No | How to show in UI: `hidden`, `name`, `description`, `stream` (default: `description`) |\n| `description` | No | Custom description shown to users (overrides skill's built-in description) |\n\n### Display Modes\n\n| Mode | Behavior |\n| ------------- | ------------------------------------------- |\n| `hidden` | Skill usage not shown to users |\n| `name` | Shows skill name while executing |\n| `description` | Shows description while executing (default) |\n| `stream` | Streams progress if available |\n\n## Enabling Skills\n\nAfter defining skills in the `skills:` section, specify which skills are available for the chat thread in `agent.skills`:\n\n```yaml\n# All skills available to this agent (defined once at protocol level)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\n# Skills available for this chat thread\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [get-user-account]\n skills: [qr-code] # Skills available for this thread\n agentic: true\n```\n\n## Skill Tools\n\nWhen skills are enabled, the LLM has access to these tools:\n\n| Tool | Purpose |\n| -------------------- | --------------------------------------- |\n| `octavus_skill_read` | Read skill documentation (SKILL.md) |\n| `octavus_skill_list` | List available scripts in a skill |\n| `octavus_skill_run` | Execute a pre-built script from a skill |\n| `octavus_code_run` | Execute arbitrary Python/Bash code |\n| `octavus_file_write` | Create files in the sandbox |\n| `octavus_file_read` | Read files from the sandbox |\n\nThe LLM learns about available skills through system prompt injection and can use these tools to interact with skills.\n\n## Example: QR Code Generation\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n agentic: true\n\nhandlers:\n user-message:\n Add message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Respond:\n block: next-message\n```\n\nWhen a user asks \"Create a QR code for octavus.ai\", the LLM will:\n\n1. Recognize the task matches the `qr-code` skill\n2. Call `octavus_skill_read` to learn how to use the skill\n3. Execute code (via `octavus_code_run` or `octavus_skill_run`) to generate the QR code\n4. Save the image to `/output/` in the sandbox\n5. The file is automatically captured and made available for download\n\n## File Output\n\nFiles saved to `/output/` in the sandbox are automatically:\n\n1. **Captured** after code execution\n2. **Uploaded** to S3 storage\n3. **Made available** via presigned URLs\n4. **Included** in the message as file parts\n\nFiles persist across page refreshes and are stored in the session's message history.\n\n## Skill Format\n\nSkills follow the [Agent Skills](https://agentskills.io) open standard:\n\n- `SKILL.md` - Required skill documentation with YAML frontmatter\n- `scripts/` - Optional executable code (Python/Bash)\n- `references/` - Optional documentation loaded as needed\n- `assets/` - Optional files used in outputs (templates, images)\n\n### SKILL.md Format\n\n````yaml\n---\nname: qr-code\ndescription: >\n Generate QR codes from text, URLs, or data. Use when the user needs to create\n a QR code for any purpose - sharing links, contact information, WiFi credentials,\n or any text data that should be scannable.\nversion: 1.0.0\nlicense: MIT\nauthor: Octavus Team\n---\n\n# QR Code Generator\n\n## Overview\n\nThis skill creates QR codes from text data using Python...\n\n## Quick Start\n\nGenerate a QR code with Python:\n\n```python\nimport qrcode\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n# ... code to generate QR code ...\n````\n\n## Scripts Reference\n\n### scripts/generate.py\n\nMain script for generating QR codes...\n\n````\n\n## Best Practices\n\n### 1. Clear Descriptions\n\nProvide clear, purpose-driven descriptions:\n\n```yaml\nskills:\n # Good - clear purpose\n qr-code:\n description: Generating QR codes for URLs, contact info, or any text data\n\n # Avoid - vague\n utility:\n description: Does stuff\n````\n\n### 2. When to Use Skills vs Tools\n\n| Use Skills When | Use Tools When |\n| ------------------------ | ---------------------------- |\n| Code execution needed | Simple API calls |\n| File generation | Database queries |\n| Complex calculations | External service integration |\n| Data processing | Authentication required |\n| Provider-agnostic needed | Backend-specific logic |\n\n### 3. Skill Selection\n\nDefine all skills available to this agent in the `skills:` section. Then specify which skills are available for the chat thread in `agent.skills`:\n\n```yaml\n# All skills available to this agent (defined once at protocol level)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data\n pdf-processor:\n display: description\n description: Processing PDFs\n\n# Skills available for this chat thread\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code, data-analysis] # Skills available for this thread\n```\n\n### 4. Display Modes\n\nChoose appropriate display modes based on user experience:\n\n```yaml\nskills:\n # Background processing - hide from user\n data-analysis:\n display: hidden\n\n # User-facing generation - show description\n qr-code:\n display: description\n\n # Interactive progress - stream updates\n report-generation:\n display: stream\n```\n\n## Comparison: Skills vs Tools vs Provider Options\n\n| Feature | Octavus Skills | External Tools | Provider Tools/Skills |\n| ------------------ | ----------------- | ------------------- | --------------------- |\n| **Execution** | Isolated sandbox | Your backend | Provider servers |\n| **Provider** | Any (agnostic) | N/A | Provider-specific |\n| **Code Execution** | Yes | No | Yes (provider tools) |\n| **File Output** | Yes | No | Yes (provider skills) |\n| **Implementation** | Skill packages | Your code | Built-in |\n| **Cost** | Sandbox + LLM API | Your infrastructure | Included in API |\n\n## Uploading Custom Skills\n\nYou can upload custom skills to your organization:\n\n1. Create a skill following the [Agent Skills](https://agentskills.io) format\n2. Package it as a `.skill` bundle (ZIP file)\n3. Upload via the platform UI\n4. Reference by slug in your protocol\n\n```yaml\nskills:\n custom-analysis:\n display: description\n description: Custom analysis tool\n\nagent:\n skills: [custom-analysis]\n```\n\n## Security\n\nSkills run in isolated sandbox environments:\n\n- **No network access** (unless explicitly configured)\n- **No persistent storage** (sandbox destroyed after execution)\n- **File output only** via `/output/` directory\n- **Time limits** enforced (5-minute default timeout)\n\n## Next Steps\n\n- [Agent Config](/docs/protocol/agent-config) — Configuring skills in agent settings\n- [Provider Options](/docs/protocol/provider-options) — Anthropic's built-in skills\n- [Skills Advanced Guide](/docs/protocol/skills-advanced) — Best practices and advanced patterns\n",
188
+ "excerpt": "Skills Skills are knowledge packages that enable agents to execute code and generate files in isolated sandbox environments. Unlike external tools (which you implement in your backend), skills are...",
189
+ "order": 5
190
+ },
146
191
  {
147
192
  "slug": "protocol/handlers",
148
193
  "section": "protocol",
149
194
  "title": "Handlers",
150
195
  "description": "Defining execution handlers with blocks.",
151
- "content": "\n# Handlers\n\nHandlers define what happens when a trigger fires. They contain execution blocks that run in sequence.\n\n## Handler Structure\n\n```yaml\nhandlers:\n trigger-name:\n Block Name:\n type: block-type\n # block-specific properties\n \n Another Block:\n type: another-type\n # ...\n```\n\nEach block has a human-readable name (shown in debug UI) and a type that determines its behavior.\n\n## Block Types\n\n### next-message\n\nGenerate a response from the LLM:\n\n```yaml\nhandlers:\n user-message:\n Respond to user:\n type: next-message\n # Uses main conversation thread by default\n # Display defaults to 'stream'\n```\n\nWith options:\n\n```yaml\nGenerate summary:\n type: next-message\n thread: summary # Use named thread\n display: stream # Show streaming content\n independent: true # Don't add to main chat\n output: SUMMARY # Store output in variable\n description: Generating summary # Shown in UI\n```\n\n### add-message\n\nAdd a message to the conversation:\n\n```yaml\nAdd user message:\n type: add-message\n role: user # user | assistant | system\n prompt: user-message # Reference to prompt file\n input: [USER_MESSAGE] # Variables to interpolate\n display: hidden # Don't show in UI\n```\n\nFor internal directives (LLM sees it, user doesn't):\n\n```yaml\nAdd internal directive:\n type: add-message\n role: user\n prompt: ticket-directive\n input: [TICKET_DETAILS]\n visible: false # LLM sees this, user doesn't\n```\n\n### tool-call\n\nCall a tool deterministically:\n\n```yaml\nCreate ticket:\n type: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY # Variable reference\n priority: medium # Literal value\n output: TICKET # Store result\n```\n\n### set-resource\n\nUpdate a persistent resource:\n\n```yaml\nSave summary:\n type: set-resource\n resource: CONVERSATION_SUMMARY\n value: SUMMARY # Variable to save\n display: name # Show block name\n```\n\n### start-thread\n\nCreate a named conversation thread:\n\n```yaml\nStart summary thread:\n type: start-thread\n thread: summary # Thread name\n model: anthropic/claude-sonnet-4-5 # Optional: different model\n thinking: low # Extended reasoning level\n maxSteps: 1 # Tool call limit\n system: escalation-summary # System prompt\n input: [COMPANY_NAME] # Variables for prompt\n```\n\n### serialize-thread\n\nConvert conversation to text:\n\n```yaml\nSerialize conversation:\n type: serialize-thread\n thread: main # Which thread (default: main)\n format: markdown # markdown | json\n output: CONVERSATION_TEXT # Variable to store result\n```\n\n## Display Modes\n\nEvery block has a `display` property:\n\n| Mode | Default For | Behavior |\n|------|-------------|----------|\n| `hidden` | add-message | Not shown to user |\n| `name` | set-resource | Shows block name |\n| `description` | tool-call | Shows description |\n| `stream` | next-message | Streams content |\n\n## Complete Example\n\n```yaml\nhandlers:\n user-message:\n # Add the user's message to conversation\n Add user message:\n type: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n display: hidden\n \n # Generate response (LLM may call tools)\n Respond to user:\n type: next-message\n # display: stream (default)\n\n request-human:\n # Step 1: Serialize conversation for summary\n Serialize conversation:\n type: serialize-thread\n format: markdown\n output: CONVERSATION_TEXT\n \n # Step 2: Create separate thread for summarization\n Start summary thread:\n type: start-thread\n thread: summary\n model: anthropic/claude-sonnet-4-5\n thinking: low\n system: escalation-summary\n input: [COMPANY_NAME]\n \n # Step 3: Add request to summary thread\n Add summarize request:\n type: add-message\n thread: summary\n role: user\n prompt: summarize-request\n input:\n - CONVERSATION: CONVERSATION_TEXT\n \n # Step 4: Generate summary\n Generate summary:\n type: next-message\n thread: summary\n display: stream\n description: Summarizing your conversation\n independent: true\n output: SUMMARY\n \n # Step 5: Save to resource\n Save summary:\n type: set-resource\n resource: CONVERSATION_SUMMARY\n value: SUMMARY\n \n # Step 6: Create support ticket\n Create ticket:\n type: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY\n priority: medium\n output: TICKET\n \n # Step 7: Add directive for response\n Add directive:\n type: add-message\n role: user\n prompt: ticket-directive\n input: [TICKET_DETAILS: TICKET]\n visible: false\n \n # Step 8: Respond to user\n Respond:\n type: next-message\n```\n\n## Block Input Mapping\n\nMap variables to block inputs:\n\n```yaml\n# Simple list (variable name = prompt variable)\ninput: [USER_MESSAGE, COMPANY_NAME]\n\n# Mapping (different names)\ninput:\n - CONVERSATION: CONVERSATION_TEXT # CONVERSATION in prompt = CONVERSATION_TEXT\n - TICKET_DETAILS: TICKET\n```\n\n## Independent Blocks\n\nUse `independent: true` for content that shouldn't go to the main chat:\n\n```yaml\nGenerate summary:\n type: next-message\n thread: summary\n independent: true # Output stored in variable, not main chat\n output: SUMMARY\n```\n\nThis is useful for:\n- Background processing\n- Summarization in separate threads\n- Generating content for tools\n\n",
152
- "excerpt": "Handlers Handlers define what happens when a trigger fires. They contain execution blocks that run in sequence. Handler Structure Each block has a human-readable name (shown in debug UI) and a type...",
153
- "order": 5
196
+ "content": "\n# Handlers\n\nHandlers define what happens when a trigger fires. They contain execution blocks that run in sequence.\n\n## Handler Structure\n\n```yaml\nhandlers:\n trigger-name:\n Block Name:\n block: block-kind\n # block-specific properties\n\n Another Block:\n block: another-kind\n # ...\n```\n\nEach block has a human-readable name (shown in debug UI) and a `block` field that determines its behavior.\n\n## Block Kinds\n\n### next-message\n\nGenerate a response from the LLM:\n\n```yaml\nhandlers:\n user-message:\n Respond to user:\n block: next-message\n # Uses main conversation thread by default\n # Display defaults to 'stream'\n```\n\nWith options:\n\n```yaml\nGenerate summary:\n block: next-message\n thread: summary # Use named thread\n display: stream # Show streaming content\n independent: true # Don't add to main chat\n output: SUMMARY # Store output in variable\n description: Generating summary # Shown in UI\n```\n\nFor structured output (typed JSON response):\n\n```yaml\nRespond with suggestions:\n block: next-message\n responseType: ChatResponse # Type defined in types section\n output: RESPONSE # Stores the parsed object\n```\n\nWhen `responseType` is specified:\n\n- The LLM generates JSON matching the type schema\n- The `output` variable receives the parsed object (not plain text)\n- The client receives a `UIObjectPart` for custom rendering\n\nSee [Types](/docs/protocol/types#structured-output) for more details.\n\n### add-message\n\nAdd a message to the conversation:\n\n```yaml\nAdd user message:\n block: add-message\n role: user # user | assistant | system\n prompt: user-message # Reference to prompt file\n input: [USER_MESSAGE] # Variables to interpolate\n display: hidden # Don't show in UI\n```\n\nFor internal directives (LLM sees it, user doesn't):\n\n```yaml\nAdd internal directive:\n block: add-message\n role: user\n prompt: ticket-directive\n input: [TICKET_DETAILS]\n visible: false # LLM sees this, user doesn't\n```\n\nFor structured user input (object shown in UI, prompt for LLM context):\n\n```yaml\nAdd user message:\n block: add-message\n role: user\n prompt: user-message # Rendered for LLM context (hidden from UI)\n input: [USER_INPUT]\n uiContent: USER_INPUT # Variable shown in UI (object → object part)\n display: hidden\n```\n\nWhen `uiContent` is set:\n\n- The variable value is shown in the UI (string → text part, object → object part)\n- The prompt text is hidden from the UI but kept for LLM context\n- Useful for rich UI interactions where the visual differs from the LLM context\n\n### tool-call\n\nCall a tool deterministically:\n\n```yaml\nCreate ticket:\n block: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY # Variable reference\n priority: medium # Literal value\n output: TICKET # Store result\n```\n\n### set-resource\n\nUpdate a persistent resource:\n\n```yaml\nSave summary:\n block: set-resource\n resource: CONVERSATION_SUMMARY\n value: SUMMARY # Variable to save\n display: name # Show block name\n```\n\n### start-thread\n\nCreate a named conversation thread:\n\n```yaml\nStart summary thread:\n block: start-thread\n thread: summary # Thread name\n model: anthropic/claude-sonnet-4-5 # Optional: different model\n thinking: low # Extended reasoning level\n maxSteps: 1 # Tool call limit\n system: escalation-summary # System prompt\n input: [COMPANY_NAME] # Variables for prompt\n```\n\n### serialize-thread\n\nConvert conversation to text:\n\n```yaml\nSerialize conversation:\n block: serialize-thread\n thread: main # Which thread (default: main)\n format: markdown # markdown | json\n output: CONVERSATION_TEXT # Variable to store result\n```\n\n### generate-image\n\nGenerate an image from a prompt variable:\n\n```yaml\nGenerate image:\n block: generate-image\n prompt: OPTIMIZED_PROMPT # Variable containing the prompt\n imageModel: google/gemini-2.5-flash-image # Required image model\n size: 1024x1024 # 1024x1024 | 1792x1024 | 1024x1792\n output: GENERATED_IMAGE # Store URL in variable\n description: Generating your image... # Shown in UI\n```\n\nThis block is for deterministic image generation pipelines where the prompt is constructed programmatically (e.g., via prompt engineering in a separate thread).\n\nFor agentic image generation where the LLM decides when to generate, configure `imageModel` in the [agent config](/docs/protocol/agent-config#image-generation).\n\n## Display Modes\n\nEvery block has a `display` property:\n\n| Mode | Default For | Behavior |\n| ------------- | ------------------------- | ----------------- |\n| `hidden` | add-message | Not shown to user |\n| `name` | set-resource | Shows block name |\n| `description` | tool-call, generate-image | Shows description |\n| `stream` | next-message | Streams content |\n\n## Complete Example\n\n```yaml\nhandlers:\n user-message:\n # Add the user's message to conversation\n Add user message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n display: hidden\n\n # Generate response (LLM may call tools)\n Respond to user:\n block: next-message\n # display: stream (default)\n\n request-human:\n # Step 1: Serialize conversation for summary\n Serialize conversation:\n block: serialize-thread\n format: markdown\n output: CONVERSATION_TEXT\n\n # Step 2: Create separate thread for summarization\n Start summary thread:\n block: start-thread\n thread: summary\n model: anthropic/claude-sonnet-4-5\n thinking: low\n system: escalation-summary\n input: [COMPANY_NAME]\n\n # Step 3: Add request to summary thread\n Add summarize request:\n block: add-message\n thread: summary\n role: user\n prompt: summarize-request\n input:\n - CONVERSATION: CONVERSATION_TEXT\n\n # Step 4: Generate summary\n Generate summary:\n block: next-message\n thread: summary\n display: stream\n description: Summarizing your conversation\n independent: true\n output: SUMMARY\n\n # Step 5: Save to resource\n Save summary:\n block: set-resource\n resource: CONVERSATION_SUMMARY\n value: SUMMARY\n\n # Step 6: Create support ticket\n Create ticket:\n block: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY\n priority: medium\n output: TICKET\n\n # Step 7: Add directive for response\n Add directive:\n block: add-message\n role: user\n prompt: ticket-directive\n input: [TICKET_DETAILS: TICKET]\n visible: false\n\n # Step 8: Respond to user\n Respond:\n block: next-message\n```\n\n## Block Input Mapping\n\nMap variables to block inputs:\n\n```yaml\n# Simple list (variable name = prompt variable)\ninput: [USER_MESSAGE, COMPANY_NAME]\n\n# Mapping (different names)\ninput:\n - CONVERSATION: CONVERSATION_TEXT # CONVERSATION in prompt = CONVERSATION_TEXT\n - TICKET_DETAILS: TICKET\n```\n\n## Independent Blocks\n\nUse `independent: true` for content that shouldn't go to the main chat:\n\n```yaml\nGenerate summary:\n block: next-message\n thread: summary\n independent: true # Output stored in variable, not main chat\n output: SUMMARY\n```\n\nThis is useful for:\n\n- Background processing\n- Summarization in separate threads\n- Generating content for tools\n",
197
+ "excerpt": "Handlers Handlers define what happens when a trigger fires. They contain execution blocks that run in sequence. Handler Structure Each block has a human-readable name (shown in debug UI) and a ...",
198
+ "order": 6
154
199
  },
155
200
  {
156
201
  "slug": "protocol/agent-config",
157
202
  "section": "protocol",
158
203
  "title": "Agent Config",
159
204
  "description": "Configuring the agent model and behavior.",
160
- "content": "\n# Agent Config\n\nThe `agent` section configures the LLM model, system prompt, tools, and behavior.\n\n## Basic Configuration\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system # References prompts/system.md\n tools: [get-user-account] # Available tools\n```\n\n## Configuration Options\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `model` | Yes | Model identifier (provider/model-id) |\n| `system` | Yes | System prompt filename (without .md) |\n| `input` | No | Variables to interpolate in system prompt |\n| `tools` | No | List of tools the LLM can call |\n| `agentic` | No | Allow multiple tool call cycles |\n| `maxSteps` | No | Maximum agentic steps (default: 10) |\n| `temperature` | No | Model temperature (0-2) |\n| `thinking` | No | Extended reasoning level |\n\n## Models\n\nSpecify models in `provider/model-id` format:\n\n```yaml\n# Anthropic\nagent:\n model: anthropic/claude-sonnet-4-5 # Claude 4.5 Sonnet\n model: anthropic/claude-opus-4 # Claude 4 Opus\n\n# OpenAI\nagent:\n model: openai/gpt-4o # GPT-4o\n model: openai/gpt-4o-mini # GPT-4o Mini\n model: openai/o1 # O1\n model: openai/o3-mini # O3 Mini\n```\n\n## System Prompt\n\nThe system prompt sets the agent's persona and instructions:\n\n```yaml\nagent:\n system: system # Uses prompts/system.md\n input:\n - COMPANY_NAME\n - PRODUCT_NAME\n```\n\nExample `prompts/system.md`:\n\n```markdown\nYou are a friendly support agent for {{COMPANY_NAME}}.\n\n## Your Role\n\nHelp users with questions about {{PRODUCT_NAME}}.\n\n## Guidelines\n\n- Be helpful and professional\n- If you can't help, offer to escalate\n- Never share internal information\n```\n\n## Agentic Mode\n\nEnable multi-step tool calling:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [get-user-account, search-docs, create-ticket]\n agentic: true # LLM can call multiple tools\n maxSteps: 10 # Limit cycles to prevent runaway\n```\n\n**How it works:**\n1. LLM receives user message\n2. LLM decides to call a tool\n3. Tool executes, result returned to LLM\n4. LLM decides if more tools needed\n5. Repeat until LLM responds or maxSteps reached\n\n## Extended Thinking\n\nEnable extended reasoning for complex tasks:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n thinking: medium # low | medium | high\n```\n\n| Level | Token Budget | Use Case |\n|-------|-------------|----------|\n| `low` | ~5,000 | Simple reasoning |\n| `medium` | ~10,000 | Moderate complexity |\n| `high` | ~20,000 | Complex analysis |\n\nThinking content streams to the UI and can be displayed to users.\n\n## Temperature\n\nControl response randomness:\n\n```yaml\nagent:\n model: openai/gpt-4o\n temperature: 0.7 # 0 = deterministic, 2 = creative\n```\n\n**Guidelines:**\n- `0 - 0.3`: Factual, consistent responses\n- `0.4 - 0.7`: Balanced (good default)\n- `0.8 - 1.2`: Creative, varied responses\n- `> 1.2`: Very creative (may be inconsistent)\n\n## Thread-Specific Config\n\nOverride config for named threads:\n\n```yaml\nhandlers:\n request-human:\n Start summary thread:\n type: start-thread\n thread: summary\n model: anthropic/claude-sonnet-4-5 # Different model\n thinking: low # Different thinking\n maxSteps: 1 # Limit tool calls\n system: escalation-summary # Different prompt\n```\n\n## Full Example\n\n```yaml\ninput:\n COMPANY_NAME: { type: string }\n PRODUCT_NAME: { type: string }\n USER_ID: { type: string, optional: true }\n\nresources:\n CONVERSATION_SUMMARY:\n type: string\n default: \"\"\n\ntools:\n get-user-account:\n description: Look up user account\n parameters:\n userId: { type: string }\n \n search-docs:\n description: Search help documentation\n parameters:\n query: { type: string }\n \n create-support-ticket:\n description: Create a support ticket\n parameters:\n summary: { type: string }\n priority: { type: string } # low, medium, high\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n input:\n - COMPANY_NAME\n - PRODUCT_NAME\n tools:\n - get-user-account\n - search-docs\n - create-support-ticket\n agentic: true\n maxSteps: 10\n thinking: medium\n\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n\nhandlers:\n user-message:\n Add message:\n type: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n display: hidden\n \n Respond:\n type: next-message\n```\n\n",
161
- "excerpt": "Agent Config The section configures the LLM model, system prompt, tools, and behavior. Basic Configuration Configuration Options | Field | Required | Description |...",
162
- "order": 6
205
+ "content": "\n# Agent Config\n\nThe `agent` section configures the LLM model, system prompt, tools, and behavior.\n\n## Basic Configuration\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system # References prompts/system.md\n tools: [get-user-account] # Available tools\n skills: [qr-code] # Available skills\n```\n\n## Configuration Options\n\n| Field | Required | Description |\n| ------------- | -------- | --------------------------------------------------------- |\n| `model` | Yes | Model identifier (provider/model-id) |\n| `system` | Yes | System prompt filename (without .md) |\n| `input` | No | Variables to interpolate in system prompt |\n| `tools` | No | List of tools the LLM can call |\n| `skills` | No | List of Octavus skills the LLM can use |\n| `imageModel` | No | Image generation model (enables agentic image generation) |\n| `agentic` | No | Allow multiple tool call cycles |\n| `maxSteps` | No | Maximum agentic steps (default: 10) |\n| `temperature` | No | Model temperature (0-2) |\n| `thinking` | No | Extended reasoning level |\n| `anthropic` | No | Anthropic-specific options (tools, skills) |\n\n## Models\n\nSpecify models in `provider/model-id` format. Any model supported by the provider's SDK will work.\n\n### Supported Providers\n\n| Provider | Format | Examples |\n| --------- | ---------------------- | ------------------------------------------------------------ |\n| Anthropic | `anthropic/{model-id}` | `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5` |\n| Google | `google/{model-id}` | `gemini-3-pro-preview`, `gemini-3-flash`, `gemini-2.5-flash` |\n| OpenAI | `openai/{model-id}` | `gpt-5`, `gpt-4o`, `o4-mini`, `o3`, `o3-mini`, `o1` |\n\n### Examples\n\n```yaml\n# Anthropic Claude 4.5\nagent:\n model: anthropic/claude-sonnet-4-5\n\n# Google Gemini 3\nagent:\n model: google/gemini-3-flash\n\n# OpenAI GPT-5\nagent:\n model: openai/gpt-5\n\n# OpenAI reasoning models\nagent:\n model: openai/o3-mini\n```\n\n> **Note**: Model IDs are passed directly to the provider SDK. Check the provider's documentation for the latest available models.\n\n## System Prompt\n\nThe system prompt sets the agent's persona and instructions:\n\n```yaml\nagent:\n system: system # Uses prompts/system.md\n input:\n - COMPANY_NAME\n - PRODUCT_NAME\n```\n\nExample `prompts/system.md`:\n\n```markdown\nYou are a friendly support agent for {{COMPANY_NAME}}.\n\n## Your Role\n\nHelp users with questions about {{PRODUCT_NAME}}.\n\n## Guidelines\n\n- Be helpful and professional\n- If you can't help, offer to escalate\n- Never share internal information\n```\n\n## Agentic Mode\n\nEnable multi-step tool calling:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [get-user-account, search-docs, create-ticket]\n agentic: true # LLM can call multiple tools\n maxSteps: 10 # Limit cycles to prevent runaway\n```\n\n**How it works:**\n\n1. LLM receives user message\n2. LLM decides to call a tool\n3. Tool executes, result returned to LLM\n4. LLM decides if more tools needed\n5. Repeat until LLM responds or maxSteps reached\n\n## Extended Thinking\n\nEnable extended reasoning for complex tasks:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n thinking: medium # low | medium | high\n```\n\n| Level | Token Budget | Use Case |\n| -------- | ------------ | ------------------- |\n| `low` | ~5,000 | Simple reasoning |\n| `medium` | ~10,000 | Moderate complexity |\n| `high` | ~20,000 | Complex analysis |\n\nThinking content streams to the UI and can be displayed to users.\n\n## Skills\n\nEnable Octavus skills for code execution and file generation:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code] # Enable skills\n agentic: true\n```\n\nSkills provide provider-agnostic code execution in isolated sandboxes. When enabled, the LLM can execute Python/Bash code, run skill scripts, and generate files.\n\nSee [Skills](/docs/protocol/skills) for full documentation.\n\n## Image Generation\n\nEnable the LLM to generate images autonomously:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n imageModel: google/gemini-2.5-flash-image\n agentic: true\n```\n\nWhen `imageModel` is configured, the `octavus_generate_image` tool becomes available. The LLM can decide when to generate images based on user requests.\n\n### Supported Image Providers\n\n| Provider | Model Types | Examples |\n| -------- | --------------------------------------- | --------------------------------------------------------- |\n| OpenAI | Dedicated image models | `gpt-image-1` |\n| Google | Gemini native (contains \"image\") | `gemini-2.5-flash-image`, `gemini-3-flash-image-generate` |\n| Google | Imagen dedicated (starts with \"imagen\") | `imagen-4.0-generate-001` |\n\n> **Note**: Google has two image generation approaches. Gemini \"native\" models (containing \"image\" in the ID) generate images using the language model API with `responseModalities`. Imagen models (starting with \"imagen\") use a dedicated image generation API.\n\n### Image Sizes\n\nThe tool supports three image sizes:\n\n- `1024x1024` (default) — Square\n- `1792x1024` — Landscape (16:9)\n- `1024x1792` — Portrait (9:16)\n\n### Agentic vs Deterministic\n\nUse `imageModel` in agent config when:\n\n- The LLM should decide when to generate images\n- Users ask for images in natural language\n\nUse `generate-image` block (see [Handlers](/docs/protocol/handlers#generate-image)) when:\n\n- You want explicit control over image generation\n- Building prompt engineering pipelines\n- Images are generated at specific handler steps\n\n## Temperature\n\nControl response randomness:\n\n```yaml\nagent:\n model: openai/gpt-4o\n temperature: 0.7 # 0 = deterministic, 2 = creative\n```\n\n**Guidelines:**\n\n- `0 - 0.3`: Factual, consistent responses\n- `0.4 - 0.7`: Balanced (good default)\n- `0.8 - 1.2`: Creative, varied responses\n- `> 1.2`: Very creative (may be inconsistent)\n\n## Provider Options\n\nEnable provider-specific features like Anthropic's built-in tools and skills:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n anthropic:\n tools:\n web-search:\n display: description\n description: Searching the web\n skills:\n pdf:\n type: anthropic\n description: Processing PDF\n```\n\nProvider options are validated against the model—using `anthropic:` with a non-Anthropic model will fail validation.\n\nSee [Provider Options](/docs/protocol/provider-options) for full documentation.\n\n## Thread-Specific Config\n\nOverride config for named threads:\n\n```yaml\nhandlers:\n request-human:\n Start summary thread:\n block: start-thread\n thread: summary\n model: anthropic/claude-sonnet-4-5 # Different model\n thinking: low # Different thinking\n maxSteps: 1 # Limit tool calls\n system: escalation-summary # Different prompt\n```\n\n## Full Example\n\n```yaml\ninput:\n COMPANY_NAME: { type: string }\n PRODUCT_NAME: { type: string }\n USER_ID: { type: string, optional: true }\n\nresources:\n CONVERSATION_SUMMARY:\n type: string\n default: ''\n\ntools:\n get-user-account:\n description: Look up user account\n parameters:\n userId: { type: string }\n\n search-docs:\n description: Search help documentation\n parameters:\n query: { type: string }\n\n create-support-ticket:\n description: Create a support ticket\n parameters:\n summary: { type: string }\n priority: { type: string } # low, medium, high\n\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n input:\n - COMPANY_NAME\n - PRODUCT_NAME\n tools:\n - get-user-account\n - search-docs\n - create-support-ticket\n skills: [qr-code] # Octavus skills\n agentic: true\n maxSteps: 10\n thinking: medium\n # Anthropic-specific options\n anthropic:\n tools:\n web-search:\n display: description\n description: Searching the web\n skills:\n pdf:\n type: anthropic\n description: Processing PDF\n\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n\nhandlers:\n user-message:\n Add message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n display: hidden\n\n Respond:\n block: next-message\n```\n",
206
+ "excerpt": "Agent Config The section configures the LLM model, system prompt, tools, and behavior. Basic Configuration Configuration Options | Field | Required | Description ...",
207
+ "order": 7
208
+ },
209
+ {
210
+ "slug": "protocol/provider-options",
211
+ "section": "protocol",
212
+ "title": "Provider Options",
213
+ "description": "Configuring provider-specific tools and features.",
214
+ "content": "\n# Provider Options\n\nProvider options let you enable provider-specific features like Anthropic's built-in tools and skills. These features run server-side on the provider's infrastructure.\n\n> **Note**: For provider-agnostic code execution, use [Octavus Skills](/docs/protocol/skills) instead. Octavus Skills work with any LLM provider and run in isolated sandbox environments.\n\n## Anthropic Options\n\nConfigure Anthropic-specific features when using `anthropic/*` models:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n anthropic:\n # Provider tools (server-side)\n tools:\n web-search:\n display: description\n description: Searching the web\n code-execution:\n display: description\n description: Running code\n\n # Skills (knowledge packages)\n skills:\n pdf:\n type: anthropic\n display: description\n description: Processing PDF document\n```\n\n> **Note**: Provider options are validated against the model provider. Using `anthropic:` options with non-Anthropic models will result in a validation error.\n\n## Provider Tools\n\nProvider tools are executed server-side by the provider (Anthropic). Unlike external tools that you implement, provider tools are built-in capabilities.\n\n### Available Tools\n\n| Tool | Description |\n| ---------------- | -------------------------------------------- |\n| `web-search` | Search the web for current information |\n| `code-execution` | Execute Python/Bash in a sandboxed container |\n\n### Tool Configuration\n\n```yaml\nanthropic:\n tools:\n web-search:\n display: description # How to show in UI\n description: Searching... # Custom display text\n```\n\n| Field | Required | Description |\n| ------------- | -------- | --------------------------------------------------------------------- |\n| `display` | No | `hidden`, `name`, `description`, or `stream` (default: `description`) |\n| `description` | No | Custom text shown to users during execution |\n\n### Web Search\n\nAllows the agent to search the web for current information:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n anthropic:\n tools:\n web-search:\n display: description\n description: Looking up current information\n```\n\nUse cases:\n\n- Current events and news\n- Real-time data (prices, weather)\n- Fact verification\n- Documentation lookups\n\n### Code Execution\n\nEnables Python and Bash execution in a sandboxed container:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n anthropic:\n tools:\n code-execution:\n display: description\n description: Running analysis\n```\n\nUse cases:\n\n- Data analysis and calculations\n- File processing\n- Chart generation\n- Script execution\n\n> **Note**: Code execution is automatically enabled when skills are configured (skills require the container environment).\n\n## Skills\n\n> **Important**: This section covers **Anthropic's built-in skills** (provider-specific). For provider-agnostic skills that work with any LLM, see [Octavus Skills](/docs/protocol/skills).\n\nAnthropic skills are knowledge packages that give the agent specialized capabilities. They're loaded into Anthropic's code execution container at `/skills/{skill-id}/` and only work with Anthropic models.\n\n### Skill Configuration\n\n```yaml\nanthropic:\n skills:\n pdf:\n type: anthropic # 'anthropic' or 'custom'\n version: latest # Optional version\n display: description\n description: Processing PDF\n```\n\n| Field | Required | Description |\n| ------------- | -------- | --------------------------------------------------------------------- |\n| `type` | Yes | `anthropic` (built-in) or `custom` (uploaded) |\n| `version` | No | Skill version (default: `latest`) |\n| `display` | No | `hidden`, `name`, `description`, or `stream` (default: `description`) |\n| `description` | No | Custom text shown to users |\n\n### Built-in Skills\n\nAnthropic provides several built-in skills:\n\n| Skill ID | Purpose |\n| -------- | ----------------------------------------------- |\n| `pdf` | PDF manipulation, text extraction, form filling |\n| `xlsx` | Excel spreadsheet operations and analysis |\n| `docx` | Word document creation and editing |\n| `pptx` | PowerPoint presentation creation |\n\n### Using Skills\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n anthropic:\n skills:\n pdf:\n type: anthropic\n description: Processing your PDF\n xlsx:\n type: anthropic\n description: Analyzing spreadsheet\n```\n\nWhen skills are configured:\n\n1. Code execution is automatically enabled\n2. Skill files are loaded into the container\n3. The agent can read skill instructions and execute scripts\n\n### Custom Skills\n\nYou can create and upload custom skills to Anthropic:\n\n```yaml\nanthropic:\n skills:\n custom-analysis:\n type: custom\n version: latest\n description: Running custom analysis\n```\n\nCustom skills follow the [Agent Skills standard](https://agentskills.io) and contain:\n\n- `SKILL.md` with instructions and metadata\n- Optional `scripts/`, `references/`, and `assets/` directories\n\n### Octavus Skills vs Anthropic Skills\n\n| Feature | Anthropic Skills | Octavus Skills |\n| ----------------- | ------------------------ | ----------------------------- |\n| **Provider** | Anthropic only | Any (agnostic) |\n| **Execution** | Anthropic's container | Isolated sandbox |\n| **Configuration** | `agent.anthropic.skills` | `agent.skills` |\n| **Definition** | `anthropic:` section | `skills:` section |\n| **Use Case** | Claude-specific features | Cross-provider code execution |\n\nFor provider-agnostic code execution, use Octavus Skills defined in the protocol's `skills:` section and enabled via `agent.skills`. See [Skills](/docs/protocol/skills) for details.\n\n## Display Modes\n\nBoth tools and skills support display modes:\n\n| Mode | Behavior |\n| ------------- | ------------------------------- |\n| `hidden` | Not shown to users |\n| `name` | Shows the tool/skill name |\n| `description` | Shows the description (default) |\n| `stream` | Streams progress if available |\n\n## Full Example\n\n```yaml\ninput:\n COMPANY_NAME: { type: string }\n USER_ID: { type: string, optional: true }\n\ntools:\n get-user-account:\n description: Looking up your account\n parameters:\n userId: { type: string }\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n input: [COMPANY_NAME, USER_ID]\n tools: [get-user-account] # External tools\n agentic: true\n thinking: medium\n\n # Anthropic-specific options\n anthropic:\n # Provider tools (server-side)\n tools:\n web-search:\n display: description\n description: Searching the web\n code-execution:\n display: description\n description: Running code\n\n # Skills (knowledge packages)\n skills:\n pdf:\n type: anthropic\n display: description\n description: Processing PDF document\n xlsx:\n type: anthropic\n display: description\n description: Analyzing spreadsheet\n\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n\nhandlers:\n user-message:\n Add message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n display: hidden\n\n Respond:\n block: next-message\n```\n\n## Validation\n\nThe protocol validator enforces:\n\n1. **Model match**: Provider options must match the model provider\n - `anthropic:` options require `anthropic/*` model\n - Using mismatched options results in a validation error\n\n2. **Valid tool types**: Only recognized tools are accepted\n - `web-search` and `code-execution` for Anthropic\n\n3. **Valid skill types**: Only `anthropic` or `custom` are accepted\n\n### Error Example\n\n```yaml\n# This will fail validation\nagent:\n model: openai/gpt-4o # OpenAI model\n anthropic: # Anthropic options - mismatch!\n tools:\n web-search: {}\n```\n\nError: `\"anthropic\" options require an anthropic model. Current model provider: \"openai\"`\n",
215
+ "excerpt": "Provider Options Provider options let you enable provider-specific features like Anthropic's built-in tools and skills. These features run server-side on the provider's infrastructure. > Note: For...",
216
+ "order": 8
217
+ },
218
+ {
219
+ "slug": "protocol/skills-advanced",
220
+ "section": "protocol",
221
+ "title": "Skills Advanced Guide",
222
+ "description": "Best practices and advanced patterns for using Octavus skills.",
223
+ "content": "\n# Skills Advanced Guide\n\nThis guide covers advanced patterns and best practices for using Octavus skills in your agents.\n\n## When to Use Skills\n\nSkills are ideal for:\n\n- **Code execution** - Running Python/Bash scripts\n- **File generation** - Creating images, PDFs, reports\n- **Data processing** - Analyzing, transforming, or visualizing data\n- **Provider-agnostic needs** - Features that should work with any LLM\n\nUse external tools instead when:\n\n- **Simple API calls** - Database queries, external services\n- **Authentication required** - Accessing user-specific resources\n- **Backend integration** - Tight coupling with your infrastructure\n\n## Skill Selection Strategy\n\n### Defining Available Skills\n\nDefine all skills available to this agent in the `skills:` section. Then specify which skills are available for the chat thread in `agent.skills`:\n\n```yaml\n# All skills available to this agent (defined once at protocol level)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n pdf-processor:\n display: description\n description: Processing PDFs\n data-analysis:\n display: description\n description: Analyzing data\n\n# Skills available for this chat thread\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code] # Skills available for this thread\n```\n\n### Match Skills to Use Cases\n\nDefine all skills available to this agent in the `skills:` section. Then specify which skills are available for the chat thread based on use case:\n\n```yaml\n# All skills available to this agent (defined once at protocol level)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data and generating reports\n visualization:\n display: description\n description: Creating charts and visualizations\n\n# Skills available for this chat thread (support use case)\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code] # Skills available for this thread\n```\n\nFor a data analysis thread, you would specify `[data-analysis, visualization]` in `agent.skills`, but still define all available skills in the `skills:` section above.\n\n## Display Mode Strategy\n\nChoose display modes based on user experience:\n\n```yaml\nskills:\n # Background processing - hide from user\n data-analysis:\n display: hidden\n\n # User-facing generation - show description\n qr-code:\n display: description\n\n # Interactive progress - stream updates\n report-generation:\n display: stream\n```\n\n### Guidelines\n\n- **`hidden`**: Background work that doesn't need user awareness\n- **`description`**: User-facing operations (default)\n- **`name`**: Quick operations where name is sufficient\n- **`stream`**: Long-running operations where progress matters\n\n## System Prompt Integration\n\nSkills are automatically injected into the system prompt. The LLM learns:\n\n1. **Available skills** - List of enabled skills with descriptions\n2. **How to use skills** - Instructions for using skill tools\n3. **Tool reference** - Available skill tools (`octavus_skill_read`, `octavus_code_run`, etc.)\n\nYou don't need to manually document skills in your system prompt. However, you can guide the LLM:\n\n```markdown\n<!-- prompts/system.md -->\n\nYou are a helpful assistant that can generate QR codes.\n\n## When to Generate QR Codes\n\nGenerate QR codes when users want to:\n\n- Share URLs easily\n- Provide contact information\n- Share WiFi credentials\n- Create scannable data\n\nUse the qr-code skill for all QR code generation tasks.\n```\n\n## Error Handling\n\nSkills handle errors gracefully:\n\n```yaml\n# Skill execution errors are returned to the LLM\n# The LLM can retry or explain the error to the user\n```\n\nCommon error scenarios:\n\n1. **Invalid skill slug** - Skill not found in organization\n2. **Code execution errors** - Syntax errors, runtime exceptions\n3. **Missing dependencies** - Required packages not installed\n4. **File I/O errors** - Permission issues, invalid paths\n\nThe LLM receives error messages and can:\n\n- Retry with corrected code\n- Explain errors to users\n- Suggest alternatives\n\n## File Output Patterns\n\n### Single File Output\n\n```python\n# Save single file to /output/\nimport qrcode\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\nqr = qrcode.QRCode()\nqr.add_data('https://example.com')\nimg = qr.make_image()\nimg.save(f'{output_dir}/qrcode.png')\n```\n\n### Multiple Files\n\n```python\n# Save multiple files\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n\n# Generate multiple outputs\nfor i in range(3):\n filename = f'{output_dir}/output_{i}.png'\n # ... generate file ...\n```\n\n### Structured Output\n\n```python\n# Save structured data + files\nimport json\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n\n# Save metadata\nmetadata = {\n 'files': ['chart.png', 'data.csv'],\n 'summary': 'Analysis complete'\n}\nwith open(f'{output_dir}/metadata.json', 'w') as f:\n json.dump(metadata, f)\n\n# Save actual files\n# ... generate chart.png and data.csv ...\n```\n\n## Performance Considerations\n\n### Lazy Initialization\n\nSandboxes are created only when a skill tool is first called:\n\n```yaml\n# Sandbox not created until LLM calls a skill tool\nagent:\n skills: [qr-code] # Sandbox created on first use\n```\n\nThis means:\n\n- No cost if skills aren't used\n- Fast startup (no sandbox creation delay)\n- Sandbox reused for all skill calls in a trigger\n\n### Timeout Limits\n\nSandboxes have a 5-minute default timeout:\n\n- **Short operations**: QR codes, simple calculations\n- **Medium operations**: Data analysis, report generation\n- **Long operations**: May need to split into multiple steps\n\n### Sandbox Lifecycle\n\nEach trigger execution gets a fresh sandbox:\n\n- **Clean state** - No leftover files from previous executions\n- **Isolated** - No interference between sessions\n- **Destroyed** - Sandbox cleaned up after trigger completes\n\n## Combining Skills with Tools\n\nSkills and tools can work together:\n\n```yaml\ntools:\n get-user-data:\n description: Fetch user data from database\n parameters:\n userId: { type: string }\n\nskills:\n data-analysis:\n display: description\n description: Analyzing data\n\nagent:\n tools: [get-user-data]\n skills: [data-analysis]\n agentic: true\n\nhandlers:\n analyze-user:\n Get user data:\n block: tool-call\n tool: get-user-data\n input:\n userId: USER_ID\n output: USER_DATA\n\n Analyze:\n block: next-message\n # LLM can use data-analysis skill with USER_DATA\n```\n\nPattern:\n\n1. Fetch data via tool (from your backend)\n2. LLM uses skill to analyze/process the data\n3. Generate outputs (files, reports)\n\n## Skill Development Tips\n\n### Writing SKILL.md\n\nFocus on **when** and **how** to use the skill:\n\n```markdown\n---\nname: qr-code\ndescription: >\n Generate QR codes from text, URLs, or data. Use when the user needs to create\n a QR code for any purpose - sharing links, contact information, WiFi credentials,\n or any text data that should be scannable.\n---\n\n# QR Code Generator\n\n## When to Use\n\nUse this skill when users want to:\n\n- Share URLs easily\n- Provide contact information\n- Create scannable data\n\n## Quick Start\n\n[Clear examples of how to use the skill]\n```\n\n### Script Organization\n\nOrganize scripts logically:\n\n```\nskill-name/\n├── SKILL.md\n└── scripts/\n ├── generate.py # Main script\n ├── utils.py # Helper functions\n └── requirements.txt # Dependencies\n```\n\n### Error Messages\n\nProvide helpful error messages:\n\n```python\ntry:\n # ... code ...\nexcept ValueError as e:\n print(f\"Error: Invalid input - {e}\")\n sys.exit(1)\n```\n\nThe LLM sees these errors and can retry or explain to users.\n\n## Security Considerations\n\n### Sandbox Isolation\n\n- **No network access** (unless explicitly configured)\n- **No persistent storage** (sandbox destroyed after execution)\n- **File output only** via `/output/` directory\n- **Time limits** enforced (5-minute default)\n\n### Input Validation\n\nSkills should validate inputs:\n\n```python\nimport sys\n\nif not data:\n print(\"Error: Data is required\")\n sys.exit(1)\n\nif len(data) > 1000:\n print(\"Error: Data too long (max 1000 characters)\")\n sys.exit(1)\n```\n\n### Resource Limits\n\nBe aware of:\n\n- **File size limits** - Large files may fail to upload\n- **Execution time** - 5-minute sandbox timeout\n- **Memory limits** - Sandbox environment constraints\n\n## Debugging Skills\n\n### Check Skill Documentation\n\nThe LLM can read skill docs:\n\n```python\n# LLM calls octavus_skill_read to see skill instructions\n```\n\n### Test Locally\n\nTest skills before uploading:\n\n```bash\n# Test skill locally\npython scripts/generate.py --data \"test\"\n```\n\n### Monitor Execution\n\nCheck execution logs in the platform debug view:\n\n- Tool calls and arguments\n- Code execution results\n- File outputs\n- Error messages\n\n## Common Patterns\n\n### Pattern 1: Generate and Return\n\n```yaml\n# User asks for QR code\n# LLM generates QR code\n# File automatically available for download\n```\n\n### Pattern 2: Analyze and Report\n\n```yaml\n# User provides data\n# LLM analyzes with skill\n# Generates report file\n# Returns summary + file link\n```\n\n### Pattern 3: Transform and Save\n\n```yaml\n# User uploads file (via tool)\n# LLM processes with skill\n# Generates transformed file\n# Returns new file link\n```\n\n## Best Practices Summary\n\n1. **Enable only needed skills** - Don't overwhelm the LLM\n2. **Choose appropriate display modes** - Match user experience needs\n3. **Write clear skill descriptions** - Help LLM understand when to use\n4. **Handle errors gracefully** - Provide helpful error messages\n5. **Test skills locally** - Verify before uploading\n6. **Monitor execution** - Check logs for issues\n7. **Combine with tools** - Use tools for data, skills for processing\n8. **Consider performance** - Be aware of timeouts and limits\n\n## Next Steps\n\n- [Skills](/docs/protocol/skills) - Basic skills documentation\n- [Agent Config](/docs/protocol/agent-config) - Configuring skills\n- [Tools](/docs/protocol/tools) - External tools integration\n",
224
+ "excerpt": "Skills Advanced Guide This guide covers advanced patterns and best practices for using Octavus skills in your agents. When to Use Skills Skills are ideal for: - Code execution - Running Python/Bash...",
225
+ "order": 9
226
+ },
227
+ {
228
+ "slug": "protocol/types",
229
+ "section": "protocol",
230
+ "title": "Types",
231
+ "description": "Defining custom types for structured data in your agent protocol.",
232
+ "content": "\n# Types\n\nTypes let you define reusable data structures for your agent. Use them in inputs, triggers, tools, resources, variables, and structured output responses.\n\n## Why Types?\n\n- **Reusability** — Define once, use in multiple places\n- **Validation** — Catch errors at protocol validation time\n- **Documentation** — Clear data contracts for your agent\n- **Tool Parameters** — Use complex types in tool parameters\n- **Structured Output** — Get typed JSON responses from the LLM\n\n## Defining Types\n\nTypes are defined in the `types:` section using PascalCase names:\n\n```yaml\ntypes:\n Product:\n id:\n type: string\n description: Unique product identifier\n name:\n type: string\n description: Product display name\n price:\n type: number\n description: Price in cents\n inStock:\n type: boolean\n description: Whether the product is available\n```\n\n## Built-in Types\n\nThese scalar types can be used directly in inputs, resources, variables, triggers, and tool parameters:\n\n| Type | Description | Example Values |\n| --------- | ------------------------------------- | ------------------------------- |\n| `string` | Text values | `\"hello\"`, `\"user@example.com\"` |\n| `number` | Numeric values (integers or decimals) | `42`, `3.14`, `-10` |\n| `integer` | Whole numbers only | `1`, `100`, `-5` |\n| `boolean` | True or false | `true`, `false` |\n| `unknown` | Any value (no type checking) | Any JSON value |\n| `file` | Uploaded file reference | `{ id, mediaType, url, ... }` |\n\nThe `file` type represents an uploaded file (image, document, etc.) with this structure:\n\n```typescript\ninterface FileReference {\n id: string; // Unique file ID\n mediaType: string; // MIME type (e.g., 'image/png')\n url: string; // Presigned download URL\n filename?: string; // Original filename\n size?: number; // File size in bytes\n}\n```\n\n> **Note:** There is no standalone `array` or `object` type. If you need typed arrays or objects, define a [custom type](#defining-types). If you don't care about the internal structure, use `unknown`.\n\n## Array Shorthand\n\nFor simple arrays, use the `type[]` shorthand syntax:\n\n```yaml\ntriggers:\n user-message:\n input:\n USER_MESSAGE:\n type: string\n FILES:\n type: file[] # Array of file references\n optional: true\n\nvariables:\n TAGS:\n type: string[] # Array of strings\n```\n\nThis is equivalent to defining a top-level array type but more concise. Array shorthand works with any built-in type or custom type reference:\n\n| Shorthand | Equivalent To |\n| ---------- | ----------------------------- |\n| `string[]` | Array of strings |\n| `file[]` | Array of file references |\n| `number[]` | Array of numbers |\n| `MyType[]` | Array of custom type `MyType` |\n\n## Property Fields\n\nEach property in a type can have these fields:\n\n| Field | Required | Description |\n| ------------- | -------- | ------------------------------------------------------ |\n| `type` | Yes | The data type (built-in or custom type reference) |\n| `description` | No | Human-readable description |\n| `optional` | No | If `true`, property is not required (default: `false`) |\n| `enum` | No | List of allowed string values |\n| `const` | No | Fixed literal value (for discriminators) |\n\n### Required vs Optional\n\nProperties are **required by default**. Use `optional: true` to make them optional:\n\n```yaml\ntypes:\n UserProfile:\n email:\n type: string\n description: User's email address\n\n phone:\n type: string\n description: User's phone number\n optional: true\n\n nickname:\n type: string\n optional: true\n```\n\n### Descriptions\n\nDescriptions help document your types and guide LLM behavior:\n\n```yaml\ntypes:\n SupportTicket:\n priority:\n type: string\n enum: [low, medium, high, urgent]\n description: >\n Ticket priority level. Use 'urgent' only for critical issues\n affecting multiple users or causing data loss.\n```\n\n## Enums\n\nRestrict string values to a specific set:\n\n```yaml\ntypes:\n OrderStatus:\n status:\n type: string\n enum: [pending, processing, shipped, delivered, cancelled]\n description: Current order status\n\n paymentMethod:\n type: string\n enum: [credit_card, paypal, bank_transfer]\n```\n\n## Arrays\n\nThere are two ways to define arrays:\n\n### Array Properties\n\nDefine array properties within object types using `type: array` and an `items` definition:\n\n```yaml\ntypes:\n ShoppingCart:\n items:\n type: array\n items:\n type: CartItem\n description: Items in the cart\n\n tags:\n type: array\n items:\n type: string\n description: Cart tags for analytics\n\n CartItem:\n productId:\n type: string\n quantity:\n type: integer\n```\n\n### Top-Level Array Types\n\nDefine a named type that IS an array (not an object containing an array):\n\n```yaml\ntypes:\n CartItem:\n productId:\n type: string\n description: Product ID to add to cart\n quantity:\n type: integer\n description: Number of items (1-10)\n\n # Top-level array type - the type IS an array\n CartItemList:\n type: array\n items:\n type: CartItem\n description: List of cart items\n```\n\nTop-level array types are useful when you need to pass arrays as tool parameters without wrapping them in an object.\n\n### Array Guidelines\n\nWhen using arrays in structured output, use descriptions to guide the LLM on expected array sizes:\n\n```yaml\ntypes:\n Survey:\n answers:\n type: array\n items:\n type: string\n description: Survey answers (provide 1-10 responses)\n\n TopPicks:\n recommendations:\n type: array\n items:\n type: Product\n description: Top 3-5 product recommendations\n```\n\n> **Note:** Array length constraints (`minItems`, `maxItems`) are not enforced by LLM providers in structured output. Use descriptive prompts to guide the model.\n\n## Type References\n\nReference other types by their PascalCase name:\n\n```yaml\ntypes:\n Address:\n street:\n type: string\n city:\n type: string\n country:\n type: string\n postalCode:\n type: string\n\n Customer:\n name:\n type: string\n email:\n type: string\n shippingAddress:\n type: Address\n billingAddress:\n type: Address\n optional: true\n```\n\n## Discriminated Unions\n\nCreate types that can be one of several variants using `anyOf`. Each variant must have a discriminator field with a unique `const` value:\n\n```yaml\ntypes:\n PaymentResult:\n anyOf:\n - PaymentSuccess\n - PaymentFailure\n discriminator: status\n\n PaymentSuccess:\n status:\n type: string\n const: success\n transactionId:\n type: string\n description: Unique transaction identifier\n amount:\n type: number\n description: Amount charged in cents\n\n PaymentFailure:\n status:\n type: string\n const: failure\n errorCode:\n type: string\n description: Error code for the failure\n message:\n type: string\n description: Human-readable error message\n```\n\n### Union Requirements\n\n- Use `anyOf` with an array of type names (minimum 2)\n- Specify a `discriminator` field name\n- Each variant must have the discriminator field with a unique `const` value\n\n### Multiple Unions\n\nYou can have multiple discriminated unions:\n\n```yaml\ntypes:\n ApiResponse:\n anyOf:\n - SuccessResponse\n - ErrorResponse\n discriminator: status\n\n SuccessResponse:\n status:\n type: string\n const: success\n data:\n type: unknown\n\n ErrorResponse:\n status:\n type: string\n const: error\n message:\n type: string\n\n UserAction:\n anyOf:\n - ClickAction\n - ScrollAction\n - SubmitAction\n discriminator: type\n\n ClickAction:\n type:\n type: string\n const: click\n elementId:\n type: string\n\n ScrollAction:\n type:\n type: string\n const: scroll\n position:\n type: number\n\n SubmitAction:\n type:\n type: string\n const: submit\n formData:\n type: unknown\n```\n\n## Complete Example\n\nHere's a comprehensive example combining multiple type features:\n\n```yaml\ntypes:\n # Simple object type\n Price:\n amount:\n type: number\n description: Price amount\n currency:\n type: string\n enum: [USD, EUR, GBP]\n description: Currency code\n\n # Type with references and arrays\n Product:\n id:\n type: string\n name:\n type: string\n price:\n type: Price\n category:\n type: string\n enum: [electronics, clothing, home, sports]\n tags:\n type: array\n items:\n type: string\n description: Product tags (up to 10)\n optional: true\n\n # Discriminated union\n SearchResult:\n anyOf:\n - ProductResult\n - CategoryResult\n discriminator: resultType\n\n ProductResult:\n resultType:\n type: string\n const: product\n product:\n type: Product\n relevanceScore:\n type: number\n\n CategoryResult:\n resultType:\n type: string\n const: category\n categoryName:\n type: string\n productCount:\n type: integer\n\ninput:\n STORE_NAME:\n type: string\n\ntriggers:\n user-message:\n input:\n USER_MESSAGE:\n type: string\n\ntools:\n search-products:\n description: Search the product catalog\n parameters:\n query:\n type: string\n category:\n type: string\n optional: true\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [search-products]\n agentic: true\n```\n\n## Using Types in Tools\n\nCustom types can be used in tool parameters. Tool calls are always objects where each parameter name maps to a value.\n\n### Basic Tool Parameters\n\n```yaml\ntools:\n get-product:\n description: Getting product details\n parameters:\n productId:\n type: string\n includeReviews:\n type: boolean\n optional: true\n```\n\nThe LLM calls this with: `{ productId: \"prod-123\", includeReviews: true }`\n\n### Array Parameters\n\nFor array parameters, define a top-level array type and use it as the parameter type:\n\n```yaml\ntypes:\n CartItem:\n productId:\n type: string\n description: Product ID to add to cart\n quantity:\n type: integer\n description: Number of items (1-10)\n giftWrap:\n type: boolean\n description: Whether to gift wrap this item\n optional: true\n\n # Top-level array type - the type IS an array\n CartItemList:\n type: array\n items:\n type: CartItem\n description: List of cart items\n\ntools:\n add-to-cart:\n description: Adding products to cart\n display: description\n parameters:\n cartItems:\n type: CartItemList\n description: Items to add to the cart\n```\n\nThe tool receives: `{ cartItems: [{ productId: \"...\", quantity: 1 }, ...] }`\n\n### Why Use Named Array Types?\n\nNamed array types provide:\n\n- **Reusability** — Use the same array type in multiple tools\n- **Clear schema** — The array structure is validated\n- **Clean tool calls** — No unnecessary wrapper objects\n\n## Structured Output\n\nUse `responseType` on a `next-message` block to get structured JSON responses instead of plain text.\n\n### Basic Example\n\n```yaml\ntypes:\n ChatResponse:\n content:\n type: string\n description: The main response text to the user\n suggestions:\n type: array\n items:\n type: string\n description: 1-3 follow-up suggestions (empty array if none)\n\nvariables:\n RESPONSE:\n type: ChatResponse\n\nhandlers:\n user-message:\n Respond to user:\n block: next-message\n responseType: ChatResponse\n output: RESPONSE\n```\n\n### Discriminated Unions for Response Variants\n\nWhen you need different response formats based on context, use a discriminated union **wrapped in an object**. LLM providers don't allow `anyOf` (discriminated unions) at the schema root, so you must wrap them.\n\n```yaml\ntypes:\n # ✅ Wrapper object (required - responseType must be an object, not a union)\n ChatResponseWrapper:\n response:\n type: ChatResponseUnion\n description: The response variant\n\n # Discriminated union with 3 variants\n ChatResponseUnion:\n anyOf:\n - ContentOnlyResponse\n - ContentWithSuggestionsResponse\n - ContentWithProductsResponse\n discriminator: responseType\n\n ContentOnlyResponse:\n responseType:\n type: string\n const: content\n content:\n type: string\n\n ContentWithSuggestionsResponse:\n responseType:\n type: string\n const: content_with_suggestions\n content:\n type: string\n suggestions:\n type: array\n items:\n type: string\n\n ContentWithProductsResponse:\n responseType:\n type: string\n const: content_with_products\n content:\n type: string\n recommendedProducts:\n type: array\n items:\n type: ProductSummary\n\nhandlers:\n user-message:\n Respond to user:\n block: next-message\n responseType: ChatResponseWrapper # Use the wrapper, not the union directly\n```\n\nThe client receives an object like `{ response: { responseType: \"content_with_suggestions\", content: \"...\", suggestions: [...] } }`.\n\n### Response Type Requirements\n\nThe `responseType` must be an **object type** (regular custom type with properties).\n\nThe following cannot be used directly as `responseType`:\n\n- **Discriminated unions** — LLM providers don't allow `anyOf` at the schema root ([OpenAI docs](https://platform.openai.com/docs/guides/structured-outputs#root-objects-must-not-be-anyof-and-must-be-an-object))\n- **Array types** — Must be wrapped in an object\n- **Primitives** — `string`, `number`, etc. are not valid\n\n```yaml\ntypes:\n # ❌ Cannot use discriminated union directly as responseType\n ChatResponseUnion:\n anyOf: [ContentResponse, ProductResponse]\n discriminator: type\n\n # ✅ Wrap the union in an object\n ChatResponseWrapper:\n response:\n type: ChatResponseUnion\n\n # ❌ Cannot use array type as responseType\n ProductList:\n type: array\n items:\n type: Product\n\n # ✅ Wrap the array in an object\n ProductListResponse:\n products:\n type: array\n items:\n type: Product\n description: List of products\n```\n\n### How It Works\n\n1. The LLM generates a structured JSON response matching the type schema\n2. The response is validated against the schema\n3. The parsed object is stored in the `output` variable (if specified)\n4. The client SDK receives an `object` part instead of a `text` part\n\n### Client-Side Rendering\n\nWhen `responseType` is set, the client SDK receives a `UIObjectPart` that can be rendered with custom UI. See the [Structured Output](/docs/client-sdk/structured-output) guide for details on building custom renderers.\n\n### Best Practices\n\n**Use descriptions to guide the LLM:**\n\n```yaml\ntypes:\n ChatResponse:\n content:\n type: string\n description: >\n The main response to the user. Use markdown formatting\n for lists and code blocks when appropriate.\n suggestions:\n type: array\n items:\n type: string\n description: >\n 2-3 natural follow-up questions the user might ask.\n Return an empty array if no suggestions are relevant.\n```\n\n**Keep types focused:**\n\nCreate separate types for different response formats rather than one complex type with many optional fields. Use discriminated unions when the response can be one of several distinct variants.\n\n**Handle streaming gracefully:**\n\nThe client receives partial objects during streaming. Design your UI to handle incomplete data (e.g., show skeleton loaders for missing fields).\n\n## Naming Conventions\n\n| Element | Convention | Examples |\n| -------------- | --------------------------------- | --------------------------------------- |\n| Type names | PascalCase | `Product`, `UserProfile`, `OrderStatus` |\n| Property names | camelCase | `firstName`, `orderId`, `isActive` |\n| Enum values | lowercase_snake_case or camelCase | `in_stock`, `pending`, `creditCard` |\n\n## Validation\n\nTypes are validated when the protocol is loaded:\n\n- Type names must be PascalCase\n- Referenced types must exist\n- Circular references are not allowed\n- Union variants must have unique discriminator values\n- Arrays with `type: array` must have an `items` definition\n\n## Limitations\n\n### Type Definition Limits\n\n- **No standalone `array` or `object`** — Define a custom type instead, or use `unknown` for untyped data\n- **No recursive types** — A type cannot reference itself (directly or indirectly)\n- **No generic types** — Types are concrete, not parameterized\n- **String enums only** — `enum` values must be strings\n- **No array constraints** — `minItems` and `maxItems` are not supported (LLM providers don't enforce them)\n\n### Tool Limitations\n\n- **Tool parameters are always objects** — Each tool call is `{ param1: value1, param2: value2, ... }`\n- **Array parameters need named types** — Use top-level array types for array parameters\n\n### Structured Output Limitations\n\n- **responseType must be an object type** — Only object types can be used as responseType\n- **Discriminated unions need object wrapper** — Unions (`anyOf`) are not allowed at the schema root\n- **Array types need object wrapper** — Arrays cannot be used directly as responseType\n- **Primitives are not allowed** — `string`, `number`, etc. cannot be used as responseType\n\nThese limitations exist because LLM providers (OpenAI, Anthropic) require the root schema to be an object:\n\n- [OpenAI: Root objects must not be anyOf](https://platform.openai.com/docs/guides/structured-outputs#root-objects-must-not-be-anyof-and-must-be-an-object)\n- JSON Schema validation works best with explicit object structures at the root\n",
233
+ "excerpt": "Types Types let you define reusable data structures for your agent. Use them in inputs, triggers, tools, resources, variables, and structured output responses. Why Types? - Reusability — Define...",
234
+ "order": 10
163
235
  },
164
236
  {
165
237
  "slug": "api-reference/overview",
166
238
  "section": "api-reference",
167
239
  "title": "Overview",
168
240
  "description": "REST API overview and authentication.",
169
- "content": "\n# API Reference\n\nThe Octavus API is a RESTful API that enables programmatic access to agent management and session execution.\n\n## Base URL\n\n```\nhttps://octavus.ai\n```\n\n## Authentication\n\nAll API requests require authentication using a Bearer token:\n\n```bash\ncurl -H \"Authorization: Bearer YOUR_API_KEY\" \\\n https://octavus.ai/api/agents\n```\n\nAPI keys can be created in the Octavus Platform under your project's **API Keys** page.\n\n## Response Format\n\nAll responses are JSON. Success responses return the data directly (not wrapped in a `data` field).\n\n### Success Response\n\n```json\n{\n \"sessionId\": \"sess_abc123\"\n}\n```\n\n### Error Response\n\n```json\n{\n \"error\": {\n \"code\": \"NOT_FOUND\",\n \"message\": \"Agent not found\"\n }\n}\n```\n\n## HTTP Status Codes\n\n| Code | Description |\n|------|-------------|\n| 200 | Success |\n| 201 | Created |\n| 400 | Bad Request - Invalid parameters |\n| 401 | Unauthorized - Missing or invalid API key |\n| 403 | Forbidden - Insufficient permissions |\n| 404 | Not Found |\n| 500 | Internal Server Error |\n\n## Endpoints Overview\n\n### Agents\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/agents` | List all agents |\n| GET | `/api/agents/:id` | Get agent by ID |\n| POST | `/api/agents` | Create agent |\n| PATCH | `/api/agents/:id` | Update agent |\n\n### Sessions\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| POST | `/api/agent-sessions` | Create session |\n| GET | `/api/agent-sessions/:id` | Get session state |\n| POST | `/api/agent-sessions/:id/trigger` | Execute trigger (SSE) |\n\n## Streaming\n\nThe trigger endpoint returns Server-Sent Events (SSE):\n\n```bash\ncurl -N -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"triggerName\": \"user-message\", \"input\": {\"USER_MESSAGE\": \"Hello\"}}' \\\n https://octavus.ai/api/agent-sessions/SESSION_ID/trigger\n```\n\nResponse format:\n\n```\ndata: {\"type\":\"start\",\"messageId\":\"...\"}\n\ndata: {\"type\":\"block-start\",\"blockId\":\"...\",\"blockName\":\"Respond\",\"blockType\":\"next-message\",\"display\":\"stream\"}\n\ndata: {\"type\":\"text-start\",\"id\":\"...\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"...\",\"delta\":\"Hello\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"...\",\"delta\":\"!\"}\n\ndata: {\"type\":\"text-end\",\"id\":\"...\"}\n\ndata: {\"type\":\"block-end\",\"blockId\":\"...\"}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"stop\"}\n\ndata: [DONE]\n```\n\n## SDKs\n\nWe recommend using our SDKs instead of calling the API directly:\n\n- **Server SDK**: `@octavus/server-sdk` - For Node.js backends\n- **React SDK**: `@octavus/react` - For React applications\n- **Client SDK**: `@octavus/client-sdk` - For other frontend frameworks\n\nThe SDKs handle authentication, streaming, and tool execution automatically.\n\n",
241
+ "content": "\n# API Reference\n\nThe Octavus API is a RESTful API that enables programmatic access to agent management and session execution.\n\n## Base URL\n\n```\nhttps://octavus.ai\n```\n\n## Authentication\n\nAll API requests require authentication using a Bearer token:\n\n```bash\ncurl -H \"Authorization: Bearer YOUR_API_KEY\" \\\n https://octavus.ai/api/agents\n```\n\nAPI keys can be created in the Octavus Platform under your project's **API Keys** page.\n\n## Response Format\n\nAll responses are JSON. Success responses return the data directly (not wrapped in a `data` field).\n\n### Success Response\n\n```json\n{\n \"sessionId\": \"sess_abc123\"\n}\n```\n\n### Error Response\n\n```json\n{\n \"error\": {\n \"code\": \"NOT_FOUND\",\n \"message\": \"Agent not found\"\n }\n}\n```\n\n## HTTP Status Codes\n\n| Code | Description |\n| ---- | ----------------------------------------- |\n| 200 | Success |\n| 201 | Created |\n| 400 | Bad Request - Invalid parameters |\n| 401 | Unauthorized - Missing or invalid API key |\n| 403 | Forbidden - Insufficient permissions |\n| 404 | Not Found |\n| 500 | Internal Server Error |\n\n## Endpoints Overview\n\n### Agents\n\n| Method | Endpoint | Description |\n| ------ | ----------------- | --------------- |\n| GET | `/api/agents` | List all agents |\n| GET | `/api/agents/:id` | Get agent by ID |\n| POST | `/api/agents` | Create agent |\n| PATCH | `/api/agents/:id` | Update agent |\n\n### Sessions\n\n| Method | Endpoint | Description |\n| ------ | --------------------------------- | --------------------- |\n| POST | `/api/agent-sessions` | Create session |\n| GET | `/api/agent-sessions/:id` | Get session state |\n| POST | `/api/agent-sessions/:id/trigger` | Execute trigger (SSE) |\n\n## Streaming\n\nThe trigger endpoint returns Server-Sent Events (SSE):\n\n```bash\ncurl -N -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"triggerName\": \"user-message\", \"input\": {\"USER_MESSAGE\": \"Hello\"}}' \\\n https://octavus.ai/api/agent-sessions/SESSION_ID/trigger\n```\n\nResponse format:\n\n```\ndata: {\"type\":\"start\",\"messageId\":\"...\"}\n\ndata: {\"type\":\"block-start\",\"blockId\":\"...\",\"blockName\":\"Respond\",\"blockType\":\"next-message\",\"display\":\"stream\"}\n\ndata: {\"type\":\"text-start\",\"id\":\"...\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"...\",\"delta\":\"Hello\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"...\",\"delta\":\"!\"}\n\ndata: {\"type\":\"text-end\",\"id\":\"...\"}\n\ndata: {\"type\":\"block-end\",\"blockId\":\"...\"}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"stop\"}\n\ndata: [DONE]\n```\n\n## SDKs\n\nWe recommend using our SDKs instead of calling the API directly:\n\n- **Server SDK**: `@octavus/server-sdk` - For Node.js backends\n- **React SDK**: `@octavus/react` - For React applications\n- **Client SDK**: `@octavus/client-sdk` - For other frontend frameworks\n\nThe SDKs handle authentication, streaming, and tool execution automatically.\n",
170
242
  "excerpt": "API Reference The Octavus API is a RESTful API that enables programmatic access to agent management and session execution. Base URL Authentication All API requests require authentication using a...",
171
243
  "order": 1
172
244
  },
@@ -175,8 +247,8 @@
175
247
  "section": "api-reference",
176
248
  "title": "Sessions",
177
249
  "description": "Session management API endpoints.",
178
- "content": "\n# Sessions API\n\nSessions represent conversations with agents. They store conversation history, resources, and variables.\n\n## Create Session\n\nCreate a new agent session.\n\n```\nPOST /api/agent-sessions\n```\n\n### Request Body\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\",\n \"USER_ID\": \"user-123\"\n }\n}\n```\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| `agentId` | string | Yes | Agent ID (the `id` field, not `slug`) |\n| `input` | object | No | Input variables for the agent |\n\n> To get the agent ID, copy it from the platform URL, use [Get Agent by slug](/docs/api-reference/agents#get-agent) (`GET /api/agents/:slug?by=slug`), or the SDK's `agents.getBySlug()` method.\n\n### Response\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\"\n}\n```\n\n### Example\n\n```bash\ncurl -X POST https://octavus.ai/api/agent-sessions \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\"\n }\n }'\n```\n\n## Get Session\n\nRetrieve session state including UI-ready messages and resources.\n\n```\nGET /api/agent-sessions/:sessionId\n```\n\n### Response\n\nThe response includes `UIMessage` objects that can be passed directly to the client SDK's `initialMessages` option:\n\n```json\n{\n \"id\": \"cm5xyz123abc456def\",\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\"\n },\n \"variables\": {},\n \"resources\": {\n \"CONVERSATION_SUMMARY\": \"\"\n },\n \"messages\": [\n {\n \"id\": \"1702345800000-xyz789a\",\n \"role\": \"user\",\n \"parts\": [\n { \"type\": \"text\", \"text\": \"How do I reset my password?\", \"status\": \"done\" }\n ],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:00.000Z\"\n },\n {\n \"id\": \"1702345805000-def456b\",\n \"role\": \"assistant\",\n \"parts\": [\n { \"type\": \"text\", \"text\": \"I can help you reset your password...\", \"status\": \"done\" }\n ],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:05.000Z\"\n }\n ],\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"updatedAt\": \"2024-01-15T10:30:05Z\"\n}\n```\n\n### UIMessage Parts\n\nMessages contain typed `parts` that preserve content ordering:\n\n| Part Type | Description |\n|-----------|-------------|\n| `text` | Text content with `text` and `status` fields |\n| `reasoning` | Extended reasoning with `text` and `status` fields |\n| `tool-call` | Tool execution with `toolCallId`, `toolName`, `displayName`, `args`, `result`, `status` |\n| `operation` | Internal operations with `operationId`, `name`, `operationType`, `status` |\n\n### Example\n\n```bash\ncurl https://octavus.ai/api/agent-sessions/:sessionId \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n\n## Trigger Session\n\nExecute a trigger on a session. Returns a Server-Sent Events stream.\n\n```\nPOST /api/agent-sessions/:sessionId/trigger\n```\n\n### Request Body\n\n```json\n{\n \"triggerName\": \"user-message\",\n \"input\": {\n \"USER_MESSAGE\": \"How do I reset my password?\"\n },\n \"toolResults\": []\n}\n```\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| `triggerName` | string | Yes | Name of the trigger to execute |\n| `input` | object | No | Input variables for the trigger |\n| `toolResults` | array | No | Tool results for continuation (handled by SDK) |\n\n### Response\n\nReturns `text/event-stream` with SSE events:\n\n```\ndata: {\"type\":\"start\",\"messageId\":\"msg-123\"}\n\ndata: {\"type\":\"block-start\",\"blockId\":\"b1\",\"blockName\":\"Add user message\",\"blockType\":\"add-message\",\"display\":\"hidden\"}\n\ndata: {\"type\":\"block-end\",\"blockId\":\"b1\"}\n\ndata: {\"type\":\"block-start\",\"blockId\":\"b2\",\"blockName\":\"Respond to user\",\"blockType\":\"next-message\",\"display\":\"stream\",\"outputToChat\":true}\n\ndata: {\"type\":\"text-start\",\"id\":\"t1\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\"I\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" can\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" help\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" you\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" reset\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" your\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" password\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\"!\"}\n\ndata: {\"type\":\"text-end\",\"id\":\"t1\"}\n\ndata: {\"type\":\"block-end\",\"blockId\":\"b2\"}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"stop\"}\n\ndata: [DONE]\n```\n\n### Event Types\n\n| Event | Description |\n|-------|-------------|\n| `start` | Stream started |\n| `finish` | Execution complete |\n| `error` | Error occurred |\n| `block-start` | Execution block started |\n| `block-end` | Execution block completed |\n| `text-start` | Text generation started |\n| `text-delta` | Incremental text content |\n| `text-end` | Text generation ended |\n| `reasoning-start` | Extended reasoning started |\n| `reasoning-delta` | Reasoning content |\n| `reasoning-end` | Extended reasoning ended |\n| `tool-input-start` | Tool call initiated |\n| `tool-input-delta` | Tool arguments streaming |\n| `tool-input-end` | Tool arguments streaming ended |\n| `tool-input-available` | Tool input complete |\n| `tool-output-available` | Tool completed with result |\n| `tool-output-error` | Tool failed |\n| `tool-request` | Platform requesting tool execution |\n| `resource-update` | Resource value changed |\n\n### Example\n\n```bash\ncurl -N -X POST https://octavus.ai/api/agent-sessions/:sessionId/trigger \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"triggerName\": \"user-message\",\n \"input\": { \"USER_MESSAGE\": \"How do I reset my password?\" }\n }'\n```\n\n## Tool Continuation\n\nWhen the agent calls external tools, you'll receive a `tool-request` event. Execute the tools and send results back:\n\n```json\n{\n \"triggerName\": \"user-message\",\n \"input\": { \"USER_MESSAGE\": \"...\" },\n \"toolResults\": [\n {\n \"toolCallId\": \"tc_123\",\n \"toolName\": \"get-user-account\",\n \"result\": {\n \"name\": \"Demo User\",\n \"email\": \"demo@example.com\"\n }\n }\n ]\n}\n```\n\nThe Server SDK handles this continuation pattern automatically.\n",
179
- "excerpt": "Sessions API Sessions represent conversations with agents. They store conversation history, resources, and variables. Create Session Create a new agent session. Request Body | Field | Type |...",
250
+ "content": "\n# Sessions API\n\nSessions represent conversations with agents. They store conversation history, resources, and variables.\n\n## Create Session\n\nCreate a new agent session.\n\n```\nPOST /api/agent-sessions\n```\n\n### Request Body\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\",\n \"USER_ID\": \"user-123\"\n }\n}\n```\n\n| Field | Type | Required | Description |\n| --------- | ------ | -------- | ------------------------------------- |\n| `agentId` | string | Yes | Agent ID (the `id` field, not `slug`) |\n| `input` | object | No | Input variables for the agent |\n\n> **Getting the agent ID:** Copy the ID from the agent URL in the [platform](https://octavus.ai) (e.g., `octavus.ai/agents/clxyz123`), or use the [CLI](/docs/server-sdk/cli) (`octavus sync ./agents/my-agent`) for local development workflows.\n\n### Response\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\"\n}\n```\n\n### Example\n\n```bash\ncurl -X POST https://octavus.ai/api/agent-sessions \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\"\n }\n }'\n```\n\n## Get Session\n\nRetrieve session state. Returns UI-ready messages for active sessions, or expiration info for expired sessions.\n\n```\nGET /api/agent-sessions/:sessionId\n```\n\n### Query Parameters\n\n| Parameter | Type | Description |\n| --------- | ------ | ---------------------------------------------------- |\n| `format` | string | Optional. Use `format=ui` for UI-ready messages only |\n\n### Response (Active Session)\n\nWhen the session is active, the response includes `UIMessage` objects:\n\n```json\n{\n \"id\": \"cm5xyz123abc456def\",\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"status\": \"active\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\"\n },\n \"variables\": {},\n \"resources\": {\n \"CONVERSATION_SUMMARY\": \"\"\n },\n \"messages\": [\n {\n \"id\": \"1702345800000-xyz789a\",\n \"role\": \"user\",\n \"parts\": [{ \"type\": \"text\", \"text\": \"How do I reset my password?\", \"status\": \"done\" }],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:00.000Z\"\n },\n {\n \"id\": \"1702345805000-def456b\",\n \"role\": \"assistant\",\n \"parts\": [\n { \"type\": \"text\", \"text\": \"I can help you reset your password...\", \"status\": \"done\" }\n ],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:05.000Z\"\n }\n ],\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"updatedAt\": \"2024-01-15T10:30:05Z\"\n}\n```\n\n### Response (Expired Session)\n\nWhen the session has expired, the response indicates the expiration status:\n\n```json\n{\n \"status\": \"expired\",\n \"sessionId\": \"cm5xyz123abc456def\",\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"createdAt\": \"2024-01-15T10:30:00Z\"\n}\n```\n\nUse the [Restore Session](#restore-session) endpoint to restore an expired session from stored messages.\n\n````\n\n### UIMessage Parts\n\nMessages contain typed `parts` that preserve content ordering:\n\n| Part Type | Description |\n|-----------|-------------|\n| `text` | Text content with `text` and `status` fields |\n| `reasoning` | Extended reasoning with `text` and `status` fields |\n| `tool-call` | Tool execution with `toolCallId`, `toolName`, `displayName`, `args`, `result`, `status` |\n| `operation` | Internal operations with `operationId`, `name`, `operationType`, `status` |\n| `file` | File attachment with `id`, `mediaType`, `url`, `filename`, `size` |\n| `source` | Source reference with `sourceType`, `id`, `url`, `title` |\n| `object` | Structured output with `id`, `typeName`, `object`, `status` |\n\n### Example\n\n```bash\ncurl https://octavus.ai/api/agent-sessions/:sessionId \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n````\n\n## Restore Session\n\nRestore an expired session from stored messages. This allows you to continue a conversation after the server-side state has expired.\n\n```\nPOST /api/agent-sessions/:sessionId/restore\n```\n\n### Request Body\n\n```json\n{\n \"messages\": [\n {\n \"id\": \"1702345800000-xyz789a\",\n \"role\": \"user\",\n \"parts\": [{ \"type\": \"text\", \"text\": \"How do I reset my password?\", \"status\": \"done\" }],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:00.000Z\"\n }\n ],\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\"\n }\n}\n```\n\n| Field | Type | Required | Description |\n| ---------- | ----------- | -------- | -------------------------------------------------------------- |\n| `messages` | UIMessage[] | Yes | Previously stored chat history |\n| `input` | object | No | Session input for system prompt interpolation (same as create) |\n\n### Response\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\",\n \"restored\": true\n}\n```\n\n| Field | Type | Description |\n| ----------- | ------- | ----------------------------------------------------------------------- |\n| `sessionId` | string | The session ID |\n| `restored` | boolean | `true` if restored from messages, `false` if session was already active |\n\n### Example\n\n```bash\ncurl -X POST https://octavus.ai/api/agent-sessions/:sessionId/restore \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"messages\": [...],\n \"input\": { \"COMPANY_NAME\": \"Acme Corp\" }\n }'\n```\n\n> **Note**: Store the `UIMessage[]` array after each interaction to enable restoration. The restore endpoint reconstructs the conversation state from these messages.\n\n## Trigger Session\n\nExecute a trigger on a session. Returns a Server-Sent Events stream.\n\n```\nPOST /api/agent-sessions/:sessionId/trigger\n```\n\n### Request Body\n\n```json\n{\n \"triggerName\": \"user-message\",\n \"input\": {\n \"USER_MESSAGE\": \"How do I reset my password?\"\n },\n \"toolResults\": []\n}\n```\n\n| Field | Type | Required | Description |\n| ------------- | ------ | -------- | ---------------------------------------------- |\n| `triggerName` | string | Yes | Name of the trigger to execute |\n| `input` | object | No | Input variables for the trigger |\n| `toolResults` | array | No | Tool results for continuation (handled by SDK) |\n\n### Response\n\nReturns `text/event-stream` with SSE events:\n\n```\ndata: {\"type\":\"start\",\"messageId\":\"msg-123\"}\n\ndata: {\"type\":\"block-start\",\"blockId\":\"b1\",\"blockName\":\"Add user message\",\"blockType\":\"add-message\",\"display\":\"hidden\"}\n\ndata: {\"type\":\"block-end\",\"blockId\":\"b1\"}\n\ndata: {\"type\":\"block-start\",\"blockId\":\"b2\",\"blockName\":\"Respond to user\",\"blockType\":\"next-message\",\"display\":\"stream\",\"outputToChat\":true}\n\ndata: {\"type\":\"text-start\",\"id\":\"t1\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\"I\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" can\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" help\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" you\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" reset\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" your\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" password\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\"!\"}\n\ndata: {\"type\":\"text-end\",\"id\":\"t1\"}\n\ndata: {\"type\":\"block-end\",\"blockId\":\"b2\"}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"stop\"}\n\ndata: [DONE]\n```\n\n### Event Types\n\n| Event | Description |\n| ----------------------- | ---------------------------------- |\n| `start` | Stream started |\n| `finish` | Execution complete |\n| `error` | Error occurred |\n| `block-start` | Execution block started |\n| `block-end` | Execution block completed |\n| `text-start` | Text generation started |\n| `text-delta` | Incremental text content |\n| `text-end` | Text generation ended |\n| `reasoning-start` | Extended reasoning started |\n| `reasoning-delta` | Reasoning content |\n| `reasoning-end` | Extended reasoning ended |\n| `tool-input-start` | Tool call initiated |\n| `tool-input-delta` | Tool arguments streaming |\n| `tool-input-end` | Tool arguments streaming ended |\n| `tool-input-available` | Tool input complete |\n| `tool-output-available` | Tool completed with result |\n| `tool-output-error` | Tool failed |\n| `tool-request` | Platform requesting tool execution |\n| `file-available` | File ready for display/download |\n| `resource-update` | Resource value changed |\n\n### Example\n\n```bash\ncurl -N -X POST https://octavus.ai/api/agent-sessions/:sessionId/trigger \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"triggerName\": \"user-message\",\n \"input\": { \"USER_MESSAGE\": \"How do I reset my password?\" }\n }'\n```\n\n## Tool Continuation\n\nWhen the agent calls external tools, you'll receive a `tool-request` event. Execute the tools and send results back:\n\n```json\n{\n \"triggerName\": \"user-message\",\n \"input\": { \"USER_MESSAGE\": \"...\" },\n \"toolResults\": [\n {\n \"toolCallId\": \"tc_123\",\n \"toolName\": \"get-user-account\",\n \"result\": {\n \"name\": \"Demo User\",\n \"email\": \"demo@example.com\"\n }\n }\n ]\n}\n```\n\nThe Server SDK handles this continuation pattern automatically.\n\n## Upload URLs\n\nGet presigned URLs for file uploads. Files are uploaded directly to S3.\n\n```\nPOST /api/files/upload-urls\n```\n\n### Request Body\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\",\n \"files\": [\n {\n \"filename\": \"photo.jpg\",\n \"mediaType\": \"image/jpeg\",\n \"size\": 102400\n }\n ]\n}\n```\n\n| Field | Type | Required | Description |\n| ------------------- | ------ | -------- | ----------------------------------- |\n| `sessionId` | string | Yes | Session ID to associate files with |\n| `files` | array | Yes | Array of file metadata (1-20 files) |\n| `files[].filename` | string | Yes | Original filename |\n| `files[].mediaType` | string | Yes | MIME type (e.g., `image/png`) |\n| `files[].size` | number | Yes | File size in bytes |\n\n### Response\n\n```json\n{\n \"files\": [\n {\n \"id\": \"file-abc123\",\n \"uploadUrl\": \"https://s3.amazonaws.com/bucket/key?...\",\n \"downloadUrl\": \"https://s3.amazonaws.com/bucket/key?...\"\n }\n ]\n}\n```\n\n### Upload Flow\n\n1. Request upload URLs from the platform\n2. PUT file content to `uploadUrl` with `Content-Type` header\n3. Use `downloadUrl` as the `url` in `FileReference`\n4. Include `FileReference` in trigger input\n\n### Supported Types\n\n| Category | Media Types |\n| --------- | -------------------------------------------------------------------- |\n| Images | `image/jpeg`, `image/png`, `image/gif`, `image/webp` |\n| Documents | `application/pdf`, `text/plain`, `text/markdown`, `application/json` |\n\n### Limits\n\n| Limit | Value |\n| --------------------- | ---------- |\n| Max file size | 10 MB |\n| Max total per request | 50 MB |\n| Max files per request | 20 |\n| Upload URL expiry | 15 minutes |\n| Download URL expiry | 24 hours |\n",
251
+ "excerpt": "Sessions API Sessions represent conversations with agents. They store conversation history, resources, and variables. Create Session Create a new agent session. Request Body | Field | Type |...",
180
252
  "order": 2
181
253
  },
182
254
  {
@@ -184,8 +256,8 @@
184
256
  "section": "api-reference",
185
257
  "title": "Agents",
186
258
  "description": "Agent management API endpoints.",
187
- "content": "\n# Agents API\n\nManage agent definitions including protocols and prompts.\n\n## List Agents\n\nGet all agents in the project.\n\n```\nGET /api/agents\n```\n\n### Response\n\n```json\n{\n \"agents\": [\n {\n \"id\": \"cm5xvz7k80001abcd\",\n \"slug\": \"support-chat\",\n \"name\": \"Support Chat\",\n \"description\": \"Customer support agent\",\n \"format\": \"interactive\",\n \"createdAt\": \"2024-01-10T08:00:00Z\",\n \"updatedAt\": \"2024-01-15T10:00:00Z\"\n }\n ]\n}\n```\n\n### Example\n\n```bash\ncurl https://octavus.ai/api/agents \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n\n## Get Agent\n\nGet a single agent by ID or slug.\n\n```\nGET /api/agents/:id\nGET /api/agents/:slug?by=slug\n```\n\n### Response\n\n```json\n{\n \"id\": \"cm5xvz7k80001abcd\",\n \"settings\": {\n \"slug\": \"support-chat\",\n \"name\": \"Support Chat\",\n \"description\": \"Customer support agent\",\n \"format\": \"interactive\"\n },\n \"protocol\": \"input:\\n COMPANY_NAME: { type: string }\\n...\",\n \"prompts\": [\n {\n \"name\": \"system\",\n \"content\": \"You are a support agent for {{COMPANY_NAME}}...\"\n },\n {\n \"name\": \"user-message\",\n \"content\": \"{{USER_MESSAGE}}\"\n }\n ]\n}\n```\n\n### Example\n\n```bash\n# By ID\ncurl https://octavus.ai/api/agents/:agentId \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n\n# By slug\ncurl \"https://octavus.ai/api/agents/:slug?by=slug\" \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n\n## Create Agent\n\nCreate a new agent.\n\n```\nPOST /api/agents\n```\n\n### Request Body\n\n```json\n{\n \"settings\": {\n \"slug\": \"support-chat\",\n \"name\": \"Support Chat\",\n \"description\": \"Customer support agent\",\n \"format\": \"interactive\"\n },\n \"protocol\": \"input:\\n COMPANY_NAME: { type: string }\\n...\",\n \"prompts\": [\n {\n \"name\": \"system\",\n \"content\": \"You are a support agent...\"\n }\n ]\n}\n```\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| `settings.slug` | string | Yes | URL-safe identifier |\n| `settings.name` | string | Yes | Display name |\n| `settings.description` | string | No | Agent description |\n| `settings.format` | string | Yes | `interactive` or `generation` |\n| `protocol` | string | Yes | YAML protocol definition |\n| `prompts` | array | Yes | Prompt files |\n\n### Response\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"message\": \"Agent created successfully\"\n}\n```\n\n### Example\n\n```bash\ncurl -X POST https://octavus.ai/api/agents \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"settings\": {\n \"slug\": \"my-agent\",\n \"name\": \"My Agent\",\n \"format\": \"interactive\"\n },\n \"protocol\": \"agent:\\n model: anthropic/claude-sonnet-4-5\\n system: system\",\n \"prompts\": [\n { \"name\": \"system\", \"content\": \"You are a helpful assistant.\" }\n ]\n }'\n```\n\n## Update Agent\n\nUpdate an existing agent.\n\n```\nPATCH /api/agents/:id\nPATCH /api/agents/:slug?by=slug\n```\n\n### Request Body\n\n```json\n{\n \"protocol\": \"input:\\n COMPANY_NAME: { type: string }\\n...\",\n \"prompts\": [\n {\n \"name\": \"system\",\n \"content\": \"Updated system prompt...\"\n }\n ]\n}\n```\n\nAll fields are optional. Only provided fields are updated.\n\n### Response\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"message\": \"Agent updated successfully\"\n}\n```\n\n### Example\n\n```bash\ncurl -X PATCH https://octavus.ai/api/agents/:agentId \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"protocol\": \"agent:\\n model: anthropic/claude-sonnet-4-5\\n system: system\\n thinking: high\"\n }'\n```\n\n## Sync Agent\n\nThe Server SDK provides a `sync` method that creates or updates an agent:\n\n```typescript\nconst { agentId, created } = await client.agents.sync({\n settings: {\n slug: 'support-chat',\n name: 'Support Chat',\n format: 'interactive',\n },\n protocol: protocolYaml,\n prompts: [\n { name: 'system', content: systemPrompt },\n ],\n});\n\nif (created) {\n console.log('Created new agent:', agentId);\n} else {\n console.log('Updated existing agent:', agentId);\n}\n```\n\nThis is useful for CI/CD pipelines to deploy agent updates.\n\n",
188
- "excerpt": "Agents API Manage agent definitions including protocols and prompts. List Agents Get all agents in the project. Response Example Get Agent Get a single agent by ID or slug. Response Example ...",
259
+ "content": "\n# Agents API\n\nManage agent definitions including protocols and prompts.\n\n## List Agents\n\nGet all agents in the project.\n\n```\nGET /api/agents\n```\n\n### Response\n\n```json\n{\n \"agents\": [\n {\n \"id\": \"cm5xvz7k80001abcd\",\n \"slug\": \"support-chat\",\n \"name\": \"Support Chat\",\n \"description\": \"Customer support agent\",\n \"format\": \"interactive\",\n \"createdAt\": \"2024-01-10T08:00:00Z\",\n \"updatedAt\": \"2024-01-15T10:00:00Z\"\n }\n ]\n}\n```\n\n### Example\n\n```bash\ncurl https://octavus.ai/api/agents \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n\n## Get Agent\n\nGet a single agent by ID.\n\n```\nGET /api/agents/:id\n```\n\n### Response\n\n```json\n{\n \"id\": \"cm5xvz7k80001abcd\",\n \"settings\": {\n \"slug\": \"support-chat\",\n \"name\": \"Support Chat\",\n \"description\": \"Customer support agent\",\n \"format\": \"interactive\"\n },\n \"protocol\": \"input:\\n COMPANY_NAME: { type: string }\\n...\",\n \"prompts\": [\n {\n \"name\": \"system\",\n \"content\": \"You are a support agent for {{COMPANY_NAME}}...\"\n },\n {\n \"name\": \"user-message\",\n \"content\": \"{{USER_MESSAGE}}\"\n }\n ]\n}\n```\n\n### Example\n\n```bash\ncurl https://octavus.ai/api/agents/:agentId \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n\n> **Tip:** You can also view and edit agents directly in the [platform](https://octavus.ai), or use the [CLI](/docs/server-sdk/cli) (`octavus list`) for local workflows.\n\n## Create Agent\n\nCreate a new agent.\n\n```\nPOST /api/agents\n```\n\n### Request Body\n\n```json\n{\n \"settings\": {\n \"slug\": \"support-chat\",\n \"name\": \"Support Chat\",\n \"description\": \"Customer support agent\",\n \"format\": \"interactive\"\n },\n \"protocol\": \"input:\\n COMPANY_NAME: { type: string }\\n...\",\n \"prompts\": [\n {\n \"name\": \"system\",\n \"content\": \"You are a support agent...\"\n }\n ]\n}\n```\n\n| Field | Type | Required | Description |\n| ---------------------- | ------ | -------- | ------------------------- |\n| `settings.slug` | string | Yes | URL-safe identifier |\n| `settings.name` | string | Yes | Display name |\n| `settings.description` | string | No | Agent description |\n| `settings.format` | string | Yes | `interactive` or `worker` |\n| `protocol` | string | Yes | YAML protocol definition |\n| `prompts` | array | Yes | Prompt files |\n\n### Response\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"message\": \"Agent created successfully\"\n}\n```\n\n### Example\n\n```bash\ncurl -X POST https://octavus.ai/api/agents \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"settings\": {\n \"slug\": \"my-agent\",\n \"name\": \"My Agent\",\n \"format\": \"interactive\"\n },\n \"protocol\": \"agent:\\n model: anthropic/claude-sonnet-4-5\\n system: system\",\n \"prompts\": [\n { \"name\": \"system\", \"content\": \"You are a helpful assistant.\" }\n ]\n }'\n```\n\n## Update Agent\n\nUpdate an existing agent.\n\n```\nPATCH /api/agents/:id\n```\n\n### Request Body\n\n```json\n{\n \"protocol\": \"input:\\n COMPANY_NAME: { type: string }\\n...\",\n \"prompts\": [\n {\n \"name\": \"system\",\n \"content\": \"Updated system prompt...\"\n }\n ]\n}\n```\n\nAll fields are optional. Only provided fields are updated.\n\n### Response\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"message\": \"Agent updated successfully\"\n}\n```\n\n### Example\n\n```bash\ncurl -X PATCH https://octavus.ai/api/agents/:agentId \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"protocol\": \"agent:\\n model: anthropic/claude-sonnet-4-5\\n system: system\\n thinking: high\"\n }'\n```\n\n## Creating and Managing Agents\n\nThere are two ways to manage agents:\n\n### Platform UI\n\nCreate and edit agents directly at [octavus.ai](https://octavus.ai). The web editor provides real-time validation and is the easiest way to get started. Copy the agent ID from the URL to use in your application.\n\n### CLI (Local Development)\n\nFor version-controlled agent definitions, use the [Octavus CLI](/docs/server-sdk/cli):\n\n```bash\noctavus sync ./agents/support-chat\n```\n\nThis creates the agent if it doesn't exist, or updates it if it does. The CLI outputs the agent ID which you should store in an environment variable.\n\nFor CI/CD integration, see the [CLI documentation](/docs/server-sdk/cli#cicd-integration).\n",
260
+ "excerpt": "Agents API Manage agent definitions including protocols and prompts. List Agents Get all agents in the project. Response Example Get Agent Get a single agent by ID. Response Example > Tip: You...",
189
261
  "order": 3
190
262
  },
191
263
  {
@@ -193,8 +265,8 @@
193
265
  "section": "examples",
194
266
  "title": "Overview",
195
267
  "description": "Complete integration examples for different architectures.",
196
- "content": "\n# Examples\n\nThis section provides complete integration examples for different architectures. Each example walks through building a functional chat interface from scratch.\n\n## Available Examples\n\n| Example | Transport | Best For |\n|---------|-----------|----------|\n| [Next.js Chat](/docs/examples/nextjs-chat) | HTTP/SSE | Next.js, Remix, standard web apps |\n| [Socket Chat](/docs/examples/socket-chat) | SockJS | Meteor, Phoenix, real-time apps |\n\n## Choosing a Transport\n\n**Use HTTP Transport when:**\n- Building with Next.js, Remix, or similar frameworks\n- You want the simplest integration\n- Deploying to serverless (Vercel, Netlify, etc.)\n\n**Use Socket Transport when:**\n- Using Meteor, Phoenix, or socket-based frameworks\n- Need custom real-time events (typing indicators, presence)\n- Behind proxies that don't support SSE well\n",
197
- "excerpt": "Examples This section provides complete integration examples for different architectures. Each example walks through building a functional chat interface from scratch. Available Examples | Example |...",
268
+ "content": "\n# Examples\n\nThis section provides complete integration examples for different architectures. Each example walks through building a functional chat interface from scratch.\n\n## Available Examples\n\n| Example | Transport | Best For |\n| ------------------------------------------ | --------- | --------------------------------- |\n| [Next.js Chat](/docs/examples/nextjs-chat) | HTTP/SSE | Next.js, Remix, standard web apps |\n| [Socket Chat](/docs/examples/socket-chat) | SockJS | Meteor, Phoenix, real-time apps |\n\n## Choosing a Transport\n\n**Use HTTP Transport when:**\n\n- Building with Next.js, Remix, or similar frameworks\n- You want the simplest integration\n- Deploying to serverless (Vercel, Netlify, etc.)\n\n**Use Socket Transport when:**\n\n- Using Meteor, Phoenix, or socket-based frameworks\n- Need custom real-time events (typing indicators, presence)\n- Behind proxies that don't support SSE well\n",
269
+ "excerpt": "Examples This section provides complete integration examples for different architectures. Each example walks through building a functional chat interface from scratch. Available Examples | Example ...",
198
270
  "order": 1
199
271
  },
200
272
  {
@@ -202,7 +274,7 @@
202
274
  "section": "examples",
203
275
  "title": "Next.js Chat",
204
276
  "description": "Building a chat interface with Next.js and HTTP transport.",
205
- "content": "\n# Next.js Chat Example\n\nThis example builds a support chat interface using Next.js App Router with HTTP/SSE transport. This is the recommended pattern for most web applications.\n\n## What You're Building\n\nA chat interface that:\n- Creates sessions server-side\n- Streams AI responses in real-time\n- Handles tool calls on your server\n- Shows typing status during streaming\n\n## Architecture\n\n```mermaid\nflowchart LR\n Browser[\"Browser<br/>(React)\"] -->|\"POST /api/chat\"| NextJS[\"Next.js API<br/>Routes\"]\n NextJS -->|\"SSE\"| Browser\n NextJS --> Platform[\"Octavus Platform\"]\n```\n\n## Prerequisites\n\n- Next.js 14+ with App Router\n- Octavus account with API key\n- An agent configured in Octavus\n\n## Step 1: Install Dependencies\n\n```bash\nnpm install @octavus/server-sdk @octavus/react\n```\n\n## Step 2: Configure Environment\n\n```bash\n# .env.local\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key\n```\n\n## Step 3: Create the Octavus Client\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n## Step 4: Create Session Endpoint\n\nSessions hold conversation state. Create one when the user opens the chat:\n\n```typescript\n// app/api/sessions/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { agentId, input } = await request.json();\n\n // Create a new session with initial input variables\n const sessionId = await octavus.agentSessions.create(agentId, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n**Protocol Note:** The `input` object contains variables defined in your agent's protocol. For example, if your agent has `COMPANY_NAME` as an input variable:\n\n```typescript\nconst sessionId = await octavus.agentSessions.create(agentId, {\n COMPANY_NAME: 'Acme Corp',\n USER_ID: user.id,\n});\n```\n\n## Step 5: Create Trigger Endpoint\n\nTriggers execute agent actions. The `user-message` trigger is the most common:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { sessionId, triggerName, input } = await request.json();\n\n // Attach to the session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Tool handlers run on YOUR server, not Octavus\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n const user = await db.users.findUnique({ where: { id: userId } });\n return {\n name: user.name,\n email: user.email,\n plan: user.plan,\n };\n },\n\n 'create-support-ticket': async (args) => {\n const ticket = await db.tickets.create({\n data: {\n summary: args.summary as string,\n priority: args.priority as string,\n },\n });\n return {\n ticketId: ticket.id,\n estimatedResponse: '24 hours',\n };\n },\n },\n });\n\n // trigger() returns parsed events, toSSEStream() converts to SSE format\n const events = session.trigger(triggerName, input);\n\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n```\n\n**Protocol Note:** Tool names and arguments are defined in your agent's protocol YAML. The tool handlers here must match those definitions.\n\n## Step 6: Build the Chat Component\n\n```tsx\n// components/Chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create transport - memoized on sessionId\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || status === 'streaming') return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Send triggers the 'user-message' action\n // The third argument adds the user message to the UI\n await send(\n 'user-message',\n { USER_MESSAGE: message },\n { userMessage: { content: message } },\n );\n };\n\n return (\n <div className=\"flex flex-col h-screen\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <div\n key={msg.id}\n className={msg.role === 'user' ? 'text-right' : 'text-left'}\n >\n <div\n className={`inline-block p-3 rounded-lg ${\n msg.role === 'user'\n ? 'bg-blue-500 text-white'\n : 'bg-gray-100'\n }`}\n >\n {msg.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n if (part.type === 'tool-call') {\n return (\n <div key={i} className=\"text-sm opacity-70\">\n Using {part.toolName}...\n </div>\n );\n }\n return null;\n })}\n </div>\n </div>\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={status === 'streaming'}\n />\n <button\n type=\"submit\"\n disabled={status === 'streaming'}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg\"\n >\n {status === 'streaming' ? 'Sending...' : 'Send'}\n </button>\n </div>\n </form>\n </div>\n );\n}\n```\n\n## Step 7: Create the Page\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/Chat';\n\nconst AGENT_ID = 'your-agent-id'; // From Octavus dashboard\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n // Create session on mount\n fetch('/api/sessions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentId: AGENT_ID,\n input: {\n COMPANY_NAME: 'Acme Corp',\n },\n }),\n })\n .then((res) => res.json())\n .then((data) => setSessionId(data.sessionId));\n }, []);\n\n if (!sessionId) {\n return <div className=\"p-8\">Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Protocol Integration\n\nYour agent's protocol defines the triggers and tools. Here's how the code maps to protocol:\n\n### Triggers\n\n```yaml\n# In your agent's protocol.yaml\ntriggers:\n user-message:\n description: User sends a chat message\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n```\n\nThe `send()` call maps directly:\n\n```typescript\nawait send(\n 'user-message', // trigger name\n { USER_MESSAGE: message }, // trigger inputs\n { userMessage: { content: message } },\n);\n```\n\n### Tools\n\n```yaml\n# In your agent's protocol.yaml\ntools:\n get-user-account:\n description: Fetch user account details\n parameters:\n userId:\n type: string\n description: The user ID to look up\n```\n\nTool handlers receive the parameters as `args`:\n\n```typescript\n'get-user-account': async (args) => {\n const userId = args.userId as string;\n // ...\n}\n```\n\n## Next Steps\n\n- [Protocol Overview](/docs/protocol/overview) — Define agent behavior\n- [Messages](/docs/client-sdk/messages) — Rich message rendering\n- [Streaming](/docs/client-sdk/streaming) — Advanced streaming UI\n\n",
277
+ "content": "\n# Next.js Chat Example\n\nThis example builds a support chat interface using Next.js App Router with HTTP/SSE transport. This is the recommended pattern for most web applications.\n\n## What You're Building\n\nA chat interface that:\n\n- Creates sessions server-side\n- Streams AI responses in real-time\n- Handles tool calls on your server\n- Shows typing status during streaming\n\n## Architecture\n\n```mermaid\nflowchart LR\n Browser[\"Browser<br/>(React)\"] -->|\"POST /api/chat\"| NextJS[\"Next.js API<br/>Routes\"]\n NextJS -->|\"SSE\"| Browser\n NextJS --> Platform[\"Octavus Platform\"]\n```\n\n## Prerequisites\n\n- Next.js 14+ with App Router\n- Octavus account with API key\n- An agent configured in Octavus\n\n## Step 1: Install Dependencies\n\n```bash\nnpm install @octavus/server-sdk @octavus/react\n```\n\n## Step 2: Configure Environment\n\n```bash\n# .env.local\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key\n```\n\n## Step 3: Create the Octavus Client\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n## Step 4: Create Upload URLs Endpoint (Optional)\n\nIf your agent supports file uploads (images, documents), create an endpoint to get presigned URLs:\n\n```typescript\n// app/api/upload-urls/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { sessionId, files } = await request.json();\n\n // Get presigned URLs from Octavus\n const result = await octavus.files.getUploadUrls(sessionId, files);\n\n return NextResponse.json(result);\n}\n```\n\n## Step 5: Create Session Endpoint\n\nSessions hold conversation state. Create one when the user opens the chat:\n\n```typescript\n// app/api/sessions/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { agentId, input } = await request.json();\n\n // Create a new session with initial input variables\n const sessionId = await octavus.agentSessions.create(agentId, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n**Protocol Note:** The `input` object contains variables defined in your agent's protocol. For example, if your agent has `COMPANY_NAME` as an input variable:\n\n```typescript\nconst sessionId = await octavus.agentSessions.create(agentId, {\n COMPANY_NAME: 'Acme Corp',\n USER_ID: user.id,\n});\n```\n\n## Step 6: Create Trigger Endpoint\n\nTriggers execute agent actions. The `user-message` trigger is the most common:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { sessionId, triggerName, input } = await request.json();\n\n // Attach to the session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Tool handlers run on YOUR server, not Octavus\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n const user = await db.users.findUnique({ where: { id: userId } });\n return {\n name: user.name,\n email: user.email,\n plan: user.plan,\n };\n },\n\n 'create-support-ticket': async (args) => {\n const ticket = await db.tickets.create({\n data: {\n summary: args.summary as string,\n priority: args.priority as string,\n },\n });\n return {\n ticketId: ticket.id,\n estimatedResponse: '24 hours',\n };\n },\n },\n });\n\n // trigger() returns parsed events, toSSEStream() converts to SSE format\n const events = session.trigger(triggerName, input);\n\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n```\n\n**Protocol Note:** Tool names and arguments are defined in your agent's protocol YAML. The tool handlers here must match those definitions.\n\n## Step 7: Build the Chat Component\n\n```tsx\n// components/Chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create transport - memoized on sessionId\n const transport = useMemo(\n () =>\n createHttpTransport({\n triggerRequest: (triggerName, input) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, triggerName, input }),\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || status === 'streaming') return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Send triggers the 'user-message' action\n // The third argument adds the user message to the UI\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return (\n <div className=\"flex flex-col h-screen\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <div key={msg.id} className={msg.role === 'user' ? 'text-right' : 'text-left'}>\n <div\n className={`inline-block p-3 rounded-lg ${\n msg.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-100'\n }`}\n >\n {msg.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n if (part.type === 'tool-call') {\n return (\n <div key={i} className=\"text-sm opacity-70\">\n Using {part.toolName}...\n </div>\n );\n }\n return null;\n })}\n </div>\n </div>\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={status === 'streaming'}\n />\n <button\n type=\"submit\"\n disabled={status === 'streaming'}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg\"\n >\n {status === 'streaming' ? 'Sending...' : 'Send'}\n </button>\n </div>\n </form>\n </div>\n );\n}\n```\n\n## Step 8: Create the Page\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/Chat';\n\nconst AGENT_ID = 'your-agent-id'; // From Octavus dashboard\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n // Create session on mount\n fetch('/api/sessions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentId: AGENT_ID,\n input: {\n COMPANY_NAME: 'Acme Corp',\n },\n }),\n })\n .then((res) => res.json())\n .then((data) => setSessionId(data.sessionId));\n }, []);\n\n if (!sessionId) {\n return <div className=\"p-8\">Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Protocol Integration\n\nYour agent's protocol defines the triggers and tools. Here's how the code maps to protocol:\n\n### Triggers\n\n```yaml\n# In your agent's protocol.yaml\ntriggers:\n user-message:\n description: User sends a chat message\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n```\n\nThe `send()` call maps directly:\n\n```typescript\nawait send(\n 'user-message', // trigger name\n { USER_MESSAGE: message }, // trigger inputs\n { userMessage: { content: message } },\n);\n```\n\n### Tools\n\n```yaml\n# In your agent's protocol.yaml\ntools:\n get-user-account:\n description: Fetch user account details\n parameters:\n userId:\n type: string\n description: The user ID to look up\n```\n\nTool handlers receive the parameters as `args`:\n\n```typescript\n'get-user-account': async (args) => {\n const userId = args.userId as string;\n // ...\n}\n```\n\n## Next Steps\n\n- [Protocol Overview](/docs/protocol/overview) — Define agent behavior\n- [Messages](/docs/client-sdk/messages) — Rich message rendering\n- [Streaming](/docs/client-sdk/streaming) — Advanced streaming UI\n",
206
278
  "excerpt": "Next.js Chat Example This example builds a support chat interface using Next.js App Router with HTTP/SSE transport. This is the recommended pattern for most web applications. What You're Building A...",
207
279
  "order": 2
208
280
  },
@@ -211,7 +283,7 @@
211
283
  "section": "examples",
212
284
  "title": "Socket Chat",
213
285
  "description": "Building a chat interface with SockJS for real-time frameworks.",
214
- "content": "\n# Socket Chat Example\n\nThis example builds a chat interface using SockJS for bidirectional communication. Use this pattern for Meteor, Phoenix, or when you need custom real-time events.\n\n## What You're Building\n\nA chat interface that:\n- Uses SockJS for real-time streaming\n- Manages sessions server-side (client doesn't need sessionId)\n- Supports custom events alongside chat\n- Works with frameworks that use WebSocket-like transports\n\n## Architecture\n\n```mermaid\nflowchart LR\n Browser[\"Browser<br/>(React)\"] <-->|\"SockJS\"| Server[\"Your Server<br/>(Express)\"]\n Server --> Platform[\"Octavus Platform\"]\n```\n\n**Key difference from HTTP:** The server maintains a persistent socket connection and manages sessions internally. The client never needs to know about `sessionId`.\n\n## Prerequisites\n\n- Express (or similar Node.js server)\n- React frontend\n- `sockjs` (server) and `sockjs-client` (client)\n- Octavus account with API key\n\n## Step 1: Install Dependencies\n\n**Server:**\n```bash\nnpm install @octavus/server-sdk sockjs express\nnpm install -D @types/sockjs @types/express\n```\n\n**Client:**\n```bash\nnpm install @octavus/react sockjs-client\nnpm install -D @types/sockjs-client\n```\n\n## Step 2: Configure Environment\n\n```bash\n# .env\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key\nOCTAVUS_AGENT_ID=your-agent-id\n```\n\n## Step 3: Create the Octavus Client (Server)\n\n```typescript\n// server/octavus/client.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nexport const AGENT_ID = process.env.OCTAVUS_AGENT_ID!;\n```\n\n## Step 4: Create the Socket Handler (Server)\n\nThis is the core of socket integration. Each connection gets its own session:\n\n```typescript\n// server/octavus/socket-handler.ts\nimport type { Connection } from 'sockjs';\nimport { OctavusClient, type AgentSession } from '@octavus/server-sdk';\n\nconst octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nconst AGENT_ID = process.env.OCTAVUS_AGENT_ID!;\n\nexport function createSocketHandler() {\n return (conn: Connection) => {\n let session: AgentSession | null = null;\n let abortController: AbortController | null = null;\n\n conn.on('data', (rawData: string) => {\n void handleMessage(rawData);\n });\n\n async function handleMessage(rawData: string) {\n const msg = JSON.parse(rawData);\n\n // Handle stop request\n if (msg.type === 'stop') {\n abortController?.abort();\n return;\n }\n\n // Handle trigger\n if (msg.type === 'trigger') {\n // Create session lazily on first trigger\n if (!session) {\n const sessionId = await octavus.agentSessions.create(AGENT_ID, {\n // Initial input variables from your protocol\n COMPANY_NAME: 'Acme Corp',\n });\n\n session = octavus.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async () => {\n // Fetch from your database\n return { name: 'Demo User', plan: 'pro' };\n },\n 'create-support-ticket': async () => {\n return { ticketId: 'TKT-123', estimatedResponse: '24h' };\n },\n },\n });\n }\n\n abortController = new AbortController();\n\n // trigger() returns parsed events — iterate directly\n const events = session.trigger(msg.triggerName, msg.input);\n\n try {\n for await (const event of events) {\n if (abortController.signal.aborted) break;\n conn.write(JSON.stringify(event));\n }\n } catch {\n // Handle errors\n }\n }\n }\n\n conn.on('close', () => {\n abortController?.abort();\n });\n };\n}\n```\n\n## Step 5: Set Up the Express Server\n\n```typescript\n// server/index.ts\nimport express from 'express';\nimport http from 'http';\nimport sockjs from 'sockjs';\nimport { createSocketHandler } from './octavus/socket-handler';\n\nconst app = express();\nconst server = http.createServer(app);\n\n// Create SockJS server\nconst sockServer = sockjs.createServer({\n prefix: '/octavus',\n log: () => {}, // Silence logs\n});\n\n// Attach handler\nsockServer.on('connection', createSocketHandler());\nsockServer.installHandlers(server);\n\n// Serve your frontend\napp.use(express.static('dist/client'));\n\nserver.listen(3001, () => {\n console.log('Server running on http://localhost:3001');\n});\n```\n\n## Step 6: Create the Socket Hook (Client)\n\n```typescript\n// src/hooks/useOctavusSocket.ts\nimport { useMemo } from 'react';\nimport SockJS from 'sockjs-client';\nimport { useOctavusChat, createSocketTransport, type SocketLike } from '@octavus/react';\n\nfunction connectSocket(): Promise<SocketLike> {\n return new Promise((resolve, reject) => {\n const sock = new SockJS('/octavus');\n\n sock.onopen = () => resolve(sock);\n sock.onerror = () => reject(new Error('Connection failed'));\n });\n}\n\nexport function useOctavusSocket() {\n // Transport is stable - empty deps because server manages sessions\n const transport = useMemo(\n () => createSocketTransport({ connect: connectSocket }),\n [],\n );\n\n const { messages, status, error, send, stop } = useOctavusChat({\n transport,\n onError: (err) => console.error('Chat error:', err),\n });\n\n const sendMessage = async (message: string) => {\n await send(\n 'user-message',\n { USER_MESSAGE: message },\n { userMessage: { content: message } },\n );\n };\n\n return { messages, status, error, sendMessage, stop };\n}\n```\n\n## Step 7: Build the Chat Component\n\n```tsx\n// src/components/Chat.tsx\nimport { useState } from 'react';\nimport { useOctavusSocket } from '../hooks/useOctavusSocket';\n\nexport function Chat() {\n const [inputValue, setInputValue] = useState('');\n const { messages, status, sendMessage, stop } = useOctavusSocket();\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || status === 'streaming') return;\n\n const message = inputValue.trim();\n setInputValue('');\n await sendMessage(message);\n };\n\n return (\n <div className=\"flex flex-col h-screen\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <div\n key={msg.id}\n className={msg.role === 'user' ? 'text-right' : 'text-left'}\n >\n <div\n className={`inline-block p-3 rounded-lg ${\n msg.role === 'user'\n ? 'bg-blue-500 text-white'\n : 'bg-gray-100'\n }`}\n >\n {msg.parts.map((part, i) => {\n if (part.type === 'text') return <p key={i}>{part.text}</p>;\n return null;\n })}\n </div>\n </div>\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={status === 'streaming'}\n />\n {status === 'streaming' ? (\n <button\n type=\"button\"\n onClick={stop}\n className=\"px-4 py-2 bg-red-500 text-white rounded-lg\"\n >\n Stop\n </button>\n ) : (\n <button\n type=\"submit\"\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg\"\n >\n Send\n </button>\n )}\n </form>\n </div>\n );\n}\n```\n\n## Custom Events\n\nSocket transport supports custom events alongside Octavus events:\n\n```typescript\n// Client - handle custom events\nconst transport = useMemo(\n () =>\n createSocketTransport({\n connect: connectSocket,\n onMessage: (data) => {\n const msg = data as { type: string; [key: string]: unknown };\n\n if (msg.type === 'typing-indicator') {\n setAgentTyping(msg.isTyping as boolean);\n }\n\n if (msg.type === 'custom-notification') {\n showToast(msg.message as string);\n }\n\n // Octavus events (text-delta, finish, etc.) are handled automatically\n },\n }),\n [],\n);\n```\n\n```typescript\n// Server - send custom events\nconn.write(JSON.stringify({\n type: 'typing-indicator',\n isTyping: true,\n}));\n```\n\n## Protocol Integration\n\n### Triggers\n\nThe socket handler receives trigger messages and forwards them to Octavus:\n\n```typescript\n// Client sends:\n{ type: 'trigger', triggerName: 'user-message', input: { USER_MESSAGE: 'Hello' } }\n\n// Server handles:\nif (msg.type === 'trigger') {\n const events = session.trigger(msg.triggerName, msg.input);\n for await (const event of events) {\n conn.write(JSON.stringify(event));\n }\n}\n```\n\n### Tools\n\nTools are defined in your agent's protocol and handled server-side:\n\n```yaml\n# protocol.yaml\ntools:\n get-user-account:\n description: Fetch user details\n parameters:\n userId:\n type: string\n```\n\n```typescript\n// Server tool handler\ntools: {\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n return await db.users.find(userId);\n },\n}\n```\n\n## Meteor Integration Note\n\nMeteor's bundler may have issues with ES6 imports of `sockjs-client`:\n\n```typescript\n// Use require() instead of import\nconst SockJS: typeof import('sockjs-client') = require('sockjs-client');\n```\n\n## Next Steps\n\n- [Socket Transport](/docs/client-sdk/socket-transport) — Advanced socket patterns\n- [Protocol Overview](/docs/protocol/overview) — Define agent behavior\n- [Tools](/docs/protocol/tools) — Building tool handlers\n\n",
286
+ "content": "\n# Socket Chat Example\n\nThis example builds a chat interface using SockJS for bidirectional communication. Use this pattern for Meteor, Phoenix, or when you need custom real-time events.\n\n## What You're Building\n\nA chat interface that:\n\n- Uses SockJS for real-time streaming\n- Manages sessions server-side (client doesn't need sessionId)\n- Supports custom events alongside chat\n- Works with frameworks that use WebSocket-like transports\n\n## Architecture\n\n```mermaid\nflowchart LR\n Browser[\"Browser<br/>(React)\"] <-->|\"SockJS\"| Server[\"Your Server<br/>(Express)\"]\n Server --> Platform[\"Octavus Platform\"]\n```\n\n**Key difference from HTTP:** The server maintains a persistent socket connection and manages sessions internally. The client never needs to know about `sessionId`.\n\n## Prerequisites\n\n- Express (or similar Node.js server)\n- React frontend\n- `sockjs` (server) and `sockjs-client` (client)\n- Octavus account with API key\n\n## Step 1: Install Dependencies\n\n**Server:**\n\n```bash\nnpm install @octavus/server-sdk sockjs express\nnpm install -D @types/sockjs @types/express\n```\n\n**Client:**\n\n```bash\nnpm install @octavus/react sockjs-client\nnpm install -D @types/sockjs-client\n```\n\n## Step 2: Configure Environment\n\n```bash\n# .env\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key\nOCTAVUS_AGENT_ID=your-agent-id\n```\n\n## Step 3: Create the Octavus Client (Server)\n\n```typescript\n// server/octavus/client.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nexport const AGENT_ID = process.env.OCTAVUS_AGENT_ID!;\n```\n\n## Step 4: Create the Socket Handler (Server)\n\nThis is the core of socket integration. Each connection gets its own session:\n\n```typescript\n// server/octavus/socket-handler.ts\nimport type { Connection } from 'sockjs';\nimport { OctavusClient, type AgentSession } from '@octavus/server-sdk';\n\nconst octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nconst AGENT_ID = process.env.OCTAVUS_AGENT_ID!;\n\nexport function createSocketHandler() {\n return (conn: Connection) => {\n let session: AgentSession | null = null;\n let abortController: AbortController | null = null;\n\n conn.on('data', (rawData: string) => {\n void handleMessage(rawData);\n });\n\n async function handleMessage(rawData: string) {\n const msg = JSON.parse(rawData);\n\n // Handle stop request\n if (msg.type === 'stop') {\n abortController?.abort();\n return;\n }\n\n // Handle trigger\n if (msg.type === 'trigger') {\n // Create session lazily on first trigger\n if (!session) {\n const sessionId = await octavus.agentSessions.create(AGENT_ID, {\n // Initial input variables from your protocol\n COMPANY_NAME: 'Acme Corp',\n });\n\n session = octavus.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async () => {\n // Fetch from your database\n return { name: 'Demo User', plan: 'pro' };\n },\n 'create-support-ticket': async () => {\n return { ticketId: 'TKT-123', estimatedResponse: '24h' };\n },\n },\n });\n }\n\n abortController = new AbortController();\n\n // trigger() returns parsed events — iterate directly\n const events = session.trigger(msg.triggerName, msg.input);\n\n try {\n for await (const event of events) {\n if (abortController.signal.aborted) break;\n conn.write(JSON.stringify(event));\n }\n } catch {\n // Handle errors\n }\n }\n }\n\n conn.on('close', () => {\n abortController?.abort();\n });\n };\n}\n```\n\n## Step 5: Set Up the Express Server\n\n```typescript\n// server/index.ts\nimport express from 'express';\nimport http from 'http';\nimport sockjs from 'sockjs';\nimport { createSocketHandler } from './octavus/socket-handler';\n\nconst app = express();\nconst server = http.createServer(app);\n\n// Create SockJS server\nconst sockServer = sockjs.createServer({\n prefix: '/octavus',\n log: () => {}, // Silence logs\n});\n\n// Attach handler\nsockServer.on('connection', createSocketHandler());\nsockServer.installHandlers(server);\n\n// Serve your frontend\napp.use(express.static('dist/client'));\n\nserver.listen(3001, () => {\n console.log('Server running on http://localhost:3001');\n});\n```\n\n## Step 6: Create the Socket Hook (Client)\n\n```typescript\n// src/hooks/useOctavusSocket.ts\nimport { useEffect, useMemo } from 'react';\nimport SockJS from 'sockjs-client';\nimport { useOctavusChat, createSocketTransport, type SocketLike } from '@octavus/react';\n\nfunction connectSocket(): Promise<SocketLike> {\n return new Promise((resolve, reject) => {\n const sock = new SockJS('/octavus');\n\n sock.onopen = () => resolve(sock);\n sock.onerror = () => reject(new Error('Connection failed'));\n });\n}\n\nexport function useOctavusSocket() {\n // Transport is stable - empty deps because server manages sessions\n const transport = useMemo(() => createSocketTransport({ connect: connectSocket }), []);\n\n const {\n messages,\n status,\n error,\n send,\n stop,\n // Socket-specific connection state\n connectionState,\n connectionError,\n connect,\n disconnect,\n } = useOctavusChat({\n transport,\n onError: (err) => console.error('Chat error:', err),\n });\n\n // Eagerly connect for UI status indicator\n useEffect(() => {\n connect?.();\n return () => disconnect?.();\n }, [connect, disconnect]);\n\n const sendMessage = async (message: string) => {\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return { messages, status, error, connectionState, connectionError, sendMessage, stop };\n}\n```\n\n## Step 7: Build the Chat Component\n\n```tsx\n// src/components/Chat.tsx\nimport { useState } from 'react';\nimport { useOctavusSocket } from '../hooks/useOctavusSocket';\n\nfunction ConnectionIndicator({ state }: { state: string | undefined }) {\n const colors: Record<string, string> = {\n connected: 'bg-green-500',\n connecting: 'bg-yellow-500',\n error: 'bg-red-500',\n disconnected: 'bg-gray-400',\n };\n const color = colors[state ?? 'disconnected'];\n return <div className={`w-2 h-2 rounded-full ${color}`} title={state} />;\n}\n\nexport function Chat() {\n const [inputValue, setInputValue] = useState('');\n const { messages, status, connectionState, sendMessage, stop } = useOctavusSocket();\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || status === 'streaming') return;\n\n const message = inputValue.trim();\n setInputValue('');\n await sendMessage(message);\n };\n\n return (\n <div className=\"flex flex-col h-screen\">\n {/* Header with connection status */}\n <div className=\"p-4 border-b flex items-center justify-between\">\n <h1 className=\"font-semibold\">AI Chat</h1>\n <ConnectionIndicator state={connectionState} />\n </div>\n\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <div key={msg.id} className={msg.role === 'user' ? 'text-right' : 'text-left'}>\n <div\n className={`inline-block p-3 rounded-lg ${\n msg.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-100'\n }`}\n >\n {msg.parts.map((part, i) => {\n if (part.type === 'text') return <p key={i}>{part.text}</p>;\n return null;\n })}\n </div>\n </div>\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={status === 'streaming'}\n />\n {status === 'streaming' ? (\n <button\n type=\"button\"\n onClick={stop}\n className=\"px-4 py-2 bg-red-500 text-white rounded-lg\"\n >\n Stop\n </button>\n ) : (\n <button type=\"submit\" className=\"px-4 py-2 bg-blue-500 text-white rounded-lg\">\n Send\n </button>\n )}\n </form>\n </div>\n );\n}\n```\n\n## Custom Events\n\nSocket transport supports custom events alongside Octavus events:\n\n```typescript\n// Client - handle custom events\nconst transport = useMemo(\n () =>\n createSocketTransport({\n connect: connectSocket,\n onMessage: (data) => {\n const msg = data as { type: string; [key: string]: unknown };\n\n if (msg.type === 'typing-indicator') {\n setAgentTyping(msg.isTyping as boolean);\n }\n\n if (msg.type === 'custom-notification') {\n showToast(msg.message as string);\n }\n\n // Octavus events (text-delta, finish, etc.) are handled automatically\n },\n }),\n [],\n);\n```\n\n```typescript\n// Server - send custom events\nconn.write(\n JSON.stringify({\n type: 'typing-indicator',\n isTyping: true,\n }),\n);\n```\n\n## Protocol Integration\n\n### Triggers\n\nThe socket handler receives trigger messages and forwards them to Octavus:\n\n```typescript\n// Client sends:\n{ type: 'trigger', triggerName: 'user-message', input: { USER_MESSAGE: 'Hello' } }\n\n// Server handles:\nif (msg.type === 'trigger') {\n const events = session.trigger(msg.triggerName, msg.input);\n for await (const event of events) {\n conn.write(JSON.stringify(event));\n }\n}\n```\n\n### Tools\n\nTools are defined in your agent's protocol and handled server-side:\n\n```yaml\n# protocol.yaml\ntools:\n get-user-account:\n description: Fetch user details\n parameters:\n userId:\n type: string\n```\n\n```typescript\n// Server tool handler\ntools: {\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n return await db.users.find(userId);\n },\n}\n```\n\n## Meteor Integration Note\n\nMeteor's bundler may have issues with ES6 imports of `sockjs-client`:\n\n```typescript\n// Use require() instead of import\nconst SockJS: typeof import('sockjs-client') = require('sockjs-client');\n```\n\n## Next Steps\n\n- [Socket Transport](/docs/client-sdk/socket-transport) — Advanced socket patterns\n- [Protocol Overview](/docs/protocol/overview) — Define agent behavior\n- [Tools](/docs/protocol/tools) — Building tool handlers\n",
215
287
  "excerpt": "Socket Chat Example This example builds a chat interface using SockJS for bidirectional communication. Use this pattern for Meteor, Phoenix, or when you need custom real-time events. What You're...",
216
288
  "order": 3
217
289
  }