@ai-sdk/angular 0.0.0-02dba89b-20251009204516

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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2023 Vercel, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,643 @@
1
+ # AI SDK Angular
2
+
3
+ Angular UI components for the [AI SDK v5](https://ai-sdk.dev/docs).
4
+
5
+ ## Overview
6
+
7
+ The `@ai-sdk/angular` package provides Angular-specific implementations using Angular signals for reactive state management:
8
+
9
+ - **Chat** - Multi-turn conversations with streaming responses
10
+ - **Completion** - Single-turn text generation
11
+ - **StructuredObject** - Type-safe object generation with Zod schemas
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @ai-sdk/angular ai
17
+ ```
18
+
19
+ ### Peer Dependencies
20
+
21
+ - Angular 16+ (`@angular/core`)
22
+ - Zod v3+ (optional, for structured objects)
23
+
24
+ ## Chat
25
+
26
+ Real-time conversation interface with streaming support.
27
+
28
+ ### Basic Usage
29
+
30
+ ```typescript
31
+ import { Component, inject } from '@angular/core';
32
+ import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
33
+ import { Chat } from '@ai-sdk/angular';
34
+ import { CommonModule } from '@angular/common';
35
+
36
+ @Component({
37
+ selector: 'app-chat',
38
+ imports: [CommonModule, ReactiveFormsModule],
39
+ template: `
40
+ <div class="chat-container">
41
+ <div class="messages">
42
+ @for (message of chat.messages; track message.id) {
43
+ <div class="message" [ngClass]="message.role">
44
+ @for (part of message.parts; track $index) {
45
+ @switch (part.type) {
46
+ @case ('text') {
47
+ <div style="white-space: pre-wrap">
48
+ {{ part.text }}
49
+ @if (part.state === 'streaming') {
50
+ <span class="cursor">&#9646;</span>
51
+ }
52
+ </div>
53
+ }
54
+ @case ('reasoning') {
55
+ <details>
56
+ <summary>Reasoning</summary>
57
+ <div style="white-space: pre-wrap; opacity: 80%">
58
+ {{ part.text }}
59
+ </div>
60
+ </details>
61
+ }
62
+ @default {
63
+ <code>{{ part | json }}</code>
64
+ }
65
+ }
66
+ }
67
+ </div>
68
+ }
69
+ @if (chat.status === 'submitted') {
70
+ <div><em>Waiting...</em></div>
71
+ }
72
+ </div>
73
+
74
+ <form [formGroup]="chatForm" (ngSubmit)="sendMessage()">
75
+ <input formControlName="userInput" placeholder="Type your message..." />
76
+ @if (chat.status === 'ready') {
77
+ <button type="submit" [disabled]="!chatForm.valid">Send</button>
78
+ } @else {
79
+ <button [disabled]="chat.status === 'error'" (click)="chat.stop()">
80
+ Stop
81
+ </button>
82
+ }
83
+ </form>
84
+ </div>
85
+ `,
86
+ })
87
+ export class ChatComponent {
88
+ private fb = inject(FormBuilder);
89
+
90
+ public chat = new Chat({});
91
+
92
+ chatForm = this.fb.group({
93
+ userInput: ['', Validators.required],
94
+ });
95
+
96
+ sendMessage() {
97
+ if (this.chatForm.invalid) return;
98
+
99
+ const userInput = this.chatForm.value.userInput;
100
+ this.chatForm.reset();
101
+
102
+ this.chat.sendMessage(
103
+ { text: userInput },
104
+ {
105
+ body: {
106
+ selectedModel: 'gpt-4o',
107
+ },
108
+ },
109
+ );
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### Constructor Options
115
+
116
+ ```typescript
117
+ interface ChatInit<UI_MESSAGE extends UIMessage = UIMessage> {
118
+ /** Initial messages */
119
+ messages?: UI_MESSAGE[];
120
+
121
+ /** Custom ID generator */
122
+ generateId?: () => string;
123
+
124
+ /** Maximum conversation steps */
125
+ maxSteps?: number;
126
+
127
+ /** Tool call handler */
128
+ onToolCall?: (params: { toolCall: ToolCall }) => Promise<string>;
129
+
130
+ /** Completion callback */
131
+ onFinish?: (params: { message: UI_MESSAGE }) => void;
132
+
133
+ /** Error handler */
134
+ onError?: (error: Error) => void;
135
+
136
+ /** Custom transport */
137
+ transport?: ChatTransport;
138
+ }
139
+ ```
140
+
141
+ ### Properties (Reactive)
142
+
143
+ - `messages: UIMessage[]` - Array of conversation messages
144
+ - `status: 'ready' | 'submitted' | 'streaming' | 'error'` - Current status
145
+ - `error: Error | undefined` - Current error state
146
+
147
+ ### Methods
148
+
149
+ ```typescript
150
+ // Send a message
151
+ await chat.sendMessage(
152
+ message: UIMessageInput,
153
+ options?: {
154
+ body?: Record<string, any>;
155
+ headers?: Record<string, string>;
156
+ }
157
+ );
158
+
159
+ // Regenerate last assistant message
160
+ await chat.regenerate(options?: {
161
+ body?: Record<string, any>;
162
+ headers?: Record<string, string>;
163
+ });
164
+
165
+ // Add tool execution result
166
+ chat.addToolResult({
167
+ toolCallId: string;
168
+ output: string;
169
+ });
170
+
171
+ // Stop current generation
172
+ chat.stop();
173
+ ```
174
+
175
+ ### File Attachments
176
+
177
+ ```typescript
178
+ // HTML template
179
+ <input type="file" multiple (change)="onFileSelect($event)" />
180
+
181
+ // Component
182
+ onFileSelect(event: Event) {
183
+ const files = (event.target as HTMLInputElement).files;
184
+ if (files) {
185
+ this.chat.sendMessage({
186
+ text: "Analyze these files",
187
+ files: files
188
+ });
189
+ }
190
+ }
191
+ ```
192
+
193
+ ### Client-side Tool Calls
194
+
195
+ ```typescript
196
+ const chat = new Chat({
197
+ async onToolCall({ toolCall }) {
198
+ switch (toolCall.toolName) {
199
+ case 'get_weather':
200
+ return await getWeather(toolCall.input.location);
201
+ case 'search':
202
+ return await search(toolCall.input.query);
203
+ default:
204
+ throw new Error(`Unknown tool: ${toolCall.toolName}`);
205
+ }
206
+ },
207
+ });
208
+ ```
209
+
210
+ ## Completion
211
+
212
+ Single-turn text generation with streaming.
213
+
214
+ ### Basic Usage
215
+
216
+ ```typescript
217
+ import { Component } from '@angular/core';
218
+ import { Completion } from '@ai-sdk/angular';
219
+
220
+ @Component({
221
+ selector: 'app-completion',
222
+ template: `
223
+ <div>
224
+ <textarea
225
+ [(ngModel)]="completion.input"
226
+ placeholder="Enter your prompt..."
227
+ rows="4"
228
+ >
229
+ </textarea>
230
+
231
+ <button
232
+ (click)="completion.complete(completion.input)"
233
+ [disabled]="completion.loading"
234
+ >
235
+ {{ completion.loading ? 'Generating...' : 'Generate' }}
236
+ </button>
237
+
238
+ @if (completion.loading) {
239
+ <button (click)="completion.stop()">Stop</button>
240
+ }
241
+
242
+ <div class="result">
243
+ <h3>Result:</h3>
244
+ <pre>{{ completion.completion }}</pre>
245
+ </div>
246
+
247
+ @if (completion.error) {
248
+ <div class="error">{{ completion.error.message }}</div>
249
+ }
250
+ </div>
251
+ `,
252
+ })
253
+ export class CompletionComponent {
254
+ completion = new Completion({
255
+ api: '/api/completion',
256
+ onFinish: (prompt, completion) => {
257
+ console.log('Completed:', { prompt, completion });
258
+ },
259
+ });
260
+ }
261
+ ```
262
+
263
+ ### Constructor Options
264
+
265
+ ```typescript
266
+ interface CompletionOptions {
267
+ /** API endpoint (default: '/api/completion') */
268
+ api?: string;
269
+
270
+ /** Unique identifier */
271
+ id?: string;
272
+
273
+ /** Initial completion text */
274
+ initialCompletion?: string;
275
+
276
+ /** Initial input text */
277
+ initialInput?: string;
278
+
279
+ /** Stream protocol: 'data' (default) | 'text' */
280
+ streamProtocol?: 'data' | 'text';
281
+
282
+ /** Completion callback */
283
+ onFinish?: (prompt: string, completion: string) => void;
284
+
285
+ /** Error handler */
286
+ onError?: (error: Error) => void;
287
+
288
+ /** Custom fetch function */
289
+ fetch?: FetchFunction;
290
+
291
+ /** Request headers */
292
+ headers?: Record<string, string>;
293
+
294
+ /** Request body */
295
+ body?: Record<string, any>;
296
+
297
+ /** Request credentials */
298
+ credentials?: RequestCredentials;
299
+ }
300
+ ```
301
+
302
+ ### Properties (Reactive)
303
+
304
+ - `completion: string` - Generated text (writable)
305
+ - `input: string` - Current input (writable)
306
+ - `loading: boolean` - Generation state
307
+ - `error: Error | undefined` - Error state
308
+ - `id: string` - Completion ID
309
+ - `api: string` - API endpoint
310
+ - `streamProtocol: 'data' | 'text'` - Stream type
311
+
312
+ ### Methods
313
+
314
+ ```typescript
315
+ // Generate completion
316
+ await completion.complete(
317
+ prompt: string,
318
+ options?: {
319
+ headers?: Record<string, string>;
320
+ body?: Record<string, any>;
321
+ }
322
+ );
323
+
324
+ // Form submission handler
325
+ await completion.handleSubmit(event?: { preventDefault?: () => void });
326
+
327
+ // Stop generation
328
+ completion.stop();
329
+ ```
330
+
331
+ ## StructuredObject
332
+
333
+ Generate structured data with Zod schemas and streaming.
334
+
335
+ ### Basic Usage
336
+
337
+ ```typescript
338
+ import { Component } from '@angular/core';
339
+ import { StructuredObject } from '@ai-sdk/angular';
340
+ import { z } from 'zod';
341
+
342
+ const schema = z.object({
343
+ title: z.string(),
344
+ summary: z.string(),
345
+ tags: z.array(z.string()),
346
+ sentiment: z.enum(['positive', 'negative', 'neutral']),
347
+ });
348
+
349
+ @Component({
350
+ selector: 'app-structured-object',
351
+ template: `
352
+ <div>
353
+ <textarea
354
+ [(ngModel)]="input"
355
+ placeholder="Enter content to analyze..."
356
+ rows="4"
357
+ >
358
+ </textarea>
359
+
360
+ <button (click)="analyze()" [disabled]="structuredObject.loading">
361
+ {{ structuredObject.loading ? 'Analyzing...' : 'Analyze' }}
362
+ </button>
363
+
364
+ @if (structuredObject.object) {
365
+ <div class="result">
366
+ <h3>Analysis:</h3>
367
+ <div><strong>Title:</strong> {{ structuredObject.object.title }}</div>
368
+ <div>
369
+ <strong>Summary:</strong> {{ structuredObject.object.summary }}
370
+ </div>
371
+ <div>
372
+ <strong>Tags:</strong>
373
+ {{ structuredObject.object.tags?.join(', ') }}
374
+ </div>
375
+ <div>
376
+ <strong>Sentiment:</strong> {{ structuredObject.object.sentiment }}
377
+ </div>
378
+ </div>
379
+ }
380
+
381
+ @if (structuredObject.error) {
382
+ <div class="error">{{ structuredObject.error.message }}</div>
383
+ }
384
+ </div>
385
+ `,
386
+ })
387
+ export class StructuredObjectComponent {
388
+ input = '';
389
+
390
+ structuredObject = new StructuredObject({
391
+ api: '/api/analyze',
392
+ schema,
393
+ onFinish: ({ object, error }) => {
394
+ if (error) {
395
+ console.error('Schema validation failed:', error);
396
+ } else {
397
+ console.log('Generated object:', object);
398
+ }
399
+ },
400
+ });
401
+
402
+ async analyze() {
403
+ if (!this.input.trim()) return;
404
+ await this.structuredObject.submit(this.input);
405
+ }
406
+ }
407
+ ```
408
+
409
+ ### Constructor Options
410
+
411
+ ```typescript
412
+ interface StructuredObjectOptions<SCHEMA, RESULT> {
413
+ /** API endpoint */
414
+ api: string;
415
+
416
+ /** Zod schema */
417
+ schema: SCHEMA;
418
+
419
+ /** Unique identifier */
420
+ id?: string;
421
+
422
+ /** Initial object value */
423
+ initialValue?: DeepPartial<RESULT>;
424
+
425
+ /** Completion callback */
426
+ onFinish?: (event: {
427
+ object: RESULT | undefined;
428
+ error: Error | undefined;
429
+ }) => void;
430
+
431
+ /** Error handler */
432
+ onError?: (error: Error) => void;
433
+
434
+ /** Custom fetch function */
435
+ fetch?: FetchFunction;
436
+
437
+ /** Request headers */
438
+ headers?: Record<string, string>;
439
+
440
+ /** Request credentials */
441
+ credentials?: RequestCredentials;
442
+ }
443
+ ```
444
+
445
+ ### Properties (Reactive)
446
+
447
+ - `object: DeepPartial<RESULT> | undefined` - Generated object
448
+ - `loading: boolean` - Generation state
449
+ - `error: Error | undefined` - Error state
450
+
451
+ ### Methods
452
+
453
+ ```typescript
454
+ // Submit input for generation
455
+ await structuredObject.submit(input: unknown);
456
+
457
+ // Stop generation
458
+ structuredObject.stop();
459
+ ```
460
+
461
+ ## Server Implementation
462
+
463
+ ### Express.js Chat Endpoint
464
+
465
+ ```typescript
466
+ import { openai } from '@ai-sdk/openai';
467
+ import { convertToModelMessages, streamText } from 'ai';
468
+ import express from 'express';
469
+
470
+ const app = express();
471
+ app.use(express.json());
472
+
473
+ app.post('/api/chat', async (req, res) => {
474
+ const { messages, selectedModel } = req.body;
475
+
476
+ const result = streamText({
477
+ model: openai(selectedModel || 'gpt-4o'),
478
+ messages: convertToModelMessages(messages),
479
+ });
480
+
481
+ result.pipeUIMessageStreamToResponse(res);
482
+ });
483
+ ```
484
+
485
+ ### Express.js Completion Endpoint
486
+
487
+ ```typescript
488
+ app.post('/api/completion', async (req, res) => {
489
+ const { prompt } = req.body;
490
+
491
+ const result = streamText({
492
+ model: openai('gpt-4o'),
493
+ prompt,
494
+ });
495
+
496
+ result.pipeTextStreamToResponse(res);
497
+ });
498
+ ```
499
+
500
+ ### Express.js Structured Object Endpoint
501
+
502
+ ```typescript
503
+ import { streamObject } from 'ai';
504
+ import { z } from 'zod';
505
+
506
+ app.post('/api/analyze', async (req, res) => {
507
+ const input = req.body;
508
+
509
+ const result = streamObject({
510
+ model: openai('gpt-4o'),
511
+ schema: z.object({
512
+ title: z.string(),
513
+ summary: z.string(),
514
+ tags: z.array(z.string()),
515
+ sentiment: z.enum(['positive', 'negative', 'neutral']),
516
+ }),
517
+ prompt: `Analyze this content: ${JSON.stringify(input)}`,
518
+ });
519
+
520
+ result.pipeTextStreamToResponse(res);
521
+ });
522
+ ```
523
+
524
+ ## Development Setup
525
+
526
+ ### Building the Library
527
+
528
+ ```bash
529
+ # Install dependencies
530
+ pnpm install
531
+
532
+ # Build library
533
+ pnpm build
534
+
535
+ # Watch mode
536
+ pnpm build:watch
537
+
538
+ # Run tests
539
+ pnpm test
540
+
541
+ # Test watch mode
542
+ pnpm test:watch
543
+ ```
544
+
545
+ ### Running the Example
546
+
547
+ ```bash
548
+ # Navigate to example
549
+ cd examples/angular-chat
550
+
551
+ # Set up environment
552
+ echo "OPENAI_API_KEY=your_key_here" > .env
553
+
554
+ # Start development (Angular + Express)
555
+ pnpm start
556
+ ```
557
+
558
+ Starts:
559
+
560
+ - Angular dev server: `http://localhost:4200`
561
+ - Express API server: `http://localhost:3000`
562
+ - Proxy routes `/api/*` to Express
563
+
564
+ ## Testing
565
+
566
+ ### Running Tests
567
+
568
+ ```bash
569
+ pnpm test # Run all tests
570
+ pnpm test:watch # Watch mode
571
+ pnpm test:update # Update snapshots
572
+ ```
573
+
574
+ ## TypeScript Support
575
+
576
+ Full type safety with automatic type inference:
577
+
578
+ ```typescript
579
+ import { Chat, UIMessage, StructuredObject } from '@ai-sdk/angular';
580
+ import { z } from 'zod';
581
+
582
+ // Custom message types
583
+ interface CustomMessage extends UIMessage {
584
+ customData?: string;
585
+ }
586
+
587
+ const chat = new Chat<CustomMessage>({});
588
+
589
+ // Schema-typed objects
590
+ const schema = z.object({
591
+ name: z.string(),
592
+ age: z.number(),
593
+ });
594
+
595
+ const obj = new StructuredObject({
596
+ api: '/api/object',
597
+ schema, // Type automatically inferred
598
+ });
599
+
600
+ // obj.object has type: { name?: string; age?: number } | undefined
601
+ ```
602
+
603
+ ## Error Handling
604
+
605
+ All components provide reactive error states:
606
+
607
+ ```typescript
608
+ const chat = new Chat({
609
+ onError: (error) => {
610
+ console.error('Chat error:', error);
611
+ }
612
+ });
613
+
614
+ // Template
615
+ @if (chat.error) {
616
+ <div class="error">{{ chat.error.message }}</div>
617
+ }
618
+ ```
619
+
620
+ ## Performance
621
+
622
+ ### Stop on-going requests
623
+
624
+ ```typescript
625
+ chat.stop();
626
+ completion.stop();
627
+ structuredObject.stop();
628
+ ```
629
+
630
+ ### Change Detection
631
+
632
+ Uses Angular signals for efficient reactivity:
633
+
634
+ ```typescript
635
+ // These trigger minimal change detection
636
+ chat.messages; // Signal<UIMessage[]>
637
+ chat.status; // Signal<ChatStatus>
638
+ chat.error; // Signal<Error | undefined>
639
+ ```
640
+
641
+ ## License
642
+
643
+ Apache-2.0