API

@tanstack/ai-angular

Angular signal-based bindings for TanStack AI, providing convenient Angular bindings for the headless client.

Injection context requirement: Every inject* function in this package calls Angular's inject() internally. They must be called within an Angular injection context — a component or directive class field initializer, the constructor, or inside runInInjectionContext. Calling them outside an injection context will throw a runtime error.

Installation

sh
npm install @tanstack/ai-angular
npm install @tanstack/ai-angular

injectChat(options?)

Main injectable for managing chat state in Angular with full type safety.

typescript
import { Component } from "@angular/core";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";

@Component({
  selector: "app-chat",
  standalone: true,
  template: `...`,
})
export class ChatComponent {
  // injectChat is called in a field initializer — valid injection context.
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
  });
}
import { Component } from "@angular/core";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";

@Component({
  selector: "app-chat",
  standalone: true,
  template: `...`,
})
export class ChatComponent {
  // injectChat is called in a field initializer — valid injection context.
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
  });
}

Options

Extends ChatClientOptions from @tanstack/ai-client (minus internal state callbacks):

  • connection - Connection adapter (required, or use fetcher)
  • fetcher? - Direct async function for one-shot generation (alternative to connection)
  • tools? - Array of client tool implementations (with .client() method)
  • initialMessages? - Initial messages array
  • id? - Unique identifier for this chat instance
  • threadId? - Thread ID for AG-UI run correlation. Persists across sends; auto-generated if omitted
  • forwardedProps? - Arbitrary client-controlled JSON forwarded to the server in the AG-UI RunAgentInput.forwardedProps field. Reactive — accepts a plain value, an Angular Signal, or a zero-arg getter; changes sync automatically via effect
  • body? - Deprecated. Use forwardedProps instead. Still works for backward compatibility; values are merged into forwardedProps on the wire. Reactive (same forms as forwardedProps)
  • context? - Typed client-local runtime context passed to client tool implementations. Reactive (same forms). This value is not serialized to the server
  • live? - Enable live subscription mode (auto-subscribes/unsubscribes). Reactive (same forms)
  • outputSchema? - Standard-schema-compatible schema (Zod, Valibot, ArkType, or JSON Schema). When provided, adds typed partial and final signals to the return value
  • persistence? - Persistence configuration
  • devtools? - Display options for TanStack AI Devtools
  • onResponse? - Callback when response is received
  • onChunk? - Callback when stream chunk is received
  • onFinish? - Callback when response finishes
  • onError? - Callback when error occurs
  • onCustomEvent? - Callback for custom stream events
  • streamProcessor? - Stream processing configuration

Reactive options (body, forwardedProps, context, live) accept a ReactiveOption<T>, which is one of:

typescript
type ReactiveOption<T> = T | Signal<T> | (() => T);
type ReactiveOption<T> = T | Signal<T> | (() => T);

A plain value becomes a constant; a Signal is read directly; a zero-arg getter is wrapped in computed so any signals read inside it are tracked.

Note: Client tools are automatically executed — no onToolCall callback needed!

Returns

typescript
interface InjectChatResult {
  messages: Signal<UIMessage[]>;
  sendMessage: (content: string | MultimodalContent) => Promise<void>;
  append: (message: ModelMessage | UIMessage) => Promise<void>;
  addToolResult: (result: {
    toolCallId: string;
    tool: string;
    output: any;
    state?: "output-available" | "output-error";
    errorText?: string;
  }) => Promise<void>;
  addToolApprovalResponse: (response: {
    id: string;
    approved: boolean;
  }) => Promise<void>;
  reload: () => Promise<void>;
  stop: () => void;
  clear: () => void;
  setMessages: (messages: UIMessage[]) => void;
  isLoading: Signal<boolean>;
  error: Signal<Error | undefined>;
  status: Signal<ChatClientState>;
  isSubscribed: Signal<boolean>;
  connectionStatus: Signal<ConnectionStatus>;
  sessionGenerating: Signal<boolean>;
  // Only present when outputSchema is supplied:
  partial: Signal<DeepPartial<InferSchemaType<TSchema>>>;
  final: Signal<InferSchemaType<TSchema> | null>;
}
interface InjectChatResult {
  messages: Signal<UIMessage[]>;
  sendMessage: (content: string | MultimodalContent) => Promise<void>;
  append: (message: ModelMessage | UIMessage) => Promise<void>;
  addToolResult: (result: {
    toolCallId: string;
    tool: string;
    output: any;
    state?: "output-available" | "output-error";
    errorText?: string;
  }) => Promise<void>;
  addToolApprovalResponse: (response: {
    id: string;
    approved: boolean;
  }) => Promise<void>;
  reload: () => Promise<void>;
  stop: () => void;
  clear: () => void;
  setMessages: (messages: UIMessage[]) => void;
  isLoading: Signal<boolean>;
  error: Signal<Error | undefined>;
  status: Signal<ChatClientState>;
  isSubscribed: Signal<boolean>;
  connectionStatus: Signal<ConnectionStatus>;
  sessionGenerating: Signal<boolean>;
  // Only present when outputSchema is supplied:
  partial: Signal<DeepPartial<InferSchemaType<TSchema>>>;
  final: Signal<InferSchemaType<TSchema> | null>;
}

Note: All reactive state (messages, isLoading, error, status, isSubscribed, connectionStatus, sessionGenerating) is exposed as read-only Angular Signals. Read them by calling them as functions (e.g., chat.messages(), chat.isLoading()). Cleanup is automatic via DestroyRef.onDestroy.

Connection Adapters

Re-exported from @tanstack/ai-client for convenience:

typescript
import {
  fetchServerSentEvents,
  fetchHttpStream,
  xhrServerSentEvents,
  xhrHttpStream,
  stream,
  rpcStream,
  type ConnectionAdapter,
} from "@tanstack/ai-angular";
import {
  fetchServerSentEvents,
  fetchHttpStream,
  xhrServerSentEvents,
  xhrHttpStream,
  stream,
  rpcStream,
  type ConnectionAdapter,
} from "@tanstack/ai-angular";

Example: Basic Chat

typescript
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";

@Component({
  selector: "app-chat",
  standalone: true,
  imports: [CommonModule],
  template: `
    <ul>
      @for (message of chat.messages(); track message.id) {
        <li>
          <strong>{{ message.role }}:</strong>
          @for (part of message.parts; track $index) {
            @if (part.type === 'thinking') {
              <em>Thinking: {{ part.content }}</em>
            } @else if (part.type === 'text') {
              <span>{{ part.content }}</span>
            }
          }
        </li>
      }
    </ul>
    <input #input placeholder="Type a message..." />
    <button
      (click)="chat.sendMessage(input.value); input.value = ''"
      [disabled]="chat.isLoading()"
    >
      Send
    </button>
    @if (chat.isLoading()) {
      <p>Thinking...</p>
    }
  `,
})
export class ChatComponent {
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
  });
}
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";

@Component({
  selector: "app-chat",
  standalone: true,
  imports: [CommonModule],
  template: `
    <ul>
      @for (message of chat.messages(); track message.id) {
        <li>
          <strong>{{ message.role }}:</strong>
          @for (part of message.parts; track $index) {
            @if (part.type === 'thinking') {
              <em>Thinking: {{ part.content }}</em>
            } @else if (part.type === 'text') {
              <span>{{ part.content }}</span>
            }
          }
        </li>
      }
    </ul>
    <input #input placeholder="Type a message..." />
    <button
      (click)="chat.sendMessage(input.value); input.value = ''"
      [disabled]="chat.isLoading()"
    >
      Send
    </button>
    @if (chat.isLoading()) {
      <p>Thinking...</p>
    }
  `,
})
export class ChatComponent {
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
  });
}

Example: Tool Approval

typescript
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";

@Component({
  selector: "app-approval-chat",
  standalone: true,
  imports: [CommonModule],
  template: `
    @for (message of chat.messages(); track message.id) {
      @for (part of message.parts; track $index) {
        @if (
          part.type === 'tool-call' &&
          part.state === 'approval-requested' &&
          part.approval
        ) {
          <div>
            <p>Approve: {{ part.name }}</p>
            <button (click)="chat.addToolApprovalResponse({ id: part.approval!.id, approved: true })">
              Approve
            </button>
            <button (click)="chat.addToolApprovalResponse({ id: part.approval!.id, approved: false })">
              Deny
            </button>
          </div>
        }
      }
    }
  `,
})
export class ApprovalChatComponent {
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
  });
}
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";

@Component({
  selector: "app-approval-chat",
  standalone: true,
  imports: [CommonModule],
  template: `
    @for (message of chat.messages(); track message.id) {
      @for (part of message.parts; track $index) {
        @if (
          part.type === 'tool-call' &&
          part.state === 'approval-requested' &&
          part.approval
        ) {
          <div>
            <p>Approve: {{ part.name }}</p>
            <button (click)="chat.addToolApprovalResponse({ id: part.approval!.id, approved: true })">
              Approve
            </button>
            <button (click)="chat.addToolApprovalResponse({ id: part.approval!.id, approved: false })">
              Deny
            </button>
          </div>
        }
      }
    }
  `,
})
export class ApprovalChatComponent {
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
  });
}

Example: Client Tools with Type Safety

typescript
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";
import {
  clientTools,
  createChatClientOptions,
  type InferChatMessages,
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "./tool-definitions";

@Component({
  selector: "app-typed-chat",
  standalone: true,
  imports: [CommonModule],
  template: `
    @for (message of chat.messages(); track message.id) {
      @for (part of message.parts; track $index) {
        @if (part.type === 'tool-call' && part.name === 'updateUI') {
          <div>Tool executed: {{ part.name }}</div>
        }
      }
    }
  `,
})
export class TypedChatComponent {
  // Create client implementations
  private updateUI = updateUIDef.client((input) => {
    // input is fully typed!
    return { success: true };
  });

  private saveToStorage = saveToStorageDef.client((input) => {
    localStorage.setItem(input.key, input.value);
    return { saved: true };
  });

  // Create typed tools array (no 'as const' needed!)
  private tools = clientTools(this.updateUI, this.saveToStorage);

  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
    tools: this.tools, // Automatic execution, full type safety
  });
}
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";
import {
  clientTools,
  createChatClientOptions,
  type InferChatMessages,
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "./tool-definitions";

@Component({
  selector: "app-typed-chat",
  standalone: true,
  imports: [CommonModule],
  template: `
    @for (message of chat.messages(); track message.id) {
      @for (part of message.parts; track $index) {
        @if (part.type === 'tool-call' && part.name === 'updateUI') {
          <div>Tool executed: {{ part.name }}</div>
        }
      }
    }
  `,
})
export class TypedChatComponent {
  // Create client implementations
  private updateUI = updateUIDef.client((input) => {
    // input is fully typed!
    return { success: true };
  });

  private saveToStorage = saveToStorageDef.client((input) => {
    localStorage.setItem(input.key, input.value);
    return { saved: true };
  });

  // Create typed tools array (no 'as const' needed!)
  private tools = clientTools(this.updateUI, this.saveToStorage);

  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
    tools: this.tools, // Automatic execution, full type safety
  });
}

Example: Reactive Options with Signals

typescript
import { Component, signal } from "@angular/core";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";

@Component({
  selector: "app-reactive-chat",
  standalone: true,
  template: `
    <button (click)="toggleLanguage()">Toggle Language</button>
    @for (message of chat.messages(); track message.id) {
      <p>{{ message.role }}: {{ message.parts[0]?.content }}</p>
    }
  `,
})
export class ReactiveChatComponent {
  language = signal("en");

  // forwardedProps is reactive — the signal is read on every request
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
    forwardedProps: () => ({ language: this.language() }),
  });

  toggleLanguage() {
    this.language.set(this.language() === "en" ? "fr" : "en");
  }
}
import { Component, signal } from "@angular/core";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";

@Component({
  selector: "app-reactive-chat",
  standalone: true,
  template: `
    <button (click)="toggleLanguage()">Toggle Language</button>
    @for (message of chat.messages(); track message.id) {
      <p>{{ message.role }}: {{ message.parts[0]?.content }}</p>
    }
  `,
})
export class ReactiveChatComponent {
  language = signal("en");

  // forwardedProps is reactive — the signal is read on every request
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
    forwardedProps: () => ({ language: this.language() }),
  });

  toggleLanguage() {
    this.language.set(this.language() === "en" ? "fr" : "en");
  }
}

Example: Structured Output

typescript
import { Component } from "@angular/core";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";
import { z } from "zod";

const recipeSchema = z.object({
  title: z.string(),
  ingredients: z.array(z.string()),
  steps: z.array(z.string()),
});

@Component({
  selector: "app-recipe-chat",
  standalone: true,
  template: `
    <button (click)="chat.sendMessage('Give me a pasta recipe')">Ask</button>
    @if (chat.partial().title) {
      <h2>{{ chat.partial().title }}</h2>
    }
    @if (chat.final()) {
      <ul>
        @for (step of chat.final()!.steps; track $index) {
          <li>{{ step }}</li>
        }
      </ul>
    }
  `,
})
export class RecipeChatComponent {
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
    outputSchema: recipeSchema,
  });
}
import { Component } from "@angular/core";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";
import { z } from "zod";

const recipeSchema = z.object({
  title: z.string(),
  ingredients: z.array(z.string()),
  steps: z.array(z.string()),
});

@Component({
  selector: "app-recipe-chat",
  standalone: true,
  template: `
    <button (click)="chat.sendMessage('Give me a pasta recipe')">Ask</button>
    @if (chat.partial().title) {
      <h2>{{ chat.partial().title }}</h2>
    }
    @if (chat.final()) {
      <ul>
        @for (step of chat.final()!.steps; track $index) {
          <li>{{ step }}</li>
        }
      </ul>
    }
  `,
})
export class RecipeChatComponent {
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
    outputSchema: recipeSchema,
  });
}

Generation Injectables

Angular injectables for one-shot generation tasks (images, audio, speech, transcription, summarization, video). All share the same pattern: provide a connection or fetcher, call generate(), and read reactive signals.

injectGeneration(options)

Base injectable for custom generation types. All specialized injectables below are built on this.

typescript
import { Component } from "@angular/core";
import { injectGeneration } from "@tanstack/ai-angular";
import { fetchServerSentEvents } from "@tanstack/ai-client";

@Component({ selector: "app-custom", standalone: true, template: `...` })
export class CustomGenerationComponent {
  gen = injectGeneration({
    connection: fetchServerSentEvents("/api/generate/custom"),
  });

  // Call gen.generate(input), read gen.result(), gen.isLoading(), etc.
}
import { Component } from "@angular/core";
import { injectGeneration } from "@tanstack/ai-angular";
import { fetchServerSentEvents } from "@tanstack/ai-client";

@Component({ selector: "app-custom", standalone: true, template: `...` })
export class CustomGenerationComponent {
  gen = injectGeneration({
    connection: fetchServerSentEvents("/api/generate/custom"),
  });

  // Call gen.generate(input), read gen.result(), gen.isLoading(), etc.
}

Options: connection?, fetcher?, id?, body? (reactive), devtools?, onResult?, onError?, onProgress?, onChunk?

Returns: generate, result, isLoading, error, status, stop, reset — all reactive state is a read-only Signal<T>.

injectGenerateImage(options)

Image generation injectable. generate() accepts ImageGenerateInput, result is ImageGenerationResult.

typescript
import { Component } from "@angular/core";
import { injectGenerateImage } from "@tanstack/ai-angular";
import { fetchServerSentEvents } from "@tanstack/ai-client";

@Component({
  selector: "app-image",
  standalone: true,
  template: `
    <button (click)="gen.generate({ prompt: 'A mountain at sunset' })" [disabled]="gen.isLoading()">
      Generate
    </button>
    @if (gen.result()) {
      <img [src]="gen.result()!.images[0]!.url" alt="Generated image" />
    }
  `,
})
export class ImageComponent {
  gen = injectGenerateImage({
    connection: fetchServerSentEvents("/api/generate/image"),
  });
}
import { Component } from "@angular/core";
import { injectGenerateImage } from "@tanstack/ai-angular";
import { fetchServerSentEvents } from "@tanstack/ai-client";

@Component({
  selector: "app-image",
  standalone: true,
  template: `
    <button (click)="gen.generate({ prompt: 'A mountain at sunset' })" [disabled]="gen.isLoading()">
      Generate
    </button>
    @if (gen.result()) {
      <img [src]="gen.result()!.images[0]!.url" alt="Generated image" />
    }
  `,
})
export class ImageComponent {
  gen = injectGenerateImage({
    connection: fetchServerSentEvents("/api/generate/image"),
  });
}

injectGenerateAudio(options)

Audio generation injectable (music, sound effects). generate() accepts AudioGenerateInput, result is AudioGenerationResult.

typescript
import { Component } from "@angular/core";
import { injectGenerateAudio } from "@tanstack/ai-angular";
import { fetchServerSentEvents } from "@tanstack/ai-client";

@Component({
  selector: "app-audio",
  standalone: true,
  template: `
    <button (click)="gen.generate({ prompt: 'An upbeat electronic track', duration: 10 })" [disabled]="gen.isLoading()">
      Generate
    </button>
    @if (gen.result()) {
      <audio [src]="gen.result()!.audio.url" controls></audio>
    }
  `,
})
export class AudioComponent {
  gen = injectGenerateAudio({
    connection: fetchServerSentEvents("/api/generate/audio"),
  });
}
import { Component } from "@angular/core";
import { injectGenerateAudio } from "@tanstack/ai-angular";
import { fetchServerSentEvents } from "@tanstack/ai-client";

@Component({
  selector: "app-audio",
  standalone: true,
  template: `
    <button (click)="gen.generate({ prompt: 'An upbeat electronic track', duration: 10 })" [disabled]="gen.isLoading()">
      Generate
    </button>
    @if (gen.result()) {
      <audio [src]="gen.result()!.audio.url" controls></audio>
    }
  `,
})
export class AudioComponent {
  gen = injectGenerateAudio({
    connection: fetchServerSentEvents("/api/generate/audio"),
  });
}

injectGenerateSpeech(options)

Text-to-speech injectable. generate() accepts SpeechGenerateInput, result is TTSResult.

injectTranscription(options)

Audio transcription injectable. generate() accepts TranscriptionGenerateInput, result is TranscriptionResult.

injectSummarize(options)

Text summarization injectable. generate() accepts SummarizeGenerateInput, result is SummarizationResult.

injectGenerateVideo(options)

Video generation injectable with job polling. Returns additional jobId and videoStatus signals. Accepts extra onJobCreated? and onStatusUpdate? callbacks.

typescript
import { Component } from "@angular/core";
import { injectGenerateVideo } from "@tanstack/ai-angular";
import { fetchServerSentEvents } from "@tanstack/ai-client";

@Component({
  selector: "app-video",
  standalone: true,
  template: `
    <button (click)="gen.generate({ prompt: 'A time-lapse of a sunset' })" [disabled]="gen.isLoading()">
      Generate
    </button>
    @if (gen.videoStatus()) {
      <p>Status: {{ gen.videoStatus()!.status }}</p>
    }
    @if (gen.result()) {
      <video [src]="gen.result()!.url" controls></video>
    }
  `,
})
export class VideoComponent {
  gen = injectGenerateVideo({
    connection: fetchServerSentEvents("/api/generate/video"),
    onJobCreated: (jobId) => console.log("Job created:", jobId),
  });
}
import { Component } from "@angular/core";
import { injectGenerateVideo } from "@tanstack/ai-angular";
import { fetchServerSentEvents } from "@tanstack/ai-client";

@Component({
  selector: "app-video",
  standalone: true,
  template: `
    <button (click)="gen.generate({ prompt: 'A time-lapse of a sunset' })" [disabled]="gen.isLoading()">
      Generate
    </button>
    @if (gen.videoStatus()) {
      <p>Status: {{ gen.videoStatus()!.status }}</p>
    }
    @if (gen.result()) {
      <video [src]="gen.result()!.url" controls></video>
    }
  `,
})
export class VideoComponent {
  gen = injectGenerateVideo({
    connection: fetchServerSentEvents("/api/generate/video"),
    onJobCreated: (jobId) => console.log("Job created:", jobId),
  });
}

Additional returns (video only):

  • jobId: Signal<string | null> — The polling job ID, once the server creates it
  • videoStatus: Signal<VideoStatusInfo | null> — Real-time status updates from the polling loop

All generation injectables automatically clean up via DestroyRef.onDestroy.

Injection Context

Angular's DI system requires that inject() is called during component construction. Every inject* function in this package calls inject() internally. Valid call sites:

typescript
// Field initializer (recommended)
export class MyComponent {
  chat = injectChat({ connection: fetchServerSentEvents("/api/chat") });
}

// Constructor
export class MyComponent {
  chat: ReturnType<typeof injectChat>;
  constructor() {
    this.chat = injectChat({ connection: fetchServerSentEvents("/api/chat") });
  }
}

// Inside runInInjectionContext
const chat = runInInjectionContext(injector, () =>
  injectChat({ connection: fetchServerSentEvents("/api/chat") }),
);
// Field initializer (recommended)
export class MyComponent {
  chat = injectChat({ connection: fetchServerSentEvents("/api/chat") });
}

// Constructor
export class MyComponent {
  chat: ReturnType<typeof injectChat>;
  constructor() {
    this.chat = injectChat({ connection: fetchServerSentEvents("/api/chat") });
  }
}

// Inside runInInjectionContext
const chat = runInInjectionContext(injector, () =>
  injectChat({ connection: fetchServerSentEvents("/api/chat") }),
);

createChatClientOptions(options)

Helper to create typed chat options (re-exported from @tanstack/ai-client).

typescript
import {
  clientTools,
  createChatClientOptions,
  type InferChatMessages,
} from "@tanstack/ai-client";

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(tool1, tool2);

const chatOptions = createChatClientOptions({
  connection: fetchServerSentEvents("/api/chat"),
  tools,
});

type Messages = InferChatMessages<typeof chatOptions>;
import {
  clientTools,
  createChatClientOptions,
  type InferChatMessages,
} from "@tanstack/ai-client";

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(tool1, tool2);

const chatOptions = createChatClientOptions({
  connection: fetchServerSentEvents("/api/chat"),
  tools,
});

type Messages = InferChatMessages<typeof chatOptions>;

Types

Re-exported from @tanstack/ai-angular (sourced from @tanstack/ai-client):

  • UIMessage<TTools> - Message type with tool type parameter
  • InjectChatOptions<TTools, TSchema, TContext> - Chat injectable options
  • InjectChatResult<TTools, TSchema> - Chat injectable return type
  • ReactiveOption<T> - Union of T | Signal<T> | (() => T) for reactive option fields
  • DeepPartial<T> - Recursive partial; used to type the in-flight partial value
  • ChatRequestBody - Request body type
  • MultimodalContent - Multimodal content type for sendMessage
  • ConnectionAdapter - Connection adapter interface
  • InferChatMessages<T> - Extract message type from options
  • GenerationClientState - Generation lifecycle state
  • ImageGenerateInput - Image generation input type
  • AudioGenerateInput - Audio generation input type
  • SpeechGenerateInput - Speech generation input type
  • TranscriptionGenerateInput - Transcription input type
  • SummarizeGenerateInput - Summarization input type
  • VideoGenerateInput - Video generation input type
  • VideoGenerateResult - Video generation result type
  • VideoStatusInfo - Video job status info

Tool authoring types — import directly from @tanstack/ai (not re-exported by @tanstack/ai-angular):

  • toolDefinition() - Create isomorphic tool definition
  • ToolDefinitionInstance - Tool definition type
  • ClientTool - Client tool type
  • ServerTool - Server tool type

Next Steps