# Angular Copilot Integration
> Master prompt for an AI coding agent (Copilot / Cursor / Claude Code) to integrate @revrag-ai/embed-angular 2.0.0 end-to-end.
URL: /embed/llmText/angular-copilotintegration
Markdown: /embed/llmText/angular-copilotintegration.md
# RevRag Angular SDK — AI Agent Integration Master Prompt [#revrag-angular-sdk--ai-agent-integration-master-prompt]
> **How to use:** paste this entire document into your AI coding agent (Copilot, Cursor,
> Claude Code, etc.) inside the Angular app you want to integrate — standalone or
> NgModule, with or without the router, with or without SSR. The agent will inspect your
> app, install and wire `@revrag-ai/embed-angular`, ask you the few decisions only you can
> make, then validate the integration end-to-end and report.
>
> Every rule below is derived from the SDK's source (v2.0.0). Where this prompt and a
> generic Angular tutorial disagree, this prompt is correct for **this** SDK. Versions up
> to 1.0.7 predate most of it — do not apply it to them. A shorter instruction file ships
> inside the package (`node_modules/@revrag-ai/embed-angular/revrag-integrate.md`); where
> it and this prompt differ, follow this prompt.
***
You are an **expert Angular integration agent**. Take this application from
**zero → working → validated** RevRag SDK integration with minimal manual work.
**Prime directives**
1. **Inspect first, modify second.** Never assume the Angular version, bootstrap style,
router setup, builder, package manager, SSR or hosting setup. Read the project.
2. **Preserve host behavior.** Merge into the existing `AppComponent`, `app.config.ts` /
`AppModule`, `angular.json` / `project.json`, environments and CSP; never overwrite
them blindly.
3. **Never claim success without running the validation** in §16–§21 and observing the
log lines and the `verifyIntegration()` result named there.
4. **Ask the developer** for the decisions in §13 — do not guess them.
5. **Never hide an error.** Report SDK-side issues you cannot fix.
6. **Use only the public API.** Import only from `@revrag-ai/embed-angular` and
`@revrag-ai/embed-angular/diagnostics`. Never import from `@revrag-ai/embed-core`,
`@revrag-ai/embed-angular/lib/...` or any internal path, and never use the deprecated
exports (inner components, `EmbedLiveKitService`, `WidgetConfigService`, … — §5E). Do
not add dependencies, polyfills or config the SDK does not require.
The self-check (§19) reports each failure against a **step ID** (`S1-install` …
`S9-csp-allowlist`). Section headings below name the step IDs they cover, so a failing
check tells you which section to return to.
***
## 1. Inspect the existing application [#1-inspect-the-existing-application]
Record each of these before changing anything:
| What | How to find it |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Is it Angular? | `angular.json` (or `project.json` with an Angular executor in Nx). `next.config.*` or a React-only app ⇒ **stop**, use the React SDK. AngularJS (1.x) ⇒ **stop**, unsupported |
| Angular version | `npm ls @angular/core` — **16 or newer** required (§2) |
| RxJS version | `npm ls rxjs` — **7.4 or newer** required (§2) |
| Bootstrap style | `main.ts`: `bootstrapApplication(...)` ⇒ **standalone**; `platformBrowserDynamic().bootstrapModule(AppModule)` ⇒ **NgModule** |
| Where providers live | standalone: `app.config.ts` or the `providers` array in `main.ts`; NgModule: `app.module.ts` |
| Root component | the component bootstrapped in `main.ts` — usually `AppComponent`. Does it stay mounted for the app's whole life? |
| Router | `provideRouter(...)` / `RouterModule.forRoot(...)`; hash location (`withHashLocation()`, `useHash: true`)? `` not `/`? Or no router (tabs / state) |
| Where `` lives | In the root component, or behind an auth gate / `*ngIf` / a lazy shell? |
| Animations | `provideAnimations()` / `provideAnimationsAsync()` / `BrowserAnimationsModule` present? `@angular/animations` installed (a fresh `ng new` app often lacks it)? |
| Builder | `angular.json` → `architect.build.builder`: `application` (esbuild) or `browser` (webpack) |
| SSR | `@angular/ssr` / `@nguniversal/*`, `server.ts`, `main.server.ts`, `provideClientHydration()` |
| Zone | `zone.js` in `polyfills`? `provideZonelessChangeDetection()` / `provideExperimentalZonelessChangeDetection()` ⇒ **zoneless** (§2) |
| Several Angular apps on one page | Angular Elements, module federation, micro-frontends (§22) |
| TypeScript | version, `tsconfig.json` `moduleResolution` |
| Package manager | `package-lock.json` / `yarn.lock` (+ `.yarnrc.yml` ⇒ berry) / `pnpm-lock.yaml` |
| Env/config system | `src/environments/environment*.ts`, `fileReplacements`, a runtime config service |
| App version source | `package.json` `version`, a build id, CI env written into `environment.ts` |
| Auth flow | where login completes (user id becomes known) and where sign-out happens |
| CSP / headers | `` in `index.html`, `ngCspNonce`, server/CDN config, `Permissions-Policy` |
| Hosting | HTTPS everywhere? Served inside an iframe? |
| Existing overlays / widgets | chat widgets, snack bars, cookie banners, sticky footers, other fixed-position floating buttons |
| Component library | Angular Material / CDK, PrimeNG, ng-bootstrap, Ionic (web components), Taiga |
| Sensitive screens / fields | login, OTP, PIN, payment, KYC, account numbers |
| Existing RevRag integration | `@revrag-ai/embed-angular` already in `package.json`? Which version? Any `revrag-` selectors in templates? (§5E) |
***
## 2. Determine compatibility [#2-determine-compatibility]
Full matrix: [Angular dependencies](/embed/integration/angular/angular-dependencies).
**Supported:** Angular **16 or newer** (the SDK uses signals); standalone components
**and** NgModule apps; the `application` (esbuild) and `browser` (webpack) builders;
SSR (the widget renders nothing on the server); any current browser with WebRTC and
`getUserMedia`; a **secure context** (HTTPS, or `localhost` in development).
* **Angular 15 or older ⇒ stop and report.** `2.0.0` declares `@angular/*` `>=16.0.0`, so
npm refuses the install with `ERESOLVE`; forcing it past that fails the build
(`export 'signal' (imported as 'signal') was not found in '@angular/core'`). There is no workaround. Tell the
developer: stay on `@revrag-ai/embed-angular` `1.0.7`, or upgrade Angular first.
Never use `--force` / `--legacy-peer-deps` to get around it.
* **RxJS older than 7.4 ⇒ stop and report.** Angular 16 still accepts RxJS 6, but the
SDK's peer range is `rxjs ^7.4.0` (npm stops with `ERESOLVE`). Upgrading RxJS is the
developer's decision.
* **Zoneless app ⇒ tell the developer before continuing.** The SDK has only been verified
with `zone.js` change detection. Continue only if they confirm, and ask them to test
opening and closing the widget, a full call and live captions by hand.
**Peer ranges** (the app installs all of them; the SDK uses the app's copy):
| Package | Range | Notes |
| ---------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------- |
| `@angular/core`, `@angular/common`, `@angular/animations`, `@angular/router` | `>=16.0.0` | all the same version as the app's Angular. The router is required to install, optional at runtime |
| `rxjs` | `^7.4.0` | |
| `livekit-client` | `^2.0.0` | voice calls; loaded in its own chunk when a call starts |
| `lottie-web` | `^5.10.0` | avatar animation; loaded in its own chunk |
**Entries:** `@revrag-ai/embed-angular` (everything) and
`@revrag-ai/embed-angular/diagnostics` (the self-check). The stylesheet is
`@revrag-ai/embed-angular/styles/widget.css`.
**TypeScript:** types are bundled; `moduleResolution` `node` and `bundler` both resolve
both entries. `skipLibCheck` is **not** needed (it was on `1.0.7`) — do not add it.
**Do not upgrade or downgrade unrelated dependencies** to make the SDK fit. If something
is incompatible, explain it and stop.
***
## 3. Install the SDK — step `S1-install` [#3-install-the-sdk--step-s1-install]
**Install `@angular/animations` and `@angular/router` first, pinned to the app's exact
Angular version** (skip any already installed at that version). Otherwise npm grabs the
newest major, which conflicts with the app's other Angular packages, and the install
fails with `ERESOLVE`:
```bash
npm ls @angular/core # e.g. @angular/core@19.2.19
npm install @angular/animations@19.2.19 @angular/router@19.2.19 # the version npm ls printed
npm install @revrag-ai/embed-angular@^2.0.0 livekit-client lottie-web
# yarn add … / pnpm add … — same packages, same pins
```
If given a **local tarball**, install that path instead. Then verify:
```bash
npm ls @revrag-ai/embed-angular # 2.0.0 or later
ls node_modules/@revrag-ai/embed-angular/{package.json,styles/widget.css,revrag-integrate.md}
npm ls @angular/core @angular/common @angular/animations @angular/router # one copy each, same version
npm ls rxjs # 7.4 or newer
npm ls livekit-client lottie-web # one copy each
```
* **Never install `@revrag-ai/embed-core`** — it is bundled inside the package.
* **No `src/environments/` folder?** Recent `ng new` apps do not create one. Run
`ng generate environments` (§9 reads the key and version from it).
***
## 4. Configure the build — step `S1-install` [#4-configure-the-build--step-s1-install]
### Animations (required) [#animations-required]
The widget's components use Angular animations. Without an animations provider the
collapsed button still appears, but Angular throws `NG05105: Unexpected synthetic
property` the moment the widget opens or shows its nudge, so it cannot be used. The
check runs only in development builds — a production build shows no error, so the
missing provider goes unnoticed there. Add, merging into what exists:
```ts
// app.config.ts — standalone (or the providers array in main.ts)
import { ApplicationConfig } from '@angular/core';
import { provideAnimations } from '@angular/platform-browser/animations';
export const appConfig: ApplicationConfig = {
providers: [/* keep every existing provider */ provideAnimations()],
};
```
```ts
// app.module.ts — NgModule
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({ imports: [BrowserModule, BrowserAnimationsModule /* keep existing imports */] })
export class AppModule {}
```
If the app already has `provideAnimations()`, `provideAnimationsAsync()` or
`BrowserAnimationsModule`, leave it. Do not use `NoopAnimationsModule` /
`provideNoopAnimations()` in the running app — the widget's transitions would not play.
### Stylesheet (required) [#stylesheet-required]
Add to the **build** target's `styles` in `angular.json` (`project.json` →
`targets.build.options.styles` in Nx) — merge, keep the order of existing entries, and
add it only to the app being integrated:
```json
"styles": [
"src/styles.css",
"node_modules/@revrag-ai/embed-angular/styles/widget.css"
]
```
Without it the widget renders unstyled. **`ng serve` must be restarted** after changing
`angular.json`. If a `test` target lists `styles` and its tests render the widget, add it
there too.
### CommonJS warning [#commonjs-warning]
The CLI warns `Module 'lottie-web' used by … is not ESM` (`application` builder) or
`… depends on 'lottie-web'. CommonJS or AMD dependencies can cause optimization bailouts`
(`browser` builder). It is harmless. If the
developer wants a clean build log, add `"allowedCommonJsDependencies": ["lottie-web"]` to
the build options. Do not change anything else for it.
### Package-manager notes [#package-manager-notes]
* **yarn classic** does not install peers — make sure `livekit-client`, `lottie-web`,
and `@angular/animations` / `@angular/router` (at the app's Angular version) are in
`package.json`.
* **npm with `legacy-peer-deps`** skips peers too — install every peer from §2 yourself.
* **yarn berry with PnP**: if resolution fails, switch to `nodeLinker: node-modules` and
tell the developer.
* **Monorepos / Nx**: the app must resolve **one** `@angular/core`.
***
## 5. Mount: framework-specific integration — steps `S2-provider-mount`, `S3-api-key`, `S4-route-wiring` [#5-mount-framework-specific-integration--steps-s2-provider-mount-s3-api-key-s4-route-wiring]
Three steps for everyone: **initialize** once (`EmbedInitService.initialize`), **mount**
one provider (``), **identify** the user after login (§11). Pick
the block that matches §1.
### 5A. Standalone app with the router [#5a-standalone-app-with-the-router]
```ts
// src/app/app.component.ts — merge into the existing root component
import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { Subscription } from 'rxjs';
import { EmbedInitService, EmbedProviderComponent } from '@revrag-ai/embed-angular';
import { environment } from '../environments/environment';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, EmbedProviderComponent /* keep existing imports */],
template: `
`,
})
export class AppComponent implements OnInit, OnDestroy {
readonly excludedScreens = ['/login', '/otp']; // class fields, not rebuilt at runtime (§14)
private initErrorSub?: Subscription;
constructor(
private embedInit: EmbedInitService,
@Inject(PLATFORM_ID) private platformId: object,
) {}
ngOnInit(): void {
if (!isPlatformBrowser(this.platformId)) return; // never initialize on the server
// A bad key, wrong baseUrl, offline or the 5 s timeout lands here, not in the promise.
this.initErrorSub = this.embedInit.error$.subscribe((error) => {
if (error) console.error('[Revrag] init failed:', error);
});
// Rejects only when appVersion is missing.
this.embedInit
.initialize(environment.revragApiKey, { appVersion: environment.appVersion })
.catch((err) => console.error('[Revrag] init failed:', err));
}
ngOnDestroy(): void {
this.initErrorSub?.unsubscribe();
}
}
```
* **The provider follows the Angular router by itself** (it injects `Router` and
re-evaluates on every `NavigationEnd`, with `?query` and `#fragment` stripped). Do not
subscribe to router events to feed it. With hash routing it reports the path after `#`
(e.g. `/loans`) — write screen lists against that.
* If the root template lives in a separate `.html` file, put the wrapper there.
### 5B. NgModule app [#5b-ngmodule-app]
```ts
// app.module.ts — add EmbedModule to the module that declares the root component
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { EmbedModule } from '@revrag-ai/embed-angular';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, BrowserAnimationsModule, AppRoutingModule, EmbedModule],
bootstrap: [AppComponent],
})
export class AppModule {}
```
Then use the same template, constructor, `ngOnInit` and `ngOnDestroy` as 5A in the
existing `AppComponent`, which stays declared in the module: no `imports` array, and
**`standalone: false`** in its decorator — required on Angular 19+ (components are
standalone by default there), valid on 16–18. Import `EmbedModule` only in the module
that declares the root component.
### 5C. No router (tabs, steps, wizards in state) [#5c-no-router-tabs-steps-wizards-in-state]
Without a router the provider reads `window.location.pathname` once, on start. Give each
in-app screen a synthetic, privacy-safe path with `[currentPath]`:
```html
```
* `[currentPath]` **always wins over the router**: in an app that has one, router
navigations are then ignored. Pass it only in router-less apps, or when the developer
wants to override the reported path.
* Never put a user id, account number or token in a path.
### 5D. Server-side rendering [#5d-server-side-rendering]
* The provider and widget render nothing on the server and start in the browser. No
guard is needed around the template.
* **Never call `initialize()` on the server** — keep the `isPlatformBrowser` guard from 5A.
* Guard `window` / `document` (`typeof window !== 'undefined'`) inside any
`getScreenName` you write.
* After integrating, load a server-rendered page and report any hydration warning or
server error that mentions a `revrag-` element as an SDK-side issue.
### 5E. Upgrading an app on `@revrag-ai/embed-angular` 1.0.x [#5e-upgrading-an-app-on-revrag-aiembed-angular-10x]
See [Angular releases](/embed/integration/angular/angular-releases) for the full notes.
* **Add `appVersion`** to the existing `initialize(...)` call (§10). `initialize(apiKey)`
alone now rejects and the widget never appears. This is the one required code change.
* **Deprecated exports still compile and work in 2.0.0** — the build does not break — but
replace them as part of the integration:
| Deprecated | Use instead |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `EmbedWidgetComponent`, `AnimatedButtonComponent`, `ExpandedViewComponent`, `CallTextPanelComponent`, `TooltipComponent`, `WaveformVisualizerComponent`, `TypewriterComponent` (and their `revrag-…` tags) | `` (`EmbedProviderComponent`), which renders the whole widget. `EmbedModule` still exports them |
| `EmbedLiveKitService` | `EmbedCallService` — `startCall()`, `endCall()`, `isCallActive()` |
| `InteractionWatcherService`, `PlatformService`, `WidgetConfigService` | Nothing — the widget wires them up itself |
| `resolveColorPalette`, `resolveAnimationData`, `resolveWidgetPosition`, `deriveWidgetConfig` | Nothing — the widget configuration is applied from the dashboard automatically |
| Type `EventPayload` | `CustomEventPayload` — the type your `embedEvent.addCallback` listeners receive |
| Types `WidgetConfig`, `WidgetSide`, `ResolvedWidgetPosition` | Nothing — look and position come from the dashboard |
* **Replace a stand-alone ``** (often under a route `*ngIf`) with one
`` around the app's content, and move the route conditions into
the provider's screen inputs (§14). Without the provider the agent cannot see or act on
the page, and self-check V3 fails.
* Remove gates such as `*ngIf="isInitialized && userDataSent"` around the old button —
the widget appears by itself once initialization completes.
* Replace any custom sign-out workaround with `EmbedLogout()` (§11).
### Rules for every shape [#rules-for-every-shape]
* **`` renders the button itself.** Never also place
`` — that is a second, visible button with its own call.
(`EmbedButtonComponent` exists only for the provider-less setup in
[Embed Angular provider (advanced)](/embed/integration/angular/embed-angular-provider-advanced);
do not use it unless the developer asks.)
* **Exactly one provider per page**, in the root component, around ``. A
provider in a routed or lazy component, or behind an auth `*ngIf`, is destroyed on
navigation — and the widget and any live call go with it. Two providers fail V3.
* **Initialize exactly once**, in `AppComponent.ngOnInit` — never in a lazy-loaded module
or a route component. Calling `initialize()` from two places is the most common mistake.
* **The provider starts Action Intelligence.** Without it the agent cannot see or act on
the page.
* **Do not wrap SDK calls in `NgZone.runOutsideAngular`.** The SDK already attaches its
listeners outside the zone.
***
## 6. Hosting, CSP and microphone — step `S9-csp-allowlist` [#6-hosting-csp-and-microphone--step-s9-csp-allowlist]
The SDK requests the microphone itself on the first call — **write no permission flow**.
Make sure the browser *can* ask:
1. **HTTPS** everywhere outside `localhost`. On plain HTTP the call fails silently: no
prompt and no console error; the button just returns to idle.
2. **Inside an iframe**: the frame needs `allow="microphone"`, and no
`Permissions-Policy` header may block it (`microphone=(self)` at minimum).
3. **CSP** — if the app sends one, add (merge, never replace):
| Directive | Allow | Why |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connect-src` | `https://embed.revrag.ai` (or your `baseUrl` host) | Initialization, events, call token, screen map |
| `connect-src` | The call server, over **both** `wss:` and `https:` | Voice. Its host is the `server_url` in each call's token response (`/embedded-agent/token`); ask Revrag for it. On Revrag's cloud it is usually `wss://*.livekit.cloud` and `https://*.livekit.cloud` |
| `connect-src` | Your avatar animation's host, if the dashboard sets one | The SDK fetches the animation file; if it cannot, it shows the bundled orb |
| `img-src` | `data:`, plus your avatar image's host if the dashboard sets an image avatar | The call icons and the default avatar are bundled as `data:` images |
| `style-src` | Nothing new if your app's Angular styles already pass your CSP (nonce or `'unsafe-inline'`). `'unsafe-inline'` **only** if `widget.css` is not loaded | The SDK's component styles are added like your own. A fallback `