@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.
Files changed (177) 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 +24 -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 +63 -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/{07-provider-options.md → 08-provider-options.md} +54 -35
  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 +99 -36
  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 +99 -36
  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 -242
  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-7KXF63FV.js +0 -537
  74. package/dist/chunk-7KXF63FV.js.map +0 -1
  75. package/dist/chunk-APASMJBS.js +0 -421
  76. package/dist/chunk-APASMJBS.js.map +0 -1
  77. package/dist/chunk-BCEV3WV2.js +0 -421
  78. package/dist/chunk-BCEV3WV2.js.map +0 -1
  79. package/dist/chunk-CHGY4G27.js +0 -421
  80. package/dist/chunk-CHGY4G27.js.map +0 -1
  81. package/dist/chunk-CI7JDWKU.js +0 -421
  82. package/dist/chunk-CI7JDWKU.js.map +0 -1
  83. package/dist/chunk-CVFWWRL7.js +0 -421
  84. package/dist/chunk-CVFWWRL7.js.map +0 -1
  85. package/dist/chunk-EPDM2NIJ.js +0 -421
  86. package/dist/chunk-EPDM2NIJ.js.map +0 -1
  87. package/dist/chunk-ESGSYVGK.js +0 -421
  88. package/dist/chunk-ESGSYVGK.js.map +0 -1
  89. package/dist/chunk-GDCTM2SV.js +0 -421
  90. package/dist/chunk-GDCTM2SV.js.map +0 -1
  91. package/dist/chunk-GJ6FMIPD.js +0 -421
  92. package/dist/chunk-GJ6FMIPD.js.map +0 -1
  93. package/dist/chunk-H6JGSSAJ.js +0 -519
  94. package/dist/chunk-H6JGSSAJ.js.map +0 -1
  95. package/dist/chunk-IKQHGGUZ.js +0 -421
  96. package/dist/chunk-IKQHGGUZ.js.map +0 -1
  97. package/dist/chunk-IUKE3XDN.js +0 -421
  98. package/dist/chunk-IUKE3XDN.js.map +0 -1
  99. package/dist/chunk-J26MLMLN.js +0 -421
  100. package/dist/chunk-J26MLMLN.js.map +0 -1
  101. package/dist/chunk-J7BMB3ZW.js +0 -421
  102. package/dist/chunk-J7BMB3ZW.js.map +0 -1
  103. package/dist/chunk-JCBQRD5N.js +0 -421
  104. package/dist/chunk-JCBQRD5N.js.map +0 -1
  105. package/dist/chunk-JOB6YWEF.js +0 -421
  106. package/dist/chunk-JOB6YWEF.js.map +0 -1
  107. package/dist/chunk-JZRABTHU.js +0 -519
  108. package/dist/chunk-JZRABTHU.js.map +0 -1
  109. package/dist/chunk-K3GFQUMC.js +0 -421
  110. package/dist/chunk-K3GFQUMC.js.map +0 -1
  111. package/dist/chunk-LWYMRXBF.js +0 -421
  112. package/dist/chunk-LWYMRXBF.js.map +0 -1
  113. package/dist/chunk-M2R2NDPR.js +0 -421
  114. package/dist/chunk-M2R2NDPR.js.map +0 -1
  115. package/dist/chunk-MA3P7WCA.js +0 -421
  116. package/dist/chunk-MA3P7WCA.js.map +0 -1
  117. package/dist/chunk-MDMRCS4W.mjs +0 -421
  118. package/dist/chunk-MDMRCS4W.mjs.map +0 -1
  119. package/dist/chunk-MJXTA2R6.js +0 -421
  120. package/dist/chunk-MJXTA2R6.js.map +0 -1
  121. package/dist/chunk-NFVJQNDP.js +0 -421
  122. package/dist/chunk-NFVJQNDP.js.map +0 -1
  123. package/dist/chunk-O5TLYMQP.js +0 -421
  124. package/dist/chunk-O5TLYMQP.js.map +0 -1
  125. package/dist/chunk-OECAPVSX.js +0 -439
  126. package/dist/chunk-OECAPVSX.js.map +0 -1
  127. package/dist/chunk-OL5QDJ42.js +0 -483
  128. package/dist/chunk-OL5QDJ42.js.map +0 -1
  129. package/dist/chunk-PMOVVTHO.js +0 -519
  130. package/dist/chunk-PMOVVTHO.js.map +0 -1
  131. package/dist/chunk-QCHDPR2D.js +0 -421
  132. package/dist/chunk-QCHDPR2D.js.map +0 -1
  133. package/dist/chunk-R5MTVABN.js +0 -439
  134. package/dist/chunk-R5MTVABN.js.map +0 -1
  135. package/dist/chunk-RJ4H4YVA.js +0 -519
  136. package/dist/chunk-RJ4H4YVA.js.map +0 -1
  137. package/dist/chunk-S5U4IWCR.js +0 -439
  138. package/dist/chunk-S5U4IWCR.js.map +0 -1
  139. package/dist/chunk-SCKIOGKI.js +0 -421
  140. package/dist/chunk-SCKIOGKI.js.map +0 -1
  141. package/dist/chunk-TGJSIJYP.js +0 -421
  142. package/dist/chunk-TGJSIJYP.js.map +0 -1
  143. package/dist/chunk-TQJG6EBM.js +0 -537
  144. package/dist/chunk-TQJG6EBM.js.map +0 -1
  145. package/dist/chunk-TQZRBMU7.js +0 -421
  146. package/dist/chunk-TQZRBMU7.js.map +0 -1
  147. package/dist/chunk-TRL4RSEO.js +0 -421
  148. package/dist/chunk-TRL4RSEO.js.map +0 -1
  149. package/dist/chunk-TWUMRHQ7.js +0 -421
  150. package/dist/chunk-TWUMRHQ7.js.map +0 -1
  151. package/dist/chunk-UCJE36LL.js +0 -519
  152. package/dist/chunk-UCJE36LL.js.map +0 -1
  153. package/dist/chunk-VCASA6KL.js +0 -421
  154. package/dist/chunk-VCASA6KL.js.map +0 -1
  155. package/dist/chunk-VWPQ6ORV.js +0 -421
  156. package/dist/chunk-VWPQ6ORV.js.map +0 -1
  157. package/dist/chunk-WPXKIHLT.js +0 -421
  158. package/dist/chunk-WPXKIHLT.js.map +0 -1
  159. package/dist/chunk-WUNFFJ32.js +0 -421
  160. package/dist/chunk-WUNFFJ32.js.map +0 -1
  161. package/dist/chunk-WW7TRC7S.js +0 -519
  162. package/dist/chunk-WW7TRC7S.js.map +0 -1
  163. package/dist/chunk-XVSMRXBJ.js +0 -421
  164. package/dist/chunk-XVSMRXBJ.js.map +0 -1
  165. package/dist/chunk-YPPXXV3I.js +0 -421
  166. package/dist/chunk-YPPXXV3I.js.map +0 -1
  167. package/dist/chunk-ZKZVV4OQ.js +0 -421
  168. package/dist/chunk-ZKZVV4OQ.js.map +0 -1
  169. package/dist/chunk-ZOFEX73I.js +0 -421
  170. package/dist/chunk-ZOFEX73I.js.map +0 -1
  171. package/dist/content.mjs +0 -17
  172. package/dist/content.mjs.map +0 -1
  173. package/dist/index.mjs +0 -11
  174. package/dist/index.mjs.map +0 -1
  175. package/dist/search.mjs +0 -30
  176. package/dist/search.mjs.map +0 -1
  177. 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.