ai-functions 2.1.3 → 2.4.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 (284) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +90 -1
  3. package/README.md +38 -0
  4. package/dist/ai-promise.d.ts +3 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +135 -64
  7. package/dist/ai-promise.js.map +1 -1
  8. package/dist/ai-schemas.d.ts +56 -0
  9. package/dist/ai-schemas.d.ts.map +1 -0
  10. package/dist/ai-schemas.js +53 -0
  11. package/dist/ai-schemas.js.map +1 -0
  12. package/dist/ai.d.ts +16 -242
  13. package/dist/ai.d.ts.map +1 -1
  14. package/dist/ai.js +51 -858
  15. package/dist/ai.js.map +1 -1
  16. package/dist/batch/anthropic.d.ts +6 -4
  17. package/dist/batch/anthropic.d.ts.map +1 -1
  18. package/dist/batch/anthropic.js +83 -145
  19. package/dist/batch/anthropic.js.map +1 -1
  20. package/dist/batch/bedrock.d.ts +8 -30
  21. package/dist/batch/bedrock.d.ts.map +1 -1
  22. package/dist/batch/bedrock.js +155 -338
  23. package/dist/batch/bedrock.js.map +1 -1
  24. package/dist/batch/cloudflare.d.ts +8 -20
  25. package/dist/batch/cloudflare.d.ts.map +1 -1
  26. package/dist/batch/cloudflare.js +68 -189
  27. package/dist/batch/cloudflare.js.map +1 -1
  28. package/dist/batch/google.d.ts +6 -20
  29. package/dist/batch/google.d.ts.map +1 -1
  30. package/dist/batch/google.js +70 -238
  31. package/dist/batch/google.js.map +1 -1
  32. package/dist/batch/index.d.ts +4 -1
  33. package/dist/batch/index.d.ts.map +1 -1
  34. package/dist/batch/index.js +4 -1
  35. package/dist/batch/index.js.map +1 -1
  36. package/dist/batch/memory.d.ts +1 -1
  37. package/dist/batch/memory.d.ts.map +1 -1
  38. package/dist/batch/memory.js +14 -10
  39. package/dist/batch/memory.js.map +1 -1
  40. package/dist/batch/openai.d.ts +11 -14
  41. package/dist/batch/openai.d.ts.map +1 -1
  42. package/dist/batch/openai.js +52 -156
  43. package/dist/batch/openai.js.map +1 -1
  44. package/dist/batch/provider.d.ts +111 -0
  45. package/dist/batch/provider.d.ts.map +1 -0
  46. package/dist/batch/provider.js +233 -0
  47. package/dist/batch/provider.js.map +1 -0
  48. package/dist/batch-map.d.ts.map +1 -1
  49. package/dist/batch-map.js +23 -17
  50. package/dist/batch-map.js.map +1 -1
  51. package/dist/batch-queue.d.ts +65 -0
  52. package/dist/batch-queue.d.ts.map +1 -1
  53. package/dist/batch-queue.js +169 -14
  54. package/dist/batch-queue.js.map +1 -1
  55. package/dist/budget.d.ts.map +1 -1
  56. package/dist/budget.js +27 -14
  57. package/dist/budget.js.map +1 -1
  58. package/dist/cache.d.ts +23 -0
  59. package/dist/cache.d.ts.map +1 -1
  60. package/dist/cache.js +36 -15
  61. package/dist/cache.js.map +1 -1
  62. package/dist/context.d.ts +26 -8
  63. package/dist/context.d.ts.map +1 -1
  64. package/dist/context.js +64 -62
  65. package/dist/context.js.map +1 -1
  66. package/dist/digital-objects-registry.d.ts +229 -0
  67. package/dist/digital-objects-registry.d.ts.map +1 -0
  68. package/dist/digital-objects-registry.js +617 -0
  69. package/dist/digital-objects-registry.js.map +1 -0
  70. package/dist/embeddings.d.ts +2 -2
  71. package/dist/embeddings.d.ts.map +1 -1
  72. package/dist/errors.d.ts +22 -0
  73. package/dist/errors.d.ts.map +1 -0
  74. package/dist/errors.js +35 -0
  75. package/dist/errors.js.map +1 -0
  76. package/dist/eval/runner.d.ts +8 -0
  77. package/dist/eval/runner.d.ts.map +1 -1
  78. package/dist/eval/runner.js +41 -35
  79. package/dist/eval/runner.js.map +1 -1
  80. package/dist/eval-log/in-memory.d.ts +34 -0
  81. package/dist/eval-log/in-memory.d.ts.map +1 -0
  82. package/dist/eval-log/in-memory.js +84 -0
  83. package/dist/eval-log/in-memory.js.map +1 -0
  84. package/dist/eval-log/index.d.ts +29 -0
  85. package/dist/eval-log/index.d.ts.map +1 -0
  86. package/dist/eval-log/index.js +39 -0
  87. package/dist/eval-log/index.js.map +1 -0
  88. package/dist/eval-log/types.d.ts +101 -0
  89. package/dist/eval-log/types.d.ts.map +1 -0
  90. package/dist/eval-log/types.js +16 -0
  91. package/dist/eval-log/types.js.map +1 -0
  92. package/dist/function-registry.d.ts +176 -0
  93. package/dist/function-registry.d.ts.map +1 -0
  94. package/dist/function-registry.js +685 -0
  95. package/dist/function-registry.js.map +1 -0
  96. package/dist/generate.d.ts +9 -3
  97. package/dist/generate.d.ts.map +1 -1
  98. package/dist/generate.js +18 -18
  99. package/dist/generate.js.map +1 -1
  100. package/dist/index.d.ts +18 -11
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +35 -18
  103. package/dist/index.js.map +1 -1
  104. package/dist/logger.d.ts +118 -0
  105. package/dist/logger.d.ts.map +1 -0
  106. package/dist/logger.js +187 -0
  107. package/dist/logger.js.map +1 -0
  108. package/dist/middleware/budget.d.ts +84 -0
  109. package/dist/middleware/budget.d.ts.map +1 -0
  110. package/dist/middleware/budget.js +110 -0
  111. package/dist/middleware/budget.js.map +1 -0
  112. package/dist/middleware/cache.d.ts +103 -0
  113. package/dist/middleware/cache.d.ts.map +1 -0
  114. package/dist/middleware/cache.js +228 -0
  115. package/dist/middleware/cache.js.map +1 -0
  116. package/dist/middleware/embed-cache.d.ts +99 -0
  117. package/dist/middleware/embed-cache.d.ts.map +1 -0
  118. package/dist/middleware/embed-cache.js +128 -0
  119. package/dist/middleware/embed-cache.js.map +1 -0
  120. package/dist/middleware/index.d.ts +11 -0
  121. package/dist/middleware/index.d.ts.map +1 -0
  122. package/dist/middleware/index.js +11 -0
  123. package/dist/middleware/index.js.map +1 -0
  124. package/dist/middleware/trace.d.ts +103 -0
  125. package/dist/middleware/trace.d.ts.map +1 -0
  126. package/dist/middleware/trace.js +176 -0
  127. package/dist/middleware/trace.js.map +1 -0
  128. package/dist/primitives.d.ts +120 -1
  129. package/dist/primitives.d.ts.map +1 -1
  130. package/dist/primitives.js +398 -26
  131. package/dist/primitives.js.map +1 -1
  132. package/dist/retry.d.ts +66 -1
  133. package/dist/retry.d.ts.map +1 -1
  134. package/dist/retry.js +115 -8
  135. package/dist/retry.js.map +1 -1
  136. package/dist/sandbox.d.ts +36 -0
  137. package/dist/sandbox.d.ts.map +1 -0
  138. package/dist/sandbox.js +44 -0
  139. package/dist/sandbox.js.map +1 -0
  140. package/dist/schema.js +2 -2
  141. package/dist/schema.js.map +1 -1
  142. package/dist/telemetry.d.ts +128 -0
  143. package/dist/telemetry.d.ts.map +1 -0
  144. package/dist/telemetry.js +285 -0
  145. package/dist/telemetry.js.map +1 -0
  146. package/dist/template.d.ts.map +1 -1
  147. package/dist/template.js +6 -1
  148. package/dist/template.js.map +1 -1
  149. package/dist/tool-orchestration.d.ts +66 -4
  150. package/dist/tool-orchestration.d.ts.map +1 -1
  151. package/dist/tool-orchestration.js +123 -23
  152. package/dist/tool-orchestration.js.map +1 -1
  153. package/dist/type-guards.d.ts +28 -0
  154. package/dist/type-guards.d.ts.map +1 -0
  155. package/dist/type-guards.js +29 -0
  156. package/dist/type-guards.js.map +1 -0
  157. package/dist/types.d.ts +155 -19
  158. package/dist/types.d.ts.map +1 -1
  159. package/dist/types.js +36 -1
  160. package/dist/types.js.map +1 -1
  161. package/dist/wrap-for-v3.d.ts +80 -0
  162. package/dist/wrap-for-v3.d.ts.map +1 -0
  163. package/dist/wrap-for-v3.js +89 -0
  164. package/dist/wrap-for-v3.js.map +1 -0
  165. package/examples/00-quickstart.ts +232 -0
  166. package/examples/01-rag-chatbot.ts +212 -0
  167. package/examples/02-multi-agent-research.ts +290 -0
  168. package/examples/03-email-classification.ts +379 -0
  169. package/examples/04-content-moderation.ts +400 -0
  170. package/examples/05-document-extraction.ts +455 -0
  171. package/examples/06-streaming-chat-nextjs.ts +437 -0
  172. package/examples/07-cloudflare-worker.ts +483 -0
  173. package/examples/08-batch-processing.ts +491 -0
  174. package/examples/09-budget-constrained.ts +527 -0
  175. package/examples/10-tool-orchestration.ts +565 -0
  176. package/examples/11-retry-resilience.ts +403 -0
  177. package/examples/12-caching-strategies.ts +422 -0
  178. package/examples/README.md +145 -0
  179. package/package.json +29 -25
  180. package/src/ai-promise.ts +226 -140
  181. package/src/ai-schemas.ts +122 -0
  182. package/src/ai.ts +71 -1176
  183. package/src/batch/anthropic.ts +96 -161
  184. package/src/batch/bedrock.ts +203 -454
  185. package/src/batch/cloudflare.ts +99 -282
  186. package/src/batch/google.ts +91 -297
  187. package/src/batch/index.ts +4 -1
  188. package/src/batch/memory.ts +15 -10
  189. package/src/batch/openai.ts +65 -193
  190. package/src/batch/provider.ts +336 -0
  191. package/src/batch-map.ts +29 -24
  192. package/src/batch-queue.ts +200 -11
  193. package/src/budget.ts +31 -18
  194. package/src/cache.ts +45 -17
  195. package/src/context.ts +106 -77
  196. package/src/digital-objects-registry.ts +750 -0
  197. package/src/errors.ts +37 -0
  198. package/src/eval/runner.ts +60 -36
  199. package/src/eval-log/in-memory.ts +90 -0
  200. package/src/eval-log/index.ts +46 -0
  201. package/src/eval-log/types.ts +110 -0
  202. package/src/function-registry.ts +874 -0
  203. package/src/generate.ts +33 -28
  204. package/src/index.ts +122 -21
  205. package/src/logger.ts +232 -0
  206. package/src/middleware/budget.ts +171 -0
  207. package/src/middleware/cache.ts +299 -0
  208. package/src/middleware/embed-cache.ts +195 -0
  209. package/src/middleware/index.ts +23 -0
  210. package/src/middleware/trace.ts +248 -0
  211. package/src/primitives.ts +589 -62
  212. package/src/retry.ts +144 -18
  213. package/src/sandbox.ts +52 -0
  214. package/src/schema.ts +8 -8
  215. package/src/telemetry.ts +403 -0
  216. package/src/template.ts +8 -4
  217. package/src/tool-orchestration.ts +213 -48
  218. package/src/type-guards.ts +31 -0
  219. package/src/types.ts +186 -27
  220. package/src/wrap-for-v3.ts +105 -0
  221. package/test/ai-promise.test.ts +1080 -0
  222. package/test/ai-proxy.test.ts +1 -1
  223. package/test/batch-autosubmit-errors.test.ts +49 -37
  224. package/test/batch-blog-posts.test.ts +87 -129
  225. package/test/core-functions.test.ts +183 -579
  226. package/test/decide.test.ts +154 -322
  227. package/test/define.test.ts +211 -8
  228. package/test/digital-objects-registry.test.ts +760 -0
  229. package/test/embedding-cache-middleware.test.ts +140 -0
  230. package/test/fill-template.test.ts +89 -0
  231. package/test/generate-core.test.ts +140 -229
  232. package/test/implicit-batch.test.ts +22 -65
  233. package/test/retry-policy-integration.test.ts +117 -0
  234. package/test/sandbox-execution.test.ts +155 -0
  235. package/test/schema.test.ts +55 -19
  236. package/test/template.test.ts +1164 -0
  237. package/test/tool-orchestration.test.ts +270 -0
  238. package/test/wrap-for-v3.test.ts +612 -0
  239. package/vitest.config.js +6 -0
  240. package/vitest.config.ts +20 -0
  241. package/LICENSE +0 -21
  242. package/dist/rpc/auth.d.ts +0 -69
  243. package/dist/rpc/auth.d.ts.map +0 -1
  244. package/dist/rpc/auth.js +0 -136
  245. package/dist/rpc/auth.js.map +0 -1
  246. package/dist/rpc/client.d.ts +0 -62
  247. package/dist/rpc/client.d.ts.map +0 -1
  248. package/dist/rpc/client.js +0 -103
  249. package/dist/rpc/client.js.map +0 -1
  250. package/dist/rpc/deferred.d.ts +0 -60
  251. package/dist/rpc/deferred.d.ts.map +0 -1
  252. package/dist/rpc/deferred.js +0 -96
  253. package/dist/rpc/deferred.js.map +0 -1
  254. package/dist/rpc/index.d.ts +0 -22
  255. package/dist/rpc/index.d.ts.map +0 -1
  256. package/dist/rpc/index.js +0 -38
  257. package/dist/rpc/index.js.map +0 -1
  258. package/dist/rpc/local.d.ts +0 -42
  259. package/dist/rpc/local.d.ts.map +0 -1
  260. package/dist/rpc/local.js +0 -50
  261. package/dist/rpc/local.js.map +0 -1
  262. package/dist/rpc/server.d.ts +0 -165
  263. package/dist/rpc/server.d.ts.map +0 -1
  264. package/dist/rpc/server.js +0 -405
  265. package/dist/rpc/server.js.map +0 -1
  266. package/dist/rpc/session.d.ts +0 -32
  267. package/dist/rpc/session.d.ts.map +0 -1
  268. package/dist/rpc/session.js +0 -43
  269. package/dist/rpc/session.js.map +0 -1
  270. package/dist/rpc/transport.d.ts +0 -306
  271. package/dist/rpc/transport.d.ts.map +0 -1
  272. package/dist/rpc/transport.js +0 -731
  273. package/dist/rpc/transport.js.map +0 -1
  274. package/src/batch/anthropic.js +0 -256
  275. package/src/batch/bedrock.js +0 -584
  276. package/src/batch/cloudflare.js +0 -287
  277. package/src/batch/google.js +0 -359
  278. package/src/batch/index.js +0 -30
  279. package/src/batch/memory.js +0 -187
  280. package/src/batch/openai.js +0 -402
  281. package/src/eval/index.js +0 -7
  282. package/src/eval/models.js +0 -119
  283. package/src/eval/runner.js +0 -147
  284. package/test/schema.test.js +0 -96
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Streaming Chat UI Example (Next.js Compatible)
3
+ *
4
+ * This example demonstrates building a streaming chat interface using ai-functions.
5
+ * It shows how to:
6
+ * - Stream responses in real-time
7
+ * - Handle partial object streaming
8
+ * - Integrate with frontend frameworks
9
+ * - Manage conversation state
10
+ *
11
+ * Note: This example simulates the streaming behavior. In a real Next.js app,
12
+ * you would use this in an API route or server action.
13
+ *
14
+ * @example
15
+ * ```bash
16
+ * ANTHROPIC_API_KEY=sk-... npx tsx examples/06-streaming-chat-nextjs.ts
17
+ * ```
18
+ */
19
+
20
+ import { ai, write, list, configure, AIPromise } from '../src/index.js'
21
+
22
+ // ============================================================================
23
+ // Types
24
+ // ============================================================================
25
+
26
+ interface Message {
27
+ id: string
28
+ role: 'user' | 'assistant'
29
+ content: string
30
+ timestamp: Date
31
+ streaming?: boolean
32
+ metadata?: {
33
+ model?: string
34
+ tokens?: number
35
+ latencyMs?: number
36
+ }
37
+ }
38
+
39
+ interface ChatState {
40
+ messages: Message[]
41
+ isStreaming: boolean
42
+ error?: string
43
+ }
44
+
45
+ interface StreamChunk {
46
+ type: 'text' | 'partial' | 'complete' | 'error'
47
+ content: string
48
+ partial?: unknown
49
+ }
50
+
51
+ // ============================================================================
52
+ // Chat Implementation
53
+ // ============================================================================
54
+
55
+ class StreamingChat {
56
+ private messages: Message[] = []
57
+ private model: string
58
+ private systemPrompt: string
59
+
60
+ constructor(options: { model?: string; systemPrompt?: string } = {}) {
61
+ this.model = options.model || 'sonnet'
62
+ this.systemPrompt =
63
+ options.systemPrompt ||
64
+ 'You are a helpful assistant. Be concise but thorough in your responses.'
65
+ }
66
+
67
+ /**
68
+ * Get conversation history
69
+ */
70
+ getMessages(): Message[] {
71
+ return [...this.messages]
72
+ }
73
+
74
+ /**
75
+ * Add a user message
76
+ */
77
+ addUserMessage(content: string): Message {
78
+ const message: Message = {
79
+ id: `msg-${Date.now()}`,
80
+ role: 'user',
81
+ content,
82
+ timestamp: new Date(),
83
+ }
84
+ this.messages.push(message)
85
+ return message
86
+ }
87
+
88
+ /**
89
+ * Send a message and get a streaming response
90
+ *
91
+ * In Next.js, this would be used in an API route:
92
+ * ```ts
93
+ * // app/api/chat/route.ts
94
+ * export async function POST(req: Request) {
95
+ * const { message } = await req.json()
96
+ * const stream = chat.streamResponse(message)
97
+ *
98
+ * return new Response(stream, {
99
+ * headers: { 'Content-Type': 'text/event-stream' }
100
+ * })
101
+ * }
102
+ * ```
103
+ */
104
+ async *streamResponse(userMessage: string): AsyncGenerator<StreamChunk> {
105
+ // Add user message
106
+ this.addUserMessage(userMessage)
107
+
108
+ // Build context from conversation history
109
+ const context = this.messages
110
+ .slice(-10) // Last 10 messages for context
111
+ .map((m) => `${m.role}: ${m.content}`)
112
+ .join('\n')
113
+
114
+ const startTime = Date.now()
115
+
116
+ try {
117
+ // Create the AI prompt
118
+ const response = write`${this.systemPrompt}
119
+
120
+ Conversation history:
121
+ ${context}
122
+
123
+ Respond to the user's last message naturally and helpfully.`
124
+
125
+ // Get the streaming interface
126
+ const stream = response.stream()
127
+
128
+ // Create placeholder assistant message
129
+ const assistantMessage: Message = {
130
+ id: `msg-${Date.now()}`,
131
+ role: 'assistant',
132
+ content: '',
133
+ timestamp: new Date(),
134
+ streaming: true,
135
+ }
136
+ this.messages.push(assistantMessage)
137
+
138
+ let fullContent = ''
139
+
140
+ // Stream text chunks
141
+ for await (const chunk of stream.textStream) {
142
+ fullContent += chunk
143
+ assistantMessage.content = fullContent
144
+
145
+ yield {
146
+ type: 'text',
147
+ content: chunk,
148
+ }
149
+ }
150
+
151
+ // Finalize message
152
+ assistantMessage.streaming = false
153
+ assistantMessage.metadata = {
154
+ model: this.model,
155
+ latencyMs: Date.now() - startTime,
156
+ }
157
+
158
+ yield {
159
+ type: 'complete',
160
+ content: fullContent,
161
+ }
162
+ } catch (error) {
163
+ yield {
164
+ type: 'error',
165
+ content: (error as Error).message,
166
+ }
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Get a non-streaming response (simpler for testing)
172
+ */
173
+ async sendMessage(userMessage: string): Promise<Message> {
174
+ this.addUserMessage(userMessage)
175
+
176
+ const context = this.messages
177
+ .slice(-10)
178
+ .map((m) => `${m.role}: ${m.content}`)
179
+ .join('\n')
180
+
181
+ const startTime = Date.now()
182
+
183
+ const response = await write`${this.systemPrompt}
184
+
185
+ Conversation history:
186
+ ${context}
187
+
188
+ Respond to the user's last message naturally and helpfully.`
189
+
190
+ const assistantMessage: Message = {
191
+ id: `msg-${Date.now()}`,
192
+ role: 'assistant',
193
+ content: response,
194
+ timestamp: new Date(),
195
+ metadata: {
196
+ model: this.model,
197
+ latencyMs: Date.now() - startTime,
198
+ },
199
+ }
200
+
201
+ this.messages.push(assistantMessage)
202
+ return assistantMessage
203
+ }
204
+
205
+ /**
206
+ * Clear conversation
207
+ */
208
+ clear(): void {
209
+ this.messages = []
210
+ }
211
+ }
212
+
213
+ // ============================================================================
214
+ // Structured Response Streaming
215
+ // ============================================================================
216
+
217
+ interface AnalysisResult {
218
+ summary: string
219
+ keyPoints: string[]
220
+ sentiment: string
221
+ confidence: number
222
+ }
223
+
224
+ async function streamStructuredResponse(text: string): Promise<void> {
225
+ console.log('\n--- Structured Response Streaming ---')
226
+ console.log('Analyzing text with streaming partial objects...\n')
227
+
228
+ const analysis = ai`Analyze this text:
229
+ "${text}"
230
+
231
+ Provide:
232
+ - summary: brief summary of the text
233
+ - keyPoints: array of 3-5 key points
234
+ - sentiment: positive/negative/neutral
235
+ - confidence: confidence score 0-1`
236
+
237
+ // Access properties to set schema
238
+ const { summary, keyPoints, sentiment, confidence } = analysis
239
+
240
+ const stream = analysis.stream()
241
+
242
+ console.log('Streaming partial objects:')
243
+ for await (const partial of stream.partialObjectStream) {
244
+ // Clear line and show current partial object
245
+ process.stdout.write('\r\x1b[K')
246
+ const p = partial as Partial<AnalysisResult>
247
+ const status = [
248
+ p.summary ? 'summary' : '',
249
+ p.keyPoints?.length ? `keyPoints(${p.keyPoints.length})` : '',
250
+ p.sentiment ? 'sentiment' : '',
251
+ p.confidence !== undefined ? 'confidence' : '',
252
+ ]
253
+ .filter(Boolean)
254
+ .join(', ')
255
+ process.stdout.write(`Received: ${status}`)
256
+ }
257
+
258
+ const result = await stream.result
259
+ console.log('\n\nFinal result:', JSON.stringify(result, null, 2))
260
+ }
261
+
262
+ // ============================================================================
263
+ // React Hooks Simulation (for documentation)
264
+ // ============================================================================
265
+
266
+ /**
267
+ * Example React hook for streaming chat (for reference)
268
+ *
269
+ * ```tsx
270
+ * // hooks/useStreamingChat.ts
271
+ * import { useState, useCallback } from 'react'
272
+ *
273
+ * export function useStreamingChat() {
274
+ * const [messages, setMessages] = useState<Message[]>([])
275
+ * const [isStreaming, setIsStreaming] = useState(false)
276
+ *
277
+ * const sendMessage = useCallback(async (content: string) => {
278
+ * setIsStreaming(true)
279
+ *
280
+ * // Add user message
281
+ * const userMsg: Message = {
282
+ * id: Date.now().toString(),
283
+ * role: 'user',
284
+ * content,
285
+ * timestamp: new Date(),
286
+ * }
287
+ * setMessages(prev => [...prev, userMsg])
288
+ *
289
+ * // Create placeholder for assistant
290
+ * const assistantMsg: Message = {
291
+ * id: (Date.now() + 1).toString(),
292
+ * role: 'assistant',
293
+ * content: '',
294
+ * timestamp: new Date(),
295
+ * streaming: true,
296
+ * }
297
+ * setMessages(prev => [...prev, assistantMsg])
298
+ *
299
+ * // Stream from API
300
+ * const response = await fetch('/api/chat', {
301
+ * method: 'POST',
302
+ * body: JSON.stringify({ message: content }),
303
+ * })
304
+ *
305
+ * const reader = response.body?.getReader()
306
+ * const decoder = new TextDecoder()
307
+ *
308
+ * while (reader) {
309
+ * const { done, value } = await reader.read()
310
+ * if (done) break
311
+ *
312
+ * const chunk = decoder.decode(value)
313
+ * setMessages(prev => {
314
+ * const msgs = [...prev]
315
+ * const last = msgs[msgs.length - 1]
316
+ * if (last.role === 'assistant') {
317
+ * last.content += chunk
318
+ * }
319
+ * return msgs
320
+ * })
321
+ * }
322
+ *
323
+ * setIsStreaming(false)
324
+ * }, [])
325
+ *
326
+ * return { messages, isStreaming, sendMessage }
327
+ * }
328
+ * ```
329
+ */
330
+
331
+ // ============================================================================
332
+ // Terminal UI Simulation
333
+ // ============================================================================
334
+
335
+ function displayMessage(message: Message): void {
336
+ const roleColor = message.role === 'user' ? '\x1b[36m' : '\x1b[32m'
337
+ const reset = '\x1b[0m'
338
+ const prefix = message.role === 'user' ? 'You' : 'Assistant'
339
+
340
+ console.log(`\n${roleColor}${prefix}:${reset} ${message.content}`)
341
+
342
+ if (message.metadata?.latencyMs) {
343
+ console.log(`\x1b[90m (${message.metadata.latencyMs}ms)${reset}`)
344
+ }
345
+ }
346
+
347
+ async function simulateStreamingUI(chat: StreamingChat, message: string): Promise<void> {
348
+ console.log(`\n\x1b[36mYou:\x1b[0m ${message}`)
349
+
350
+ // Add user message
351
+ chat.addUserMessage(message)
352
+
353
+ // Build response like we would in the streaming method
354
+ const context = chat
355
+ .getMessages()
356
+ .slice(-10)
357
+ .map((m) => `${m.role}: ${m.content}`)
358
+ .join('\n')
359
+
360
+ process.stdout.write('\n\x1b[32mAssistant:\x1b[0m ')
361
+
362
+ const response = write`You are a helpful assistant. Be concise but thorough.
363
+
364
+ Conversation history:
365
+ ${context}
366
+
367
+ Respond to the user's last message naturally and helpfully.`
368
+
369
+ // Since streaming requires gateway, we'll show non-streaming with simulated delay
370
+ const fullResponse = await response
371
+
372
+ // Simulate streaming output
373
+ for (const char of fullResponse) {
374
+ process.stdout.write(char)
375
+ await new Promise((r) => setTimeout(r, 10))
376
+ }
377
+
378
+ console.log('')
379
+ }
380
+
381
+ // ============================================================================
382
+ // Main Example
383
+ // ============================================================================
384
+
385
+ async function main() {
386
+ console.log('\n=== Streaming Chat UI Example ===\n')
387
+
388
+ // Configure the AI provider
389
+ configure({
390
+ model: 'sonnet',
391
+ provider: 'anthropic',
392
+ })
393
+
394
+ // Create chat instance
395
+ const chat = new StreamingChat({
396
+ systemPrompt:
397
+ 'You are a friendly and helpful assistant. Keep responses concise but informative.',
398
+ })
399
+
400
+ console.log('Simulating a streaming chat interface...')
401
+ console.log('(In Next.js, you would use Server-Sent Events or WebSockets)\n')
402
+
403
+ // Simulate a conversation
404
+ const conversation = [
405
+ 'What are the key features of ai-functions?',
406
+ 'How does streaming work?',
407
+ 'Can you give me a code example?',
408
+ ]
409
+
410
+ for (const message of conversation) {
411
+ await simulateStreamingUI(chat, message)
412
+ console.log('')
413
+ await new Promise((r) => setTimeout(r, 500))
414
+ }
415
+
416
+ // Show structured streaming
417
+ await streamStructuredResponse(
418
+ 'The new product launch exceeded expectations with a 40% increase in sales.'
419
+ )
420
+
421
+ // Display conversation summary
422
+ console.log('\n--- Conversation Summary ---')
423
+ const messages = chat.getMessages()
424
+ console.log(`Total messages: ${messages.length}`)
425
+ console.log(`User messages: ${messages.filter((m) => m.role === 'user').length}`)
426
+ console.log(`Assistant messages: ${messages.filter((m) => m.role === 'assistant').length}`)
427
+ }
428
+
429
+ main()
430
+ .then(() => {
431
+ console.log('\n=== Example Complete ===\n')
432
+ process.exit(0)
433
+ })
434
+ .catch((error) => {
435
+ console.error('\nError:', error.message)
436
+ process.exit(1)
437
+ })