Chat · Getting Started

Installation

A complete walkthrough for installing @threadplane/chat in an Angular 20+ application, activating a commercial license, and rendering your first chat in under 30 minutes.

This guide goes deeper than the Quick Start: license activation, peer dependencies, and the warnings you'll see in the console.

Just bought a license?

This guide is written for customers who purchased a Developer Seat, Team, or Enterprise plan and received a THREADPLANE_LICENSE token by email. If you're evaluating @threadplane/chat for noncommercial use, you can skip the license steps — the library runs without a token (with a one-time advisory warning).

#Prerequisites

1
Angular 20 or later

@threadplane/chat uses Angular Signals, input(), and contentChildren(). Run ng version to confirm. Upgrade with ng update @angular/core @angular/cli if you're below 20.

2
Node.js 18+

Required for the Angular build toolchain. node --version should report v18 or newer.

3
A running agent backend (or a mock)

The chat UI needs something to talk to. Two officially supported adapters cover virtually every backend:

  • @threadplane/langgraph — pick this if your backend is LangGraph or LangGraph Platform.
  • @threadplane/ag-ui — pick this for any AG-UI compatible backend (CrewAI, Mastra, Microsoft Agent Framework, AG2, Pydantic AI, AWS Strands, CopilotKit runtime).

Both adapters expose the same Agent contract to @threadplane/chat, so swapping later is a one-line change. If you don't have a backend yet, use mockAgent() from @threadplane/chat to wire up the UI first.

#1. Install the packages

Install @threadplane/chat, your chosen runtime adapter, and the marked markdown parser:

# LangGraph backends
npm install @threadplane/chat @threadplane/langgraph marked
 
# AG-UI compatible backends
npm install @threadplane/chat @threadplane/ag-ui marked

marked is a required peer dependency used to render assistant message markdown (code blocks, tables, headings). The chat components ship with their own design tokens and component-scoped styles — no Tailwind, PostCSS, or global stylesheet import is required.

Full peer dependency list

@threadplane/chat declares peers on @angular/core, @angular/common, @angular/forms, @angular/platform-browser (all ^20.0.0 || ^21.0.0), plus @threadplane/licensing, @threadplane/render, @threadplane/a2ui, @json-render/core (^0.16.0), @langchain/core (^1.1.33), rxjs (~7.8.0), and marked (^15 || ^16). npm 7+ installs all of these automatically.

#2. Add your license token

After purchase, Threadplane emails a signed token that looks like:

eyJzdWIiOiJjdXN0QGV4YW1wbGUuY29tIiwidGllciI6ImRldmVsb3Blci1zZWF0IiwiaWF0IjoxNzM…

The token is signed with Ed25519. Verification is offline and advisory — a missing or expired token logs one console.warn line on first boot and the chat keeps working. There is no kill switch, no network call, no telemetry.

So how you store the token is a matter of secret-management preference, not runtime correctness. Pick whichever flow fits your team.

Create .env at the project root (add it to .gitignore):

# .env
THREADPLANE_LICENSE=eyJzdWIi…

Wire the variable into your Angular environment file or read it directly in app.config.ts via your build's env-var mechanism (Vite/esbuild define, Angular CLI fileReplacements, etc.). For a typical Angular CLI setup:

// src/environments/environment.ts
export const environment = {
  production: false,
  threadplaneLicense: process.env['THREADPLANE_LICENSE'] ?? '',
};

Set THREADPLANE_LICENSE as a build-time secret in GitHub Actions, Vercel, Netlify, CircleCI, AWS Secrets Manager, Doppler, or whatever you already use. Reference it the same way as Option A — the token is baked into the bundle at build time.

# .github/workflows/deploy.yml
- run: npm run build
  env:
    THREADPLANE_LICENSE: ${{ secrets.THREADPLANE_LICENSE }}

#Option C: Commit to a private repo

The token is signed — it cannot be forged. If your repository is private and you accept distributing the token to everyone with repo access, it's safe to commit. Paste the literal string into app.config.ts:

provideChat({
  license: 'eyJzdWIi…',
});

For me, this is the lowest-friction path for a small team — the tradeoff is that everyone with repo access also gets the token. Do not commit it to a public repository — anyone with the token can use the seats it grants.

#3. Wire provideChat() and provideAgent()

Register both providers in your application config. Below is a minimal app.config.ts for a LangGraph backend (swap @threadplane/langgraph for @threadplane/ag-ui if you picked that adapter — the API surface is identical).

// src/app/app.config.ts
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideAgent } from '@threadplane/langgraph';
import { provideChat } from '@threadplane/chat';
import { environment } from '../environments/environment';
 
export const appConfig: ApplicationConfig = {
  providers: [
    provideZonelessChangeDetection(),
    provideAgent({
      apiUrl: environment.langgraphApiUrl, // e.g. 'http://localhost:2024'
      assistantId: 'chat',                 // The graph/agent ID exposed by your backend
    }),
    provideChat({
      license: environment.threadplaneLicense,
      assistantName: 'Assistant',
    }),
  ],
};

provideChat() accepts the license as a plain string — you can also inline a typeof guard if your build defines the token as a global:

declare const THREADPLANE_LICENSE: string | undefined;
 
provideChat({
  license: typeof THREADPLANE_LICENSE === 'string' ? THREADPLANE_LICENSE : undefined,
});

provideChat() itself is optional — chat components fall back to sensible defaults — but you'll want it once you start passing a license, an assistantName, or any other global config.

#4. Render your first chat

Add assistantId and threadId to the same root provideAgent({...}) from step 3, then bind <chat> to the result of injectAgent() in your component. Extend the config from step 3 rather than declaring a second provider:

// src/app/app.config.ts
import { ApplicationConfig, provideZonelessChangeDetection, signal } from '@angular/core';
import { provideAgent } from '@threadplane/langgraph';
import { provideChat } from '@threadplane/chat';
import { environment } from '../environments/environment';
 
export const appConfig: ApplicationConfig = {
  providers: [
    provideZonelessChangeDetection(),
    provideAgent({
      apiUrl: environment.langgraphApiUrl, // e.g. 'http://localhost:2024'
      assistantId: 'chat',                 // The graph/agent ID exposed by your backend
      threadId: signal(null),              // null = new thread on first send
    }),
    provideChat({
      license: environment.threadplaneLicense,
      assistantName: 'Assistant',
    }),
  ],
};

The component just injects that agent — no second provider:

// src/app/chat-page.component.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { injectAgent } from '@threadplane/langgraph';
import { ChatComponent } from '@threadplane/chat';
 
@Component({
  selector: 'app-chat-page',
  standalone: true,
  imports: [ChatComponent],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div style="height: 100vh">
      <chat [agent]="chatAgent" />
    </div>
  `,
})
export class ChatPageComponent {
  protected readonly chatAgent = injectAgent();
}

Add the route, run ng serve, and open the page. You should see the chat surface mount, an input at the bottom, and streaming responses as the agent replies.

Multiple agents in one app

Want several <chat> surfaces pointing at different graphs? Keep apiUrl (and license) on the root provider, then re-provide provideAgent({ assistantId, threadId }) in each component's providers: []. Angular's hierarchical DI merges the component-level config over the root, so each subtree gets its own assistantId while sharing the backend URL. injectAgent() resolves the nearest provider.

#5. Verify the license activated

Open the browser DevTools console and look for messages prefixed with [threadplane].

What you seeWhat it means
(no [threadplane] warnings)Token verified successfully. You're licensed.
[threadplane] @threadplane/chat: license missing.No token was passed. Chat still works, but you're in advisory mode.
[threadplane] @threadplane/chat: license expired.Token is past exp. Renew at threadplane.ai.
[threadplane] @threadplane/chat: license tampered.Token failed signature verification. Re-copy from the purchase email.
[threadplane] @threadplane/chat: license in grace period.Token expired recently but is still inside the 14-day grace window.

Each warning fires at most once per package per status, so the console stays quiet on subsequent renders. There is no UI badge, no banner, and no rate limit — verification is purely a one-time advisory.

No console warnings = healthy

A successful boot is silent. If you see [threadplane] lines in production, fix them before shipping — but the app will continue to function either way.

#Troubleshooting

1
`license missing` warning in production

You shipped without THREADPLANE_LICENSE defined at build time. Confirm the build secret is set in your CI environment, then verify the value actually reaches the bundle. A quick check:

console.log('license length:', environment.threadplaneLicense?.length ?? 0);

It should print roughly 200+ characters. Zero means the env var didn't get inlined.

2
`license tampered` warning

The token failed Ed25519 verification. Common causes:

  • The string was truncated when copied from email (some clients break long lines).
  • A .env parser stripped a trailing character.
  • You pasted the token with a leading or trailing whitespace.

Re-copy the full token from the original purchase email and try again. Tokens never expire silently — a tampered status always means string corruption.

3
`license expired` warning

Your 12-month term has lapsed. Renew at threadplane.ai/pricing; you'll receive a new token by email. There's a 14-day grace window after exp (during which the status reports grace rather than expired) to give you time to renew without disruption.

4
Chat renders blank or 404s the backend

Verify apiUrl in provideAgent() points to a reachable LangGraph or AG-UI endpoint, and assistantId matches a deployed graph on that server. For LangGraph, langgraph dev defaults to http://localhost:2024.

5
`Cannot find module '@threadplane/chat'`

Double-check the install completed and your tsconfig.json paths does not shadow node_modules. The package ships ESM and CJS — both Angular CLI and Vite-based builds work out of the box.

#What's next