npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ai-sdk/angular

v2.0.3

Published

Angular implementation of ai-sdk.

Readme

AI SDK Angular

Angular UI components for the AI SDK v5.

Overview

The @ai-sdk/angular package provides Angular-specific implementations using Angular signals for reactive state management:

  • Chat - Multi-turn conversations with streaming responses
  • Completion - Single-turn text generation
  • StructuredObject - Type-safe object generation with Zod schemas

Installation

npm install @ai-sdk/angular ai

Peer Dependencies

  • Angular 16+ (@angular/core)
  • Zod v3+ (optional, for structured objects)

Chat

Real-time conversation interface with streaming support.

Basic Usage

import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { Chat } from '@ai-sdk/angular';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-chat',
  imports: [CommonModule, ReactiveFormsModule],
  template: `
    <div class="chat-container">
      <div class="messages">
        @for (message of chat.messages; track message.id) {
          <div class="message" [ngClass]="message.role">
            @for (part of message.parts; track $index) {
              @switch (part.type) {
                @case ('text') {
                  <div style="white-space: pre-wrap">
                    {{ part.text }}
                    @if (part.state === 'streaming') {
                      <span class="cursor">&#9646;</span>
                    }
                  </div>
                }
                @case ('reasoning') {
                  <details>
                    <summary>Reasoning</summary>
                    <div style="white-space: pre-wrap; opacity: 80%">
                      {{ part.text }}
                    </div>
                  </details>
                }
                @default {
                  <code>{{ part | json }}</code>
                }
              }
            }
          </div>
        }
        @if (chat.status === 'submitted') {
          <div><em>Waiting...</em></div>
        }
      </div>

      <form [formGroup]="chatForm" (ngSubmit)="sendMessage()">
        <input formControlName="userInput" placeholder="Type your message..." />
        @if (chat.status === 'ready') {
          <button type="submit" [disabled]="!chatForm.valid">Send</button>
        } @else {
          <button [disabled]="chat.status === 'error'" (click)="chat.stop()">
            Stop
          </button>
        }
      </form>
    </div>
  `,
})
export class ChatComponent {
  private fb = inject(FormBuilder);

  public chat = new Chat({});

  chatForm = this.fb.group({
    userInput: ['', Validators.required],
  });

  sendMessage() {
    if (this.chatForm.invalid) return;

    const userInput = this.chatForm.value.userInput;
    this.chatForm.reset();

    this.chat.sendMessage(
      { text: userInput },
      {
        body: {
          selectedModel: 'gpt-4o',
        },
      },
    );
  }
}

Constructor Options

interface ChatInit<UI_MESSAGE extends UIMessage = UIMessage> {
  /** Initial messages */
  messages?: UI_MESSAGE[];

  /** Custom ID generator */
  generateId?: () => string;

  /** Maximum conversation steps */
  maxSteps?: number;

  /** Tool call handler */
  onToolCall?: (params: { toolCall: ToolCall }) => Promise<string>;

  /** Completion callback */
  onFinish?: (params: { message: UI_MESSAGE }) => void;

  /** Error handler */
  onError?: (error: Error) => void;

  /** Custom transport */
  transport?: ChatTransport;
}

Properties (Reactive)

  • messages: UIMessage[] - Array of conversation messages
  • status: 'ready' | 'submitted' | 'streaming' | 'error' - Current status
  • error: Error | undefined - Current error state

Methods

// Send a message
await chat.sendMessage(
  message: UIMessageInput,
  options?: {
    body?: Record<string, any>;
    headers?: Record<string, string>;
  }
);

// Regenerate last assistant message
await chat.regenerate(options?: {
  body?: Record<string, any>;
  headers?: Record<string, string>;
});

// Add tool execution result
chat.addToolOutput({
  toolCallId: string;
  output: string;
});

// Stop current generation
chat.stop();

File Attachments

// HTML template
<input type="file" multiple (change)="onFileSelect($event)" />

// Component
onFileSelect(event: Event) {
  const files = (event.target as HTMLInputElement).files;
  if (files) {
    this.chat.sendMessage({
      text: "Analyze these files",
      files: files
    });
  }
}

Client-side Tool Calls

const chat = new Chat({
  async onToolCall({ toolCall }) {
    switch (toolCall.toolName) {
      case 'get_weather':
        return await getWeather(toolCall.input.location);
      case 'search':
        return await search(toolCall.input.query);
      default:
        throw new Error(`Unknown tool: ${toolCall.toolName}`);
    }
  },
});

Completion

Single-turn text generation with streaming.

Basic Usage

import { Component } from '@angular/core';
import { Completion } from '@ai-sdk/angular';

@Component({
  selector: 'app-completion',
  template: `
    <div>
      <textarea
        [(ngModel)]="completion.input"
        placeholder="Enter your prompt..."
        rows="4"
      >
      </textarea>

      <button
        (click)="completion.complete(completion.input)"
        [disabled]="completion.loading"
      >
        {{ completion.loading ? 'Generating...' : 'Generate' }}
      </button>

      @if (completion.loading) {
        <button (click)="completion.stop()">Stop</button>
      }

      <div class="result">
        <h3>Result:</h3>
        <pre>{{ completion.completion }}</pre>
      </div>

      @if (completion.error) {
        <div class="error">{{ completion.error.message }}</div>
      }
    </div>
  `,
})
export class CompletionComponent {
  completion = new Completion({
    api: '/api/completion',
    onFinish: (prompt, completion) => {
      console.log('Completed:', { prompt, completion });
    },
  });
}

Constructor Options

interface CompletionOptions {
  /** API endpoint (default: '/api/completion') */
  api?: string;

  /** Unique identifier */
  id?: string;

  /** Initial completion text */
  initialCompletion?: string;

  /** Initial input text */
  initialInput?: string;

  /** Stream protocol: 'data' (default) | 'text' */
  streamProtocol?: 'data' | 'text';

  /** Completion callback */
  onFinish?: (prompt: string, completion: string) => void;

  /** Error handler */
  onError?: (error: Error) => void;

  /** Custom fetch function */
  fetch?: FetchFunction;

  /** Request headers */
  headers?: Record<string, string>;

  /** Request body */
  body?: Record<string, any>;

  /** Request credentials */
  credentials?: RequestCredentials;
}

Properties (Reactive)

  • completion: string - Generated text (writable)
  • input: string - Current input (writable)
  • loading: boolean - Generation state
  • error: Error | undefined - Error state
  • id: string - Completion ID
  • api: string - API endpoint
  • streamProtocol: 'data' | 'text' - Stream type

Methods

// Generate completion
await completion.complete(
  prompt: string,
  options?: {
    headers?: Record<string, string>;
    body?: Record<string, any>;
  }
);

// Form submission handler
await completion.handleSubmit(event?: { preventDefault?: () => void });

// Stop generation
completion.stop();

StructuredObject

Generate structured data with Zod schemas and streaming.

Basic Usage

import { Component } from '@angular/core';
import { StructuredObject } from '@ai-sdk/angular';
import { z } from 'zod';

const schema = z.object({
  title: z.string(),
  summary: z.string(),
  tags: z.array(z.string()),
  sentiment: z.enum(['positive', 'negative', 'neutral']),
});

@Component({
  selector: 'app-structured-object',
  template: `
    <div>
      <textarea
        [(ngModel)]="input"
        placeholder="Enter content to analyze..."
        rows="4"
      >
      </textarea>

      <button (click)="analyze()" [disabled]="structuredObject.loading">
        {{ structuredObject.loading ? 'Analyzing...' : 'Analyze' }}
      </button>

      @if (structuredObject.object) {
        <div class="result">
          <h3>Analysis:</h3>
          <div><strong>Title:</strong> {{ structuredObject.object.title }}</div>
          <div>
            <strong>Summary:</strong> {{ structuredObject.object.summary }}
          </div>
          <div>
            <strong>Tags:</strong>
            {{ structuredObject.object.tags?.join(', ') }}
          </div>
          <div>
            <strong>Sentiment:</strong> {{ structuredObject.object.sentiment }}
          </div>
        </div>
      }

      @if (structuredObject.error) {
        <div class="error">{{ structuredObject.error.message }}</div>
      }
    </div>
  `,
})
export class StructuredObjectComponent {
  input = '';

  structuredObject = new StructuredObject({
    api: '/api/analyze',
    schema,
    onFinish: ({ object, error }) => {
      if (error) {
        console.error('Schema validation failed:', error);
      } else {
        console.log('Generated object:', object);
      }
    },
  });

  async analyze() {
    if (!this.input.trim()) return;
    await this.structuredObject.submit(this.input);
  }
}

Constructor Options

interface StructuredObjectOptions<SCHEMA, RESULT> {
  /** API endpoint */
  api: string;

  /** Zod schema */
  schema: SCHEMA;

  /** Unique identifier */
  id?: string;

  /** Initial object value */
  initialValue?: DeepPartial<RESULT>;

  /** Completion callback */
  onFinish?: (event: {
    object: RESULT | undefined;
    error: Error | undefined;
  }) => void;

  /** Error handler */
  onError?: (error: Error) => void;

  /** Custom fetch function */
  fetch?: FetchFunction;

  /** Request headers */
  headers?: Record<string, string>;

  /** Request credentials */
  credentials?: RequestCredentials;
}

Properties (Reactive)

  • object: DeepPartial<RESULT> | undefined - Generated object
  • loading: boolean - Generation state
  • error: Error | undefined - Error state

Methods

// Submit input for generation
await structuredObject.submit(input: unknown);

// Stop generation
structuredObject.stop();

Server Implementation

Express.js Chat Endpoint

import { openai } from '@ai-sdk/openai';
import { convertToModelMessages, streamText } from 'ai';
import express from 'express';

const app = express();
app.use(express.json());

app.post('/api/chat', async (req, res) => {
  const { messages, selectedModel } = req.body;

  const result = streamText({
    model: openai(selectedModel || 'gpt-4o'),
    messages: convertToModelMessages(messages),
  });

  result.pipeUIMessageStreamToResponse(res);
});

Express.js Completion Endpoint

app.post('/api/completion', async (req, res) => {
  const { prompt } = req.body;

  const result = streamText({
    model: openai('gpt-4o'),
    prompt,
  });

  result.pipeTextStreamToResponse(res);
});

Express.js Structured Object Endpoint

import { streamObject } from 'ai';
import { z } from 'zod';

app.post('/api/analyze', async (req, res) => {
  const input = req.body;

  const result = streamObject({
    model: openai('gpt-4o'),
    schema: z.object({
      title: z.string(),
      summary: z.string(),
      tags: z.array(z.string()),
      sentiment: z.enum(['positive', 'negative', 'neutral']),
    }),
    prompt: `Analyze this content: ${JSON.stringify(input)}`,
  });

  result.pipeTextStreamToResponse(res);
});

Development Setup

Building the Library

# Install dependencies
pnpm install

# Build library
pnpm build

# Watch mode
pnpm build:watch

# Run tests
pnpm test

# Test watch mode
pnpm test:watch

Running the Example

# Navigate to example
cd examples/angular-chat

# Set up environment
echo "OPENAI_API_KEY=your_key_here" > .env

# Start development (Angular + Express)
pnpm start

Starts:

  • Angular dev server: http://localhost:4200
  • Express API server: http://localhost:3000
  • Proxy routes /api/* to Express

Testing

Running Tests

pnpm test              # Run all tests
pnpm test:watch        # Watch mode
pnpm test:update       # Update snapshots

TypeScript Support

Full type safety with automatic type inference:

import { Chat, UIMessage, StructuredObject } from '@ai-sdk/angular';
import { z } from 'zod';

// Custom message types
interface CustomMessage extends UIMessage {
  customData?: string;
}

const chat = new Chat<CustomMessage>({});

// Schema-typed objects
const schema = z.object({
  name: z.string(),
  age: z.number(),
});

const obj = new StructuredObject({
  api: '/api/object',
  schema, // Type automatically inferred
});

// obj.object has type: { name?: string; age?: number } | undefined

Error Handling

All components provide reactive error states:

const chat = new Chat({
  onError: (error) => {
    console.error('Chat error:', error);
  }
});

// Template
@if (chat.error) {
  <div class="error">{{ chat.error.message }}</div>
}

Performance

Stop on-going requests

chat.stop();
completion.stop();
structuredObject.stop();

Change Detection

Uses Angular signals for efficient reactivity:

// These trigger minimal change detection
chat.messages; // Signal<UIMessage[]>
chat.status; // Signal<ChatStatus>
chat.error; // Signal<Error | undefined>

License

Apache-2.0