# 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 `