@octavus/docs 0.0.9 → 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.
- package/README.md +127 -0
- package/content/01-getting-started/01-introduction.md +3 -3
- package/content/01-getting-started/02-quickstart.md +40 -17
- package/content/02-server-sdk/01-overview.md +54 -11
- package/content/02-server-sdk/02-sessions.md +166 -15
- package/content/02-server-sdk/03-tools.md +21 -21
- package/content/02-server-sdk/04-streaming.md +50 -20
- package/content/02-server-sdk/05-cli.md +247 -0
- package/content/03-client-sdk/01-overview.md +65 -35
- package/content/03-client-sdk/02-messages.md +116 -8
- package/content/03-client-sdk/03-streaming.md +36 -17
- package/content/03-client-sdk/04-execution-blocks.md +8 -12
- package/content/03-client-sdk/05-socket-transport.md +161 -45
- package/content/03-client-sdk/06-http-transport.md +48 -24
- package/content/03-client-sdk/07-structured-output.md +412 -0
- package/content/03-client-sdk/08-file-uploads.md +473 -0
- package/content/03-client-sdk/09-error-handling.md +274 -0
- package/content/04-protocol/01-overview.md +24 -14
- package/content/04-protocol/02-input-resources.md +35 -35
- package/content/04-protocol/03-triggers.md +9 -11
- package/content/04-protocol/04-tools.md +63 -29
- package/content/04-protocol/05-skills.md +304 -0
- package/content/04-protocol/06-handlers.md +304 -0
- package/content/04-protocol/07-agent-config.md +334 -0
- package/content/04-protocol/{07-provider-options.md → 08-provider-options.md} +54 -35
- package/content/04-protocol/09-skills-advanced.md +439 -0
- package/content/04-protocol/10-types.md +719 -0
- package/content/05-api-reference/01-overview.md +20 -21
- package/content/05-api-reference/02-sessions.md +192 -37
- package/content/05-api-reference/03-agents.md +25 -37
- package/content/06-examples/01-overview.md +6 -4
- package/content/06-examples/02-nextjs-chat.md +28 -18
- package/content/06-examples/03-socket-chat.md +53 -30
- package/content/06-examples/_meta.md +0 -1
- package/dist/chunk-WJ2W3DUC.js +663 -0
- package/dist/chunk-WJ2W3DUC.js.map +1 -0
- package/dist/content.js +1 -1
- package/dist/docs.json +99 -36
- package/dist/index.js +1 -1
- package/dist/search-index.json +1 -1
- package/dist/search.js +1 -1
- package/dist/search.js.map +1 -1
- package/dist/sections.json +99 -36
- package/package.json +12 -2
- package/content/04-protocol/05-handlers.md +0 -251
- package/content/04-protocol/06-agent-config.md +0 -242
- package/dist/chunk-232K4EME.js +0 -439
- package/dist/chunk-232K4EME.js.map +0 -1
- package/dist/chunk-2JDZLMS3.js +0 -439
- package/dist/chunk-2JDZLMS3.js.map +0 -1
- package/dist/chunk-2YMRODFE.js +0 -421
- package/dist/chunk-2YMRODFE.js.map +0 -1
- package/dist/chunk-2ZBPX5QB.js +0 -421
- package/dist/chunk-2ZBPX5QB.js.map +0 -1
- package/dist/chunk-3PIIST4D.js +0 -421
- package/dist/chunk-3PIIST4D.js.map +0 -1
- package/dist/chunk-42JETGDO.js +0 -421
- package/dist/chunk-42JETGDO.js.map +0 -1
- package/dist/chunk-4WWUKU4V.js +0 -421
- package/dist/chunk-4WWUKU4V.js.map +0 -1
- package/dist/chunk-5M7DS4DF.js +0 -519
- package/dist/chunk-5M7DS4DF.js.map +0 -1
- package/dist/chunk-6JQ3OMGF.js +0 -421
- package/dist/chunk-6JQ3OMGF.js.map +0 -1
- package/dist/chunk-7AOWCJHW.js +0 -421
- package/dist/chunk-7AOWCJHW.js.map +0 -1
- package/dist/chunk-7AS4ST73.js +0 -421
- package/dist/chunk-7AS4ST73.js.map +0 -1
- package/dist/chunk-7F5WOCIL.js +0 -421
- package/dist/chunk-7F5WOCIL.js.map +0 -1
- package/dist/chunk-7FPUAWSX.js +0 -421
- package/dist/chunk-7FPUAWSX.js.map +0 -1
- package/dist/chunk-7KXF63FV.js +0 -537
- package/dist/chunk-7KXF63FV.js.map +0 -1
- package/dist/chunk-APASMJBS.js +0 -421
- package/dist/chunk-APASMJBS.js.map +0 -1
- package/dist/chunk-BCEV3WV2.js +0 -421
- package/dist/chunk-BCEV3WV2.js.map +0 -1
- package/dist/chunk-CHGY4G27.js +0 -421
- package/dist/chunk-CHGY4G27.js.map +0 -1
- package/dist/chunk-CI7JDWKU.js +0 -421
- package/dist/chunk-CI7JDWKU.js.map +0 -1
- package/dist/chunk-CVFWWRL7.js +0 -421
- package/dist/chunk-CVFWWRL7.js.map +0 -1
- package/dist/chunk-EPDM2NIJ.js +0 -421
- package/dist/chunk-EPDM2NIJ.js.map +0 -1
- package/dist/chunk-ESGSYVGK.js +0 -421
- package/dist/chunk-ESGSYVGK.js.map +0 -1
- package/dist/chunk-GDCTM2SV.js +0 -421
- package/dist/chunk-GDCTM2SV.js.map +0 -1
- package/dist/chunk-GJ6FMIPD.js +0 -421
- package/dist/chunk-GJ6FMIPD.js.map +0 -1
- package/dist/chunk-H6JGSSAJ.js +0 -519
- package/dist/chunk-H6JGSSAJ.js.map +0 -1
- package/dist/chunk-IKQHGGUZ.js +0 -421
- package/dist/chunk-IKQHGGUZ.js.map +0 -1
- package/dist/chunk-IUKE3XDN.js +0 -421
- package/dist/chunk-IUKE3XDN.js.map +0 -1
- package/dist/chunk-J26MLMLN.js +0 -421
- package/dist/chunk-J26MLMLN.js.map +0 -1
- package/dist/chunk-J7BMB3ZW.js +0 -421
- package/dist/chunk-J7BMB3ZW.js.map +0 -1
- package/dist/chunk-JCBQRD5N.js +0 -421
- package/dist/chunk-JCBQRD5N.js.map +0 -1
- package/dist/chunk-JOB6YWEF.js +0 -421
- package/dist/chunk-JOB6YWEF.js.map +0 -1
- package/dist/chunk-JZRABTHU.js +0 -519
- package/dist/chunk-JZRABTHU.js.map +0 -1
- package/dist/chunk-K3GFQUMC.js +0 -421
- package/dist/chunk-K3GFQUMC.js.map +0 -1
- package/dist/chunk-LWYMRXBF.js +0 -421
- package/dist/chunk-LWYMRXBF.js.map +0 -1
- package/dist/chunk-M2R2NDPR.js +0 -421
- package/dist/chunk-M2R2NDPR.js.map +0 -1
- package/dist/chunk-MA3P7WCA.js +0 -421
- package/dist/chunk-MA3P7WCA.js.map +0 -1
- package/dist/chunk-MDMRCS4W.mjs +0 -421
- package/dist/chunk-MDMRCS4W.mjs.map +0 -1
- package/dist/chunk-MJXTA2R6.js +0 -421
- package/dist/chunk-MJXTA2R6.js.map +0 -1
- package/dist/chunk-NFVJQNDP.js +0 -421
- package/dist/chunk-NFVJQNDP.js.map +0 -1
- package/dist/chunk-O5TLYMQP.js +0 -421
- package/dist/chunk-O5TLYMQP.js.map +0 -1
- package/dist/chunk-OECAPVSX.js +0 -439
- package/dist/chunk-OECAPVSX.js.map +0 -1
- package/dist/chunk-OL5QDJ42.js +0 -483
- package/dist/chunk-OL5QDJ42.js.map +0 -1
- package/dist/chunk-PMOVVTHO.js +0 -519
- package/dist/chunk-PMOVVTHO.js.map +0 -1
- package/dist/chunk-QCHDPR2D.js +0 -421
- package/dist/chunk-QCHDPR2D.js.map +0 -1
- package/dist/chunk-R5MTVABN.js +0 -439
- package/dist/chunk-R5MTVABN.js.map +0 -1
- package/dist/chunk-RJ4H4YVA.js +0 -519
- package/dist/chunk-RJ4H4YVA.js.map +0 -1
- package/dist/chunk-S5U4IWCR.js +0 -439
- package/dist/chunk-S5U4IWCR.js.map +0 -1
- package/dist/chunk-SCKIOGKI.js +0 -421
- package/dist/chunk-SCKIOGKI.js.map +0 -1
- package/dist/chunk-TGJSIJYP.js +0 -421
- package/dist/chunk-TGJSIJYP.js.map +0 -1
- package/dist/chunk-TQJG6EBM.js +0 -537
- package/dist/chunk-TQJG6EBM.js.map +0 -1
- package/dist/chunk-TQZRBMU7.js +0 -421
- package/dist/chunk-TQZRBMU7.js.map +0 -1
- package/dist/chunk-TRL4RSEO.js +0 -421
- package/dist/chunk-TRL4RSEO.js.map +0 -1
- package/dist/chunk-TWUMRHQ7.js +0 -421
- package/dist/chunk-TWUMRHQ7.js.map +0 -1
- package/dist/chunk-UCJE36LL.js +0 -519
- package/dist/chunk-UCJE36LL.js.map +0 -1
- package/dist/chunk-VCASA6KL.js +0 -421
- package/dist/chunk-VCASA6KL.js.map +0 -1
- package/dist/chunk-VWPQ6ORV.js +0 -421
- package/dist/chunk-VWPQ6ORV.js.map +0 -1
- package/dist/chunk-WPXKIHLT.js +0 -421
- package/dist/chunk-WPXKIHLT.js.map +0 -1
- package/dist/chunk-WUNFFJ32.js +0 -421
- package/dist/chunk-WUNFFJ32.js.map +0 -1
- package/dist/chunk-WW7TRC7S.js +0 -519
- package/dist/chunk-WW7TRC7S.js.map +0 -1
- package/dist/chunk-XVSMRXBJ.js +0 -421
- package/dist/chunk-XVSMRXBJ.js.map +0 -1
- package/dist/chunk-YPPXXV3I.js +0 -421
- package/dist/chunk-YPPXXV3I.js.map +0 -1
- package/dist/chunk-ZKZVV4OQ.js +0 -421
- package/dist/chunk-ZKZVV4OQ.js.map +0 -1
- package/dist/chunk-ZOFEX73I.js +0 -421
- package/dist/chunk-ZOFEX73I.js.map +0 -1
- package/dist/content.mjs +0 -17
- package/dist/content.mjs.map +0 -1
- package/dist/index.mjs +0 -11
- package/dist/index.mjs.map +0 -1
- package/dist/search.mjs +0 -30
- package/dist/search.mjs.map +0 -1
- package/dist/types-BltYGlWI.d.ts +0 -36
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Structured Output
|
|
3
|
+
description: Rendering structured object responses with custom UI components.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Structured Output
|
|
7
|
+
|
|
8
|
+
When 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.
|
|
9
|
+
|
|
10
|
+
## How It Works
|
|
11
|
+
|
|
12
|
+
1. The protocol defines a type and uses it as `responseType`:
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
types:
|
|
16
|
+
ChatResponse:
|
|
17
|
+
content:
|
|
18
|
+
type: string
|
|
19
|
+
description: The main response text
|
|
20
|
+
suggestions:
|
|
21
|
+
type: array
|
|
22
|
+
items:
|
|
23
|
+
type: string
|
|
24
|
+
description: Follow-up suggestions
|
|
25
|
+
|
|
26
|
+
handlers:
|
|
27
|
+
user-message:
|
|
28
|
+
Respond:
|
|
29
|
+
block: next-message
|
|
30
|
+
responseType: ChatResponse
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. The agent generates a JSON response matching the schema
|
|
34
|
+
3. The client SDK receives a `UIObjectPart` with progressive JSON parsing
|
|
35
|
+
4. Your app renders custom UI based on the `typeName`
|
|
36
|
+
|
|
37
|
+
## The UIObjectPart
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
interface UIObjectPart {
|
|
41
|
+
type: 'object';
|
|
42
|
+
id: string;
|
|
43
|
+
typeName: string; // Type name from protocol (e.g., "ChatResponse")
|
|
44
|
+
partial?: unknown; // Partial object while streaming
|
|
45
|
+
object?: unknown; // Final validated object when done
|
|
46
|
+
status: 'streaming' | 'done' | 'error';
|
|
47
|
+
error?: string; // Error message if parsing failed
|
|
48
|
+
thread?: string;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
During streaming, `partial` contains the progressively parsed object. When streaming completes, `object` contains the final validated result.
|
|
53
|
+
|
|
54
|
+
## Building a Renderer
|
|
55
|
+
|
|
56
|
+
Create a renderer component for each type you want to customize:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import type { UIObjectPart } from '@octavus/react';
|
|
60
|
+
|
|
61
|
+
interface ChatResponse {
|
|
62
|
+
content?: string;
|
|
63
|
+
suggestions?: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function ChatResponseRenderer({ part }: { part: UIObjectPart }) {
|
|
67
|
+
// Use final object if available, otherwise partial
|
|
68
|
+
const data = (part.object ?? part.partial) as ChatResponse | undefined;
|
|
69
|
+
const isStreaming = part.status === 'streaming';
|
|
70
|
+
|
|
71
|
+
if (!data) {
|
|
72
|
+
return <LoadingIndicator />;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="space-y-4">
|
|
77
|
+
{/* Main content */}
|
|
78
|
+
{data.content && (
|
|
79
|
+
<p>
|
|
80
|
+
{data.content}
|
|
81
|
+
{isStreaming && <span className="animate-pulse">▌</span>}
|
|
82
|
+
</p>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
{/* Suggestions */}
|
|
86
|
+
{data.suggestions && data.suggestions.length > 0 && (
|
|
87
|
+
<div className="flex flex-wrap gap-2">
|
|
88
|
+
{data.suggestions.map((suggestion, i) => (
|
|
89
|
+
<button key={i} className="px-3 py-1 bg-blue-100 rounded-full text-sm">
|
|
90
|
+
{suggestion}
|
|
91
|
+
</button>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Renderer Registry Pattern
|
|
101
|
+
|
|
102
|
+
For apps with multiple response types, use a registry to map type names to renderers:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import type { ComponentType } from 'react';
|
|
106
|
+
import type { UIObjectPart } from '@octavus/react';
|
|
107
|
+
|
|
108
|
+
// Define props interface
|
|
109
|
+
interface ObjectRendererProps {
|
|
110
|
+
part: UIObjectPart;
|
|
111
|
+
onSuggestionClick?: (suggestion: string) => void;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Registry type
|
|
115
|
+
type ObjectRendererRegistry = Record<string, ComponentType<ObjectRendererProps>>;
|
|
116
|
+
|
|
117
|
+
// Create registry for each agent
|
|
118
|
+
const productAdvisorRenderers: ObjectRendererRegistry = {
|
|
119
|
+
ChatResponse: ChatResponseRenderer,
|
|
120
|
+
ProductList: ProductListRenderer,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Map agents to their renderers
|
|
124
|
+
const AGENT_RENDERERS: Record<string, ObjectRendererRegistry> = {
|
|
125
|
+
'product-advisor': productAdvisorRenderers,
|
|
126
|
+
'support-chat': supportChatRenderers,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Get renderers for an agent
|
|
130
|
+
function getRenderers(agentSlug: string): ObjectRendererRegistry {
|
|
131
|
+
return AGENT_RENDERERS[agentSlug] ?? {};
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Using in Part Renderer
|
|
136
|
+
|
|
137
|
+
Integrate with your part renderer:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
function PartRenderer({ part, agentSlug }: { part: UIMessagePart; agentSlug: string }) {
|
|
141
|
+
if (part.type === 'object') {
|
|
142
|
+
const renderers = getRenderers(agentSlug);
|
|
143
|
+
const Renderer = renderers[part.typeName];
|
|
144
|
+
|
|
145
|
+
if (Renderer) {
|
|
146
|
+
return <Renderer part={part} />;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Fallback: render as formatted JSON
|
|
150
|
+
return (
|
|
151
|
+
<pre className="text-sm bg-gray-100 p-3 rounded overflow-auto">
|
|
152
|
+
{JSON.stringify(part.object ?? part.partial, null, 2)}
|
|
153
|
+
</pre>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle other part types...
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Handling Streaming State
|
|
162
|
+
|
|
163
|
+
During streaming, the object is progressively parsed. Handle incomplete data gracefully:
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
function ProductListRenderer({ part }: { part: UIObjectPart }) {
|
|
167
|
+
const data = (part.object ?? part.partial) as ProductList | undefined;
|
|
168
|
+
const isStreaming = part.status === 'streaming';
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<div className="grid grid-cols-2 gap-4">
|
|
172
|
+
{data?.products?.map((product, i) => (
|
|
173
|
+
<ProductCard
|
|
174
|
+
key={product.id ?? i}
|
|
175
|
+
product={product}
|
|
176
|
+
// Show loading state for incomplete products
|
|
177
|
+
isLoading={isStreaming && !product.name}
|
|
178
|
+
/>
|
|
179
|
+
))}
|
|
180
|
+
|
|
181
|
+
{isStreaming && <div className="animate-pulse bg-gray-200 rounded h-32" />}
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Error Handling
|
|
188
|
+
|
|
189
|
+
If JSON parsing fails, `status` will be `'error'` with details in `error`:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
function ObjectPartRenderer({ part }: { part: UIObjectPart }) {
|
|
193
|
+
if (part.status === 'error') {
|
|
194
|
+
return (
|
|
195
|
+
<div className="text-red-500 p-3 bg-red-50 rounded">
|
|
196
|
+
<p className="font-medium">Failed to parse response</p>
|
|
197
|
+
<p className="text-sm">{part.error}</p>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Normal rendering...
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Complete Example
|
|
207
|
+
|
|
208
|
+
Here's a complete chat interface with structured output support:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { useMemo } from 'react';
|
|
212
|
+
import {
|
|
213
|
+
useOctavusChat,
|
|
214
|
+
createHttpTransport,
|
|
215
|
+
type UIMessage,
|
|
216
|
+
type UIMessagePart,
|
|
217
|
+
type UIObjectPart,
|
|
218
|
+
} from '@octavus/react';
|
|
219
|
+
|
|
220
|
+
// Renderers for ChatResponse type
|
|
221
|
+
function ChatResponseRenderer({ part }: { part: UIObjectPart }) {
|
|
222
|
+
const data = part.object ?? part.partial;
|
|
223
|
+
const isStreaming = part.status === 'streaming';
|
|
224
|
+
|
|
225
|
+
if (!data) {
|
|
226
|
+
return <div className="animate-pulse h-20 bg-gray-200 rounded" />;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { content, suggestions } = data as {
|
|
230
|
+
content?: string;
|
|
231
|
+
suggestions?: string[];
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div className="space-y-3">
|
|
236
|
+
{content && (
|
|
237
|
+
<p className="text-gray-800">
|
|
238
|
+
{content}
|
|
239
|
+
{isStreaming && <span className="animate-pulse ml-1">▌</span>}
|
|
240
|
+
</p>
|
|
241
|
+
)}
|
|
242
|
+
|
|
243
|
+
{suggestions && suggestions.length > 0 && (
|
|
244
|
+
<div className="flex flex-wrap gap-2 pt-2 border-t">
|
|
245
|
+
{suggestions.map((s, i) => (
|
|
246
|
+
<span key={i} className="px-2 py-1 bg-blue-50 text-blue-700 rounded text-sm">
|
|
247
|
+
{s}
|
|
248
|
+
</span>
|
|
249
|
+
))}
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Part renderer with object support
|
|
257
|
+
function PartRenderer({ part }: { part: UIMessagePart }) {
|
|
258
|
+
switch (part.type) {
|
|
259
|
+
case 'text':
|
|
260
|
+
return <p>{part.text}</p>;
|
|
261
|
+
|
|
262
|
+
case 'object':
|
|
263
|
+
if (part.typeName === 'ChatResponse') {
|
|
264
|
+
return <ChatResponseRenderer part={part} />;
|
|
265
|
+
}
|
|
266
|
+
return <pre>{JSON.stringify(part.object ?? part.partial, null, 2)}</pre>;
|
|
267
|
+
|
|
268
|
+
default:
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Message component
|
|
274
|
+
function Message({ message }: { message: UIMessage }) {
|
|
275
|
+
return (
|
|
276
|
+
<div className={message.role === 'user' ? 'text-right' : 'text-left'}>
|
|
277
|
+
<div className="inline-block p-3 rounded-lg max-w-[80%]">
|
|
278
|
+
{message.parts.map((part, i) => (
|
|
279
|
+
<PartRenderer key={i} part={part} />
|
|
280
|
+
))}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Chat component
|
|
287
|
+
function Chat({ sessionId }: { sessionId: string }) {
|
|
288
|
+
const transport = useMemo(
|
|
289
|
+
() =>
|
|
290
|
+
createHttpTransport({
|
|
291
|
+
triggerRequest: (triggerName, input) =>
|
|
292
|
+
fetch('/api/trigger', {
|
|
293
|
+
method: 'POST',
|
|
294
|
+
headers: { 'Content-Type': 'application/json' },
|
|
295
|
+
body: JSON.stringify({ sessionId, triggerName, input }),
|
|
296
|
+
}),
|
|
297
|
+
}),
|
|
298
|
+
[sessionId],
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const { messages, status, send } = useOctavusChat({ transport });
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<div className="flex flex-col h-full">
|
|
305
|
+
<div className="flex-1 overflow-auto p-4 space-y-4">
|
|
306
|
+
{messages.map((msg) => (
|
|
307
|
+
<Message key={msg.id} message={msg} />
|
|
308
|
+
))}
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
{/* Input form... */}
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Best Practices
|
|
318
|
+
|
|
319
|
+
**Design types for progressive rendering:**
|
|
320
|
+
|
|
321
|
+
Structure your types so the most important fields stream first. Property order in YAML is preserved during streaming.
|
|
322
|
+
|
|
323
|
+
```yaml
|
|
324
|
+
types:
|
|
325
|
+
ChatResponse:
|
|
326
|
+
content: # Streams first - show immediately
|
|
327
|
+
type: string
|
|
328
|
+
suggestions: # Streams after content
|
|
329
|
+
type: array
|
|
330
|
+
items:
|
|
331
|
+
type: string
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Keep renderers resilient:**
|
|
335
|
+
|
|
336
|
+
Handle missing fields gracefully since partial objects may have undefined properties:
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
// Good - handles missing data
|
|
340
|
+
const name = product?.name ?? 'Loading...';
|
|
341
|
+
|
|
342
|
+
// Avoid - might crash on partial data
|
|
343
|
+
const name = product.name; // Error if product is undefined
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Use TypeScript for type safety:**
|
|
347
|
+
|
|
348
|
+
Define TypeScript interfaces matching your protocol types:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// Match your protocol types
|
|
352
|
+
interface ChatResponse {
|
|
353
|
+
content?: string;
|
|
354
|
+
suggestions?: string[];
|
|
355
|
+
recommendedProducts?: ProductSummary[];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
interface ProductSummary {
|
|
359
|
+
id?: string;
|
|
360
|
+
name?: string;
|
|
361
|
+
price?: number;
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Test with slow connections:**
|
|
366
|
+
|
|
367
|
+
Streaming is more noticeable on slow connections. Test your UI with network throttling to ensure a good experience.
|
|
368
|
+
|
|
369
|
+
## Type Requirements
|
|
370
|
+
|
|
371
|
+
The `responseType` in your protocol must be an **object type** (regular custom type with properties).
|
|
372
|
+
|
|
373
|
+
The following cannot be used directly as `responseType`:
|
|
374
|
+
|
|
375
|
+
- **Discriminated unions** — LLM providers don't allow `anyOf` at the schema root
|
|
376
|
+
- **Array types** — Must be wrapped in an object
|
|
377
|
+
- **Primitives** — `string`, `number`, etc. are not valid
|
|
378
|
+
|
|
379
|
+
If you need variant responses, wrap the discriminated union in an object:
|
|
380
|
+
|
|
381
|
+
```yaml
|
|
382
|
+
types:
|
|
383
|
+
# ❌ Cannot use union directly as responseType
|
|
384
|
+
ChatResponseUnion:
|
|
385
|
+
anyOf: [ContentResponse, ProductResponse]
|
|
386
|
+
discriminator: responseType
|
|
387
|
+
|
|
388
|
+
# ✅ Wrap the union in an object
|
|
389
|
+
ChatResponseWrapper:
|
|
390
|
+
response:
|
|
391
|
+
type: ChatResponseUnion
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
If you need the LLM to return an array, wrap it in an object:
|
|
395
|
+
|
|
396
|
+
```yaml
|
|
397
|
+
types:
|
|
398
|
+
# ❌ Cannot use array type as responseType
|
|
399
|
+
ProductList:
|
|
400
|
+
type: array
|
|
401
|
+
items:
|
|
402
|
+
type: Product
|
|
403
|
+
|
|
404
|
+
# ✅ Wrap the array in an object
|
|
405
|
+
ProductListResponse:
|
|
406
|
+
products:
|
|
407
|
+
type: array
|
|
408
|
+
items:
|
|
409
|
+
type: Product
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
See [Types - Structured Output](/docs/protocol/types#structured-output) for more details on defining response types.
|