Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@syncagent/angular

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@syncagent/angular - npm Package Compare versions

Comparing version
0.3.4
to
0.4.0
+155
-3
dist/index.d.mts
import * as rxjs from 'rxjs';
import * as _angular_core from '@angular/core';
import { Message, ToolData, SyncAgentConfig } from '@syncagent/js';
export { ChatOptions, ChatResult, Message, SyncAgentClient, SyncAgentConfig, ToolData, ToolDefinition, ToolParameter, detectPageContext } from '@syncagent/js';
import { Message, ToolData, SyncAgentConfig, GuestIdentity } from '@syncagent/js';
export { ChatOptions, ChatResult, CustomerChatOptions, CustomerChatResult, FieldValidationResult, GuestFormConfig, GuestIdentificationRequiredError, GuestIdentity, Message, SyncAgentClient, SyncAgentConfig, ToolData, ToolDefinition, ToolParameter, detectPageContext, generateGuestIdentifier, validateEmail, validateGuestForm, validateName } from '@syncagent/js';

@@ -70,2 +70,154 @@ interface SyncAgentServiceConfig extends SyncAgentConfig {

export { SyncAgentService, type SyncAgentServiceConfig };
interface CustomerChatServiceConfig extends SyncAgentConfig {
/** Customer identifier — bypasses guest flow when provided */
externalUserId?: string;
/** Called when a guest completes identification */
onGuestIdentified?: (identity: GuestIdentity) => void;
/** Called when the conversation is escalated to a human agent */
onEscalated?: () => void;
/** Called when the conversation is resolved */
onResolved?: (conversationId: string) => void;
}
/**
* Angular service for SyncAgent customer chat mode.
* Provide at component or module level.
*
* @example
* @Component({
* providers: [CustomerChatService],
* })
* export class SupportWidgetComponent {
* constructor(private chat: CustomerChatService) {
* chat.configure({
* apiKey: environment.syncagentKey,
* connectionString: environment.databaseUrl,
* customerMode: true,
* externalUserId: currentUser.id,
* });
* }
* }
*/
declare class CustomerChatService {
private client;
private _onEscalated?;
private _onResolved?;
private _onGuestIdentified?;
readonly isConfigured: _angular_core.WritableSignal<boolean>;
readonly messages: _angular_core.WritableSignal<Message[]>;
readonly conversationId: _angular_core.WritableSignal<string | null>;
readonly isLoading: _angular_core.WritableSignal<boolean>;
readonly isEscalated: _angular_core.WritableSignal<boolean>;
readonly isResolved: _angular_core.WritableSignal<boolean>;
readonly error: _angular_core.WritableSignal<Error | null>;
readonly welcomeMessage: _angular_core.WritableSignal<string | null>;
readonly isIdentified: _angular_core.WritableSignal<boolean>;
readonly guestIdentity: _angular_core.WritableSignal<GuestIdentity | null>;
private _messages$;
private _isLoading$;
private _error$;
private _escalated$;
private _resolved$;
private _isIdentified$;
private _guestIdentified$;
readonly messages$: rxjs.Observable<Message[]>;
readonly isLoading$: rxjs.Observable<boolean>;
readonly error$: rxjs.Observable<Error | null>;
/** Emits when the conversation is escalated to a human agent */
readonly escalated$: rxjs.Observable<void>;
/** Emits the conversation ID when the conversation is resolved */
readonly resolved$: rxjs.Observable<string>;
/** Emits the current identification state */
readonly isIdentified$: rxjs.Observable<boolean>;
/** Emits the GuestIdentity when guest identification completes */
readonly guestIdentified$: rxjs.Observable<GuestIdentity>;
configure(config: CustomerChatServiceConfig): void;
sendMessage(content: string, metadata?: Record<string, any>): Promise<void>;
rateConversation(rating: number): Promise<void>;
reset(): void;
/**
* Submit guest identification data.
* Validates, generates identifier, persists, and updates state.
*/
identifyGuest(data: {
name: string;
email: string;
phone?: string;
}): void;
private _setIdentified;
private _setMessages;
private _setLoading;
}
interface DualChatServiceConfig extends SyncAgentConfig {
context?: Record<string, any>;
onData?: (data: ToolData) => void;
onEscalated?: () => void;
onResolved?: (conversationId: string) => void;
}
/**
* Angular service for dual-mode SyncAgent chat (database + customer support).
* Provides both database agent and customer agent functionality through a single injectable.
*
* @example
* @Component({
* providers: [DualChatService],
* })
* export class DashboardComponent {
* constructor(private dual: DualChatService) {
* dual.configure({
* apiKey: environment.syncagentKey,
* connectionString: environment.databaseUrl,
* externalUserId: currentUser.id,
* });
* }
*
* askDb(question: string) {
* this.dual.sendDbMessage(question);
* }
*
* askSupport(message: string) {
* this.dual.sendSupportMessage(message);
* }
* }
*/
declare class DualChatService {
private client;
private config;
private abortController;
private _context?;
private _onData?;
private _onEscalated?;
private _onResolved?;
readonly dbMessages: _angular_core.WritableSignal<Message[]>;
readonly dbIsLoading: _angular_core.WritableSignal<boolean>;
readonly dbError: _angular_core.WritableSignal<Error | null>;
readonly supportMessages: _angular_core.WritableSignal<Message[]>;
readonly supportIsLoading: _angular_core.WritableSignal<boolean>;
readonly supportError: _angular_core.WritableSignal<Error | null>;
readonly supportConversationId: _angular_core.WritableSignal<string | null>;
readonly supportIsEscalated: _angular_core.WritableSignal<boolean>;
readonly supportIsResolved: _angular_core.WritableSignal<boolean>;
private _dbMessages$;
private _dbIsLoading$;
private _dbError$;
readonly dbMessages$: rxjs.Observable<Message[]>;
readonly dbIsLoading$: rxjs.Observable<boolean>;
readonly dbError$: rxjs.Observable<Error | null>;
private _supportMessages$;
private _supportIsLoading$;
private _supportError$;
readonly supportMessages$: rxjs.Observable<Message[]>;
readonly supportIsLoading$: rxjs.Observable<boolean>;
readonly supportError$: rxjs.Observable<Error | null>;
configure(config: DualChatServiceConfig): void;
sendDbMessage(content: string): Promise<void>;
sendSupportMessage(content: string, metadata?: Record<string, any>): Promise<void>;
stop(): void;
reset(): void;
private _setDbMessages;
private _setDbLoading;
private _setSupportMessages;
private _setSupportLoading;
}
export { CustomerChatService, type CustomerChatServiceConfig, DualChatService, type DualChatServiceConfig, SyncAgentService, type SyncAgentServiceConfig };
import * as rxjs from 'rxjs';
import * as _angular_core from '@angular/core';
import { Message, ToolData, SyncAgentConfig } from '@syncagent/js';
export { ChatOptions, ChatResult, Message, SyncAgentClient, SyncAgentConfig, ToolData, ToolDefinition, ToolParameter, detectPageContext } from '@syncagent/js';
import { Message, ToolData, SyncAgentConfig, GuestIdentity } from '@syncagent/js';
export { ChatOptions, ChatResult, CustomerChatOptions, CustomerChatResult, FieldValidationResult, GuestFormConfig, GuestIdentificationRequiredError, GuestIdentity, Message, SyncAgentClient, SyncAgentConfig, ToolData, ToolDefinition, ToolParameter, detectPageContext, generateGuestIdentifier, validateEmail, validateGuestForm, validateName } from '@syncagent/js';

@@ -70,2 +70,154 @@ interface SyncAgentServiceConfig extends SyncAgentConfig {

export { SyncAgentService, type SyncAgentServiceConfig };
interface CustomerChatServiceConfig extends SyncAgentConfig {
/** Customer identifier — bypasses guest flow when provided */
externalUserId?: string;
/** Called when a guest completes identification */
onGuestIdentified?: (identity: GuestIdentity) => void;
/** Called when the conversation is escalated to a human agent */
onEscalated?: () => void;
/** Called when the conversation is resolved */
onResolved?: (conversationId: string) => void;
}
/**
* Angular service for SyncAgent customer chat mode.
* Provide at component or module level.
*
* @example
* @Component({
* providers: [CustomerChatService],
* })
* export class SupportWidgetComponent {
* constructor(private chat: CustomerChatService) {
* chat.configure({
* apiKey: environment.syncagentKey,
* connectionString: environment.databaseUrl,
* customerMode: true,
* externalUserId: currentUser.id,
* });
* }
* }
*/
declare class CustomerChatService {
private client;
private _onEscalated?;
private _onResolved?;
private _onGuestIdentified?;
readonly isConfigured: _angular_core.WritableSignal<boolean>;
readonly messages: _angular_core.WritableSignal<Message[]>;
readonly conversationId: _angular_core.WritableSignal<string | null>;
readonly isLoading: _angular_core.WritableSignal<boolean>;
readonly isEscalated: _angular_core.WritableSignal<boolean>;
readonly isResolved: _angular_core.WritableSignal<boolean>;
readonly error: _angular_core.WritableSignal<Error | null>;
readonly welcomeMessage: _angular_core.WritableSignal<string | null>;
readonly isIdentified: _angular_core.WritableSignal<boolean>;
readonly guestIdentity: _angular_core.WritableSignal<GuestIdentity | null>;
private _messages$;
private _isLoading$;
private _error$;
private _escalated$;
private _resolved$;
private _isIdentified$;
private _guestIdentified$;
readonly messages$: rxjs.Observable<Message[]>;
readonly isLoading$: rxjs.Observable<boolean>;
readonly error$: rxjs.Observable<Error | null>;
/** Emits when the conversation is escalated to a human agent */
readonly escalated$: rxjs.Observable<void>;
/** Emits the conversation ID when the conversation is resolved */
readonly resolved$: rxjs.Observable<string>;
/** Emits the current identification state */
readonly isIdentified$: rxjs.Observable<boolean>;
/** Emits the GuestIdentity when guest identification completes */
readonly guestIdentified$: rxjs.Observable<GuestIdentity>;
configure(config: CustomerChatServiceConfig): void;
sendMessage(content: string, metadata?: Record<string, any>): Promise<void>;
rateConversation(rating: number): Promise<void>;
reset(): void;
/**
* Submit guest identification data.
* Validates, generates identifier, persists, and updates state.
*/
identifyGuest(data: {
name: string;
email: string;
phone?: string;
}): void;
private _setIdentified;
private _setMessages;
private _setLoading;
}
interface DualChatServiceConfig extends SyncAgentConfig {
context?: Record<string, any>;
onData?: (data: ToolData) => void;
onEscalated?: () => void;
onResolved?: (conversationId: string) => void;
}
/**
* Angular service for dual-mode SyncAgent chat (database + customer support).
* Provides both database agent and customer agent functionality through a single injectable.
*
* @example
* @Component({
* providers: [DualChatService],
* })
* export class DashboardComponent {
* constructor(private dual: DualChatService) {
* dual.configure({
* apiKey: environment.syncagentKey,
* connectionString: environment.databaseUrl,
* externalUserId: currentUser.id,
* });
* }
*
* askDb(question: string) {
* this.dual.sendDbMessage(question);
* }
*
* askSupport(message: string) {
* this.dual.sendSupportMessage(message);
* }
* }
*/
declare class DualChatService {
private client;
private config;
private abortController;
private _context?;
private _onData?;
private _onEscalated?;
private _onResolved?;
readonly dbMessages: _angular_core.WritableSignal<Message[]>;
readonly dbIsLoading: _angular_core.WritableSignal<boolean>;
readonly dbError: _angular_core.WritableSignal<Error | null>;
readonly supportMessages: _angular_core.WritableSignal<Message[]>;
readonly supportIsLoading: _angular_core.WritableSignal<boolean>;
readonly supportError: _angular_core.WritableSignal<Error | null>;
readonly supportConversationId: _angular_core.WritableSignal<string | null>;
readonly supportIsEscalated: _angular_core.WritableSignal<boolean>;
readonly supportIsResolved: _angular_core.WritableSignal<boolean>;
private _dbMessages$;
private _dbIsLoading$;
private _dbError$;
readonly dbMessages$: rxjs.Observable<Message[]>;
readonly dbIsLoading$: rxjs.Observable<boolean>;
readonly dbError$: rxjs.Observable<Error | null>;
private _supportMessages$;
private _supportIsLoading$;
private _supportError$;
readonly supportMessages$: rxjs.Observable<Message[]>;
readonly supportIsLoading$: rxjs.Observable<boolean>;
readonly supportError$: rxjs.Observable<Error | null>;
configure(config: DualChatServiceConfig): void;
sendDbMessage(content: string): Promise<void>;
sendSupportMessage(content: string, metadata?: Record<string, any>): Promise<void>;
stop(): void;
reset(): void;
private _setDbMessages;
private _setDbLoading;
private _setSupportMessages;
private _setSupportLoading;
}
export { CustomerChatService, type CustomerChatServiceConfig, DualChatService, type DualChatServiceConfig, SyncAgentService, type SyncAgentServiceConfig };

@@ -31,5 +31,12 @@ "use strict";

__export(index_exports, {
SyncAgentClient: () => import_js2.SyncAgentClient,
CustomerChatService: () => CustomerChatService,
DualChatService: () => DualChatService,
GuestIdentificationRequiredError: () => import_js5.GuestIdentificationRequiredError,
SyncAgentClient: () => import_js4.SyncAgentClient,
SyncAgentService: () => SyncAgentService,
detectPageContext: () => import_js2.detectPageContext
detectPageContext: () => import_js4.detectPageContext,
generateGuestIdentifier: () => import_js5.generateGuestIdentifier,
validateEmail: () => import_js5.validateEmail,
validateGuestForm: () => import_js5.validateGuestForm,
validateName: () => import_js5.validateName
});

@@ -162,9 +169,345 @@ module.exports = __toCommonJS(index_exports);

// src/customer-chat.service.ts
var import_core2 = require("@angular/core");
var import_rxjs2 = require("rxjs");
var import_js2 = require("@syncagent/js");
var CustomerChatService = class {
constructor() {
this.client = null;
// Reactive state using Angular signals
this.isConfigured = (0, import_core2.signal)(false);
this.messages = (0, import_core2.signal)([]);
this.conversationId = (0, import_core2.signal)(null);
this.isLoading = (0, import_core2.signal)(false);
this.isEscalated = (0, import_core2.signal)(false);
this.isResolved = (0, import_core2.signal)(false);
this.error = (0, import_core2.signal)(null);
this.welcomeMessage = (0, import_core2.signal)(null);
// Guest identification state
this.isIdentified = (0, import_core2.signal)(false);
this.guestIdentity = (0, import_core2.signal)(null);
// RxJS observables
this._messages$ = new import_rxjs2.BehaviorSubject([]);
this._isLoading$ = new import_rxjs2.BehaviorSubject(false);
this._error$ = new import_rxjs2.BehaviorSubject(null);
this._escalated$ = new import_rxjs2.Subject();
this._resolved$ = new import_rxjs2.Subject();
this._isIdentified$ = new import_rxjs2.BehaviorSubject(false);
this._guestIdentified$ = new import_rxjs2.Subject();
this.messages$ = this._messages$.asObservable();
this.isLoading$ = this._isLoading$.asObservable();
this.error$ = this._error$.asObservable();
/** Emits when the conversation is escalated to a human agent */
this.escalated$ = this._escalated$.asObservable();
/** Emits the conversation ID when the conversation is resolved */
this.resolved$ = this._resolved$.asObservable();
/** Emits the current identification state */
this.isIdentified$ = this._isIdentified$.asObservable();
/** Emits the GuestIdentity when guest identification completes */
this.guestIdentified$ = this._guestIdentified$.asObservable();
}
configure(config) {
this.client = new import_js2.SyncAgentClient(config);
this._onEscalated = config.onEscalated;
this._onResolved = config.onResolved;
this._onGuestIdentified = config.onGuestIdentified;
this.isConfigured.set(true);
if (config.externalUserId) {
this._setIdentified(true, null);
} else {
const stored = this.client.getGuestIdentity();
if (stored) {
this._setIdentified(true, stored);
}
}
}
async sendMessage(content, metadata) {
if (!this.client) throw new Error("CustomerChatService: call configure() first");
if (!this.isIdentified()) {
const err = new import_js2.GuestIdentificationRequiredError();
this.error.set(err);
this._error$.next(err);
return;
}
if (!content.trim() || this.isLoading()) return;
const userMsg = { role: "user", content };
this._setMessages([...this.messages(), userMsg]);
this._setLoading(true);
this.error.set(null);
this._error$.next(null);
const chatOptions = {
conversationId: this.conversationId() || void 0,
metadata,
onEscalated: () => {
this.isEscalated.set(true);
this._escalated$.next();
this._onEscalated?.();
},
onResolved: (id) => {
this.isResolved.set(true);
this._resolved$.next(id);
this._onResolved?.(id);
}
};
try {
const result = await this.client.customerChat(content, chatOptions);
this.conversationId.set(result.conversationId);
if (result.welcomeMessage) {
this.welcomeMessage.set(result.welcomeMessage);
}
if (result.response) {
const assistantMsg = { role: "assistant", content: result.response };
this._setMessages([...this.messages(), assistantMsg]);
}
} catch (e) {
const err = e instanceof Error ? e : new Error(String(e));
this.error.set(err);
this._error$.next(err);
} finally {
this._setLoading(false);
}
}
async rateConversation(rating) {
if (!this.client) throw new Error("CustomerChatService: call configure() first");
const id = this.conversationId();
if (!id) throw new Error("No active conversation to rate");
await this.client.rateConversation(id, rating);
}
reset() {
this._setMessages([]);
this.conversationId.set(null);
this._setLoading(false);
this.isEscalated.set(false);
this.isResolved.set(false);
this.error.set(null);
this._error$.next(null);
this.welcomeMessage.set(null);
}
/**
* Submit guest identification data.
* Validates, generates identifier, persists, and updates state.
*/
identifyGuest(data) {
const validation = (0, import_js2.validateGuestForm)(data);
if (!validation.valid) {
const err = new Error(Object.values(validation.errors).join(", "));
this.error.set(err);
this._error$.next(err);
return;
}
const guestId = (0, import_js2.generateGuestIdentifier)(data.email);
const identity = {
name: data.name,
email: data.email,
phone: data.phone || null,
guestId
};
this.client.setGuestIdentity(identity);
this._setIdentified(true, identity);
this._guestIdentified$.next(identity);
this.error.set(null);
this._error$.next(null);
this._onGuestIdentified?.(identity);
}
_setIdentified(value, identity) {
this.isIdentified.set(value);
this._isIdentified$.next(value);
this.guestIdentity.set(identity);
}
_setMessages(msgs) {
this.messages.set(msgs);
this._messages$.next(msgs);
}
_setLoading(v) {
this.isLoading.set(v);
this._isLoading$.next(v);
}
};
CustomerChatService = __decorateClass([
(0, import_core2.Injectable)()
], CustomerChatService);
// src/dual-chat.service.ts
var import_core3 = require("@angular/core");
var import_rxjs3 = require("rxjs");
var import_js3 = require("@syncagent/js");
var DualChatService = class {
constructor() {
this.client = null;
this.config = null;
this.abortController = null;
// DB mode signals
this.dbMessages = (0, import_core3.signal)([]);
this.dbIsLoading = (0, import_core3.signal)(false);
this.dbError = (0, import_core3.signal)(null);
// Support mode signals
this.supportMessages = (0, import_core3.signal)([]);
this.supportIsLoading = (0, import_core3.signal)(false);
this.supportError = (0, import_core3.signal)(null);
this.supportConversationId = (0, import_core3.signal)(null);
this.supportIsEscalated = (0, import_core3.signal)(false);
this.supportIsResolved = (0, import_core3.signal)(false);
// DB mode RxJS observables
this._dbMessages$ = new import_rxjs3.BehaviorSubject([]);
this._dbIsLoading$ = new import_rxjs3.BehaviorSubject(false);
this._dbError$ = new import_rxjs3.BehaviorSubject(null);
this.dbMessages$ = this._dbMessages$.asObservable();
this.dbIsLoading$ = this._dbIsLoading$.asObservable();
this.dbError$ = this._dbError$.asObservable();
// Support mode RxJS observables
this._supportMessages$ = new import_rxjs3.BehaviorSubject([]);
this._supportIsLoading$ = new import_rxjs3.BehaviorSubject(false);
this._supportError$ = new import_rxjs3.BehaviorSubject(null);
this.supportMessages$ = this._supportMessages$.asObservable();
this.supportIsLoading$ = this._supportIsLoading$.asObservable();
this.supportError$ = this._supportError$.asObservable();
}
configure(config) {
this.client = new import_js3.SyncAgentClient(config);
this.config = config;
this._context = config.context;
this._onData = config.onData;
this._onEscalated = config.onEscalated;
this._onResolved = config.onResolved;
}
async sendDbMessage(content) {
if (!this.client) throw new Error("DualChatService: call configure() first");
if (!content.trim() || this.dbIsLoading()) return;
const userMsg = { role: "user", content };
const updated = [...this.dbMessages(), userMsg];
this._setDbMessages(updated);
this._setDbLoading(true);
this.dbError.set(null);
this._dbError$.next(null);
const placeholder = { role: "assistant", content: "" };
this._setDbMessages([...updated, placeholder]);
this.abortController = new AbortController();
try {
await this.client.chat(updated, {
signal: this.abortController.signal,
context: this._context,
onData: (data) => {
this._onData?.(data);
},
onToken: (token) => {
placeholder.content += token;
const msgs = [...this.dbMessages()];
msgs[msgs.length - 1] = { ...placeholder };
this._setDbMessages(msgs);
},
onComplete: (text) => {
const msgs = [...this.dbMessages()];
const finalMsg = { role: "assistant", content: text };
msgs[msgs.length - 1] = finalMsg;
this._setDbMessages(msgs);
}
});
} catch (e) {
if (e.name !== "AbortError") {
const err = e instanceof Error ? e : new Error(String(e));
this.dbError.set(err);
this._dbError$.next(err);
const msgs = [...this.dbMessages()];
if (msgs[msgs.length - 1]?.content === "") {
this._setDbMessages(msgs.slice(0, -1));
}
}
} finally {
this._setDbLoading(false);
this.abortController = null;
}
}
async sendSupportMessage(content, metadata) {
if (!this.client) throw new Error("DualChatService: call configure() first");
if (!this.config?.externalUserId) {
throw new Error("DualChatService: externalUserId is required for customer chat");
}
if (!content.trim() || this.supportIsLoading()) return;
const userMsg = { role: "user", content };
this._setSupportMessages([...this.supportMessages(), userMsg]);
this._setSupportLoading(true);
this.supportError.set(null);
this._supportError$.next(null);
const chatOptions = {
conversationId: this.supportConversationId() || void 0,
metadata,
onEscalated: () => {
this.supportIsEscalated.set(true);
this._onEscalated?.();
},
onResolved: (id) => {
this.supportIsResolved.set(true);
this._onResolved?.(id);
}
};
try {
const result = await this.client.customerChat(content, chatOptions);
this.supportConversationId.set(result.conversationId);
if (result.response) {
const assistantMsg = { role: "assistant", content: result.response };
this._setSupportMessages([...this.supportMessages(), assistantMsg]);
}
} catch (e) {
const err = e instanceof Error ? e : new Error(String(e));
this.supportError.set(err);
this._supportError$.next(err);
} finally {
this._setSupportLoading(false);
}
}
stop() {
if (!this.client) throw new Error("DualChatService: call configure() first");
this.abortController?.abort();
}
reset() {
if (!this.client) throw new Error("DualChatService: call configure() first");
this.abortController?.abort();
this._setDbMessages([]);
this.dbError.set(null);
this._dbError$.next(null);
this._setDbLoading(false);
this._setSupportMessages([]);
this.supportError.set(null);
this._supportError$.next(null);
this._setSupportLoading(false);
this.supportConversationId.set(null);
this.supportIsEscalated.set(false);
this.supportIsResolved.set(false);
}
_setDbMessages(msgs) {
this.dbMessages.set(msgs);
this._dbMessages$.next(msgs);
}
_setDbLoading(v) {
this.dbIsLoading.set(v);
this._dbIsLoading$.next(v);
}
_setSupportMessages(msgs) {
this.supportMessages.set(msgs);
this._supportMessages$.next(msgs);
}
_setSupportLoading(v) {
this.supportIsLoading.set(v);
this._supportIsLoading$.next(v);
}
};
DualChatService = __decorateClass([
(0, import_core3.Injectable)()
], DualChatService);
// src/index.ts
var import_js2 = require("@syncagent/js");
var import_js4 = require("@syncagent/js");
var import_js5 = require("@syncagent/js");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CustomerChatService,
DualChatService,
GuestIdentificationRequiredError,
SyncAgentClient,
SyncAgentService,
detectPageContext
detectPageContext,
generateGuestIdentifier,
validateEmail,
validateGuestForm,
validateName
});

@@ -136,8 +136,351 @@ var __defProp = Object.defineProperty;

// src/customer-chat.service.ts
import { Injectable as Injectable2, signal as signal2 } from "@angular/core";
import { BehaviorSubject as BehaviorSubject2, Subject as Subject2 } from "rxjs";
import {
SyncAgentClient as SyncAgentClient2,
validateGuestForm,
generateGuestIdentifier,
GuestIdentificationRequiredError
} from "@syncagent/js";
var CustomerChatService = class {
constructor() {
this.client = null;
// Reactive state using Angular signals
this.isConfigured = signal2(false);
this.messages = signal2([]);
this.conversationId = signal2(null);
this.isLoading = signal2(false);
this.isEscalated = signal2(false);
this.isResolved = signal2(false);
this.error = signal2(null);
this.welcomeMessage = signal2(null);
// Guest identification state
this.isIdentified = signal2(false);
this.guestIdentity = signal2(null);
// RxJS observables
this._messages$ = new BehaviorSubject2([]);
this._isLoading$ = new BehaviorSubject2(false);
this._error$ = new BehaviorSubject2(null);
this._escalated$ = new Subject2();
this._resolved$ = new Subject2();
this._isIdentified$ = new BehaviorSubject2(false);
this._guestIdentified$ = new Subject2();
this.messages$ = this._messages$.asObservable();
this.isLoading$ = this._isLoading$.asObservable();
this.error$ = this._error$.asObservable();
/** Emits when the conversation is escalated to a human agent */
this.escalated$ = this._escalated$.asObservable();
/** Emits the conversation ID when the conversation is resolved */
this.resolved$ = this._resolved$.asObservable();
/** Emits the current identification state */
this.isIdentified$ = this._isIdentified$.asObservable();
/** Emits the GuestIdentity when guest identification completes */
this.guestIdentified$ = this._guestIdentified$.asObservable();
}
configure(config) {
this.client = new SyncAgentClient2(config);
this._onEscalated = config.onEscalated;
this._onResolved = config.onResolved;
this._onGuestIdentified = config.onGuestIdentified;
this.isConfigured.set(true);
if (config.externalUserId) {
this._setIdentified(true, null);
} else {
const stored = this.client.getGuestIdentity();
if (stored) {
this._setIdentified(true, stored);
}
}
}
async sendMessage(content, metadata) {
if (!this.client) throw new Error("CustomerChatService: call configure() first");
if (!this.isIdentified()) {
const err = new GuestIdentificationRequiredError();
this.error.set(err);
this._error$.next(err);
return;
}
if (!content.trim() || this.isLoading()) return;
const userMsg = { role: "user", content };
this._setMessages([...this.messages(), userMsg]);
this._setLoading(true);
this.error.set(null);
this._error$.next(null);
const chatOptions = {
conversationId: this.conversationId() || void 0,
metadata,
onEscalated: () => {
this.isEscalated.set(true);
this._escalated$.next();
this._onEscalated?.();
},
onResolved: (id) => {
this.isResolved.set(true);
this._resolved$.next(id);
this._onResolved?.(id);
}
};
try {
const result = await this.client.customerChat(content, chatOptions);
this.conversationId.set(result.conversationId);
if (result.welcomeMessage) {
this.welcomeMessage.set(result.welcomeMessage);
}
if (result.response) {
const assistantMsg = { role: "assistant", content: result.response };
this._setMessages([...this.messages(), assistantMsg]);
}
} catch (e) {
const err = e instanceof Error ? e : new Error(String(e));
this.error.set(err);
this._error$.next(err);
} finally {
this._setLoading(false);
}
}
async rateConversation(rating) {
if (!this.client) throw new Error("CustomerChatService: call configure() first");
const id = this.conversationId();
if (!id) throw new Error("No active conversation to rate");
await this.client.rateConversation(id, rating);
}
reset() {
this._setMessages([]);
this.conversationId.set(null);
this._setLoading(false);
this.isEscalated.set(false);
this.isResolved.set(false);
this.error.set(null);
this._error$.next(null);
this.welcomeMessage.set(null);
}
/**
* Submit guest identification data.
* Validates, generates identifier, persists, and updates state.
*/
identifyGuest(data) {
const validation = validateGuestForm(data);
if (!validation.valid) {
const err = new Error(Object.values(validation.errors).join(", "));
this.error.set(err);
this._error$.next(err);
return;
}
const guestId = generateGuestIdentifier(data.email);
const identity = {
name: data.name,
email: data.email,
phone: data.phone || null,
guestId
};
this.client.setGuestIdentity(identity);
this._setIdentified(true, identity);
this._guestIdentified$.next(identity);
this.error.set(null);
this._error$.next(null);
this._onGuestIdentified?.(identity);
}
_setIdentified(value, identity) {
this.isIdentified.set(value);
this._isIdentified$.next(value);
this.guestIdentity.set(identity);
}
_setMessages(msgs) {
this.messages.set(msgs);
this._messages$.next(msgs);
}
_setLoading(v) {
this.isLoading.set(v);
this._isLoading$.next(v);
}
};
CustomerChatService = __decorateClass([
Injectable2()
], CustomerChatService);
// src/dual-chat.service.ts
import { Injectable as Injectable3, signal as signal3 } from "@angular/core";
import { BehaviorSubject as BehaviorSubject3 } from "rxjs";
import {
SyncAgentClient as SyncAgentClient3
} from "@syncagent/js";
var DualChatService = class {
constructor() {
this.client = null;
this.config = null;
this.abortController = null;
// DB mode signals
this.dbMessages = signal3([]);
this.dbIsLoading = signal3(false);
this.dbError = signal3(null);
// Support mode signals
this.supportMessages = signal3([]);
this.supportIsLoading = signal3(false);
this.supportError = signal3(null);
this.supportConversationId = signal3(null);
this.supportIsEscalated = signal3(false);
this.supportIsResolved = signal3(false);
// DB mode RxJS observables
this._dbMessages$ = new BehaviorSubject3([]);
this._dbIsLoading$ = new BehaviorSubject3(false);
this._dbError$ = new BehaviorSubject3(null);
this.dbMessages$ = this._dbMessages$.asObservable();
this.dbIsLoading$ = this._dbIsLoading$.asObservable();
this.dbError$ = this._dbError$.asObservable();
// Support mode RxJS observables
this._supportMessages$ = new BehaviorSubject3([]);
this._supportIsLoading$ = new BehaviorSubject3(false);
this._supportError$ = new BehaviorSubject3(null);
this.supportMessages$ = this._supportMessages$.asObservable();
this.supportIsLoading$ = this._supportIsLoading$.asObservable();
this.supportError$ = this._supportError$.asObservable();
}
configure(config) {
this.client = new SyncAgentClient3(config);
this.config = config;
this._context = config.context;
this._onData = config.onData;
this._onEscalated = config.onEscalated;
this._onResolved = config.onResolved;
}
async sendDbMessage(content) {
if (!this.client) throw new Error("DualChatService: call configure() first");
if (!content.trim() || this.dbIsLoading()) return;
const userMsg = { role: "user", content };
const updated = [...this.dbMessages(), userMsg];
this._setDbMessages(updated);
this._setDbLoading(true);
this.dbError.set(null);
this._dbError$.next(null);
const placeholder = { role: "assistant", content: "" };
this._setDbMessages([...updated, placeholder]);
this.abortController = new AbortController();
try {
await this.client.chat(updated, {
signal: this.abortController.signal,
context: this._context,
onData: (data) => {
this._onData?.(data);
},
onToken: (token) => {
placeholder.content += token;
const msgs = [...this.dbMessages()];
msgs[msgs.length - 1] = { ...placeholder };
this._setDbMessages(msgs);
},
onComplete: (text) => {
const msgs = [...this.dbMessages()];
const finalMsg = { role: "assistant", content: text };
msgs[msgs.length - 1] = finalMsg;
this._setDbMessages(msgs);
}
});
} catch (e) {
if (e.name !== "AbortError") {
const err = e instanceof Error ? e : new Error(String(e));
this.dbError.set(err);
this._dbError$.next(err);
const msgs = [...this.dbMessages()];
if (msgs[msgs.length - 1]?.content === "") {
this._setDbMessages(msgs.slice(0, -1));
}
}
} finally {
this._setDbLoading(false);
this.abortController = null;
}
}
async sendSupportMessage(content, metadata) {
if (!this.client) throw new Error("DualChatService: call configure() first");
if (!this.config?.externalUserId) {
throw new Error("DualChatService: externalUserId is required for customer chat");
}
if (!content.trim() || this.supportIsLoading()) return;
const userMsg = { role: "user", content };
this._setSupportMessages([...this.supportMessages(), userMsg]);
this._setSupportLoading(true);
this.supportError.set(null);
this._supportError$.next(null);
const chatOptions = {
conversationId: this.supportConversationId() || void 0,
metadata,
onEscalated: () => {
this.supportIsEscalated.set(true);
this._onEscalated?.();
},
onResolved: (id) => {
this.supportIsResolved.set(true);
this._onResolved?.(id);
}
};
try {
const result = await this.client.customerChat(content, chatOptions);
this.supportConversationId.set(result.conversationId);
if (result.response) {
const assistantMsg = { role: "assistant", content: result.response };
this._setSupportMessages([...this.supportMessages(), assistantMsg]);
}
} catch (e) {
const err = e instanceof Error ? e : new Error(String(e));
this.supportError.set(err);
this._supportError$.next(err);
} finally {
this._setSupportLoading(false);
}
}
stop() {
if (!this.client) throw new Error("DualChatService: call configure() first");
this.abortController?.abort();
}
reset() {
if (!this.client) throw new Error("DualChatService: call configure() first");
this.abortController?.abort();
this._setDbMessages([]);
this.dbError.set(null);
this._dbError$.next(null);
this._setDbLoading(false);
this._setSupportMessages([]);
this.supportError.set(null);
this._supportError$.next(null);
this._setSupportLoading(false);
this.supportConversationId.set(null);
this.supportIsEscalated.set(false);
this.supportIsResolved.set(false);
}
_setDbMessages(msgs) {
this.dbMessages.set(msgs);
this._dbMessages$.next(msgs);
}
_setDbLoading(v) {
this.dbIsLoading.set(v);
this._dbIsLoading$.next(v);
}
_setSupportMessages(msgs) {
this.supportMessages.set(msgs);
this._supportMessages$.next(msgs);
}
_setSupportLoading(v) {
this.supportIsLoading.set(v);
this._supportIsLoading$.next(v);
}
};
DualChatService = __decorateClass([
Injectable3()
], DualChatService);
// src/index.ts
import { SyncAgentClient as SyncAgentClient2, detectPageContext } from "@syncagent/js";
import { SyncAgentClient as SyncAgentClient4, detectPageContext } from "@syncagent/js";
import { GuestIdentificationRequiredError as GuestIdentificationRequiredError2, validateGuestForm as validateGuestForm2, validateName, validateEmail, generateGuestIdentifier as generateGuestIdentifier2 } from "@syncagent/js";
export {
SyncAgentClient2 as SyncAgentClient,
CustomerChatService,
DualChatService,
GuestIdentificationRequiredError2 as GuestIdentificationRequiredError,
SyncAgentClient4 as SyncAgentClient,
SyncAgentService,
detectPageContext
detectPageContext,
generateGuestIdentifier2 as generateGuestIdentifier,
validateEmail,
validateGuestForm2 as validateGuestForm,
validateName
};
+12
-3
{
"name": "@syncagent/angular",
"version": "0.3.4",
"version": "0.4.0",
"description": "SyncAgent Angular SDK — service and component for AI database chat",

@@ -21,3 +21,5 @@ "homepage": "https://syncagentdev.vercel.app/docs",

},
"files": ["dist"],
"files": [
"dist"
],
"scripts": {

@@ -42,3 +44,10 @@ "build": "tsup",

"license": "MIT",
"keywords": ["syncagent", "angular", "ai", "database", "agent", "service"]
"keywords": [
"syncagent",
"angular",
"ai",
"database",
"agent",
"service"
]
}
+514
-0

@@ -211,2 +211,516 @@ # @syncagent/angular

## Customer Agent Mode
The `CustomerChatService` is a dedicated Angular service for building customer-facing support interfaces. It routes messages through SyncAgent's customer support pipeline — using your configured persona, flows, knowledge base, and escalation rules — instead of direct database access.
Provide it at the component or module level and call `configure()` before sending messages.
```typescript
import { Component, OnInit } from "@angular/core";
import { CustomerChatService } from "@syncagent/angular";
import { environment } from "./environments/environment";
@Component({
selector: "app-support-widget",
providers: [CustomerChatService],
template: `
<div class="chat">
@if (chat.welcomeMessage()) {
<p class="welcome">{{ chat.welcomeMessage() }}</p>
}
<div class="messages">
@for (msg of chat.messages(); track msg.content) {
<div [class]="'message ' + msg.role">{{ msg.content }}</div>
}
</div>
@if (chat.isEscalated()) {
<p class="notice">You've been connected to a human agent.</p>
}
@if (chat.error() as err) {
<p class="error">{{ err.message }}</p>
}
<div class="input-row">
<input
[(ngModel)]="input"
(keydown.enter)="send()"
placeholder="How can we help?"
[disabled]="chat.isLoading()"
/>
<button (click)="send()" [disabled]="chat.isLoading() || !input.trim()">Send</button>
</div>
@if (chat.isResolved()) {
<div class="rating">
<p>How was your experience?</p>
<button *ngFor="let r of [1,2,3,4,5]" (click)="rate(r)">{{ r }}⭐</button>
</div>
}
</div>
`,
})
export class SupportWidgetComponent implements OnInit {
input = "";
constructor(public chat: CustomerChatService) {}
ngOnInit() {
this.chat.configure({
apiKey: environment.syncagentKey,
connectionString: environment.databaseUrl,
externalUserId: "user_abc123",
onEscalated: () => console.log("Escalated to human agent"),
onResolved: (id) => console.log("Conversation resolved:", id),
});
}
send() {
if (!this.input.trim()) return;
this.chat.sendMessage(this.input);
this.input = "";
}
rate(rating: number) {
this.chat.rateConversation(rating);
}
}
```
### Angular Signals
| Signal | Type | Description |
|--------|------|-------------|
| `messages()` | `Message[]` | Conversation message history |
| `conversationId()` | `string \| null` | Current conversation ID |
| `isLoading()` | `boolean` | True while waiting for a response |
| `isEscalated()` | `boolean` | True when escalated to a human agent |
| `isResolved()` | `boolean` | True when the conversation is resolved |
| `error()` | `Error \| null` | Last error, if any |
| `welcomeMessage()` | `string \| null` | Welcome message (first interaction only) |
| `isConfigured()` | `boolean` | True after `configure()` has been called |
### RxJS Observables
| Observable | Type | Description |
|------------|------|-------------|
| `messages$` | `Observable<Message[]>` | Conversation history stream |
| `isLoading$` | `Observable<boolean>` | Loading state stream |
| `error$` | `Observable<Error \| null>` | Error stream |
| `escalated$` | `Observable<void>` | Emits when escalated to a human agent |
| `resolved$` | `Observable<string>` | Emits the conversation ID when resolved |
### `CustomerChatServiceConfig`
Extends `SyncAgentConfig` with customer-mode-specific options:
| Property | Type | Description |
|----------|------|-------------|
| `apiKey` | `string` | Your SyncAgent API key |
| `connectionString` | `string` | Database connection string |
| `customerMode` | `true` | Enables customer agent mode |
| `externalUserId` | `string` | Unique identifier for the end-user |
| `onEscalated` | `() => void` | Optional callback when escalated to a human agent |
| `onResolved` | `(conversationId: string) => void` | Optional callback when the conversation is resolved |
### Methods
| Method | Signature | Description |
|--------|-----------|-------------|
| `configure(config)` | `configure(config: CustomerChatServiceConfig): void` | Initialize the service with customer mode config. Must be called before other methods. |
| `sendMessage(content, metadata?)` | `sendMessage(content: string, metadata?: Record<string, any>): Promise<void>` | Send a message and receive an AI response. Ignored if content is empty or already loading. |
| `rateConversation(rating)` | `rateConversation(rating: number): Promise<void>` | Rate the current conversation (1-5). Throws if no active conversation. |
| `reset()` | `reset(): void` | Clear all state (messages, conversation ID, errors) and start fresh. |
### Guest Identification
When no `externalUserId` is provided, the `CustomerChatService` activates the guest identification flow. Guests must identify themselves before sending messages. The service exposes Angular signals and RxJS observables for reactive state management.
#### Signals & Observables
| Signal / Observable | Type | Description |
|---------------------|------|-------------|
| `isIdentified()` | `Signal<boolean>` | `true` when the guest has been identified (via form or stored identity) |
| `guestIdentity()` | `Signal<GuestIdentity \| null>` | The current guest identity, or `null` if not yet identified |
| `isIdentified$` | `Observable<boolean>` | Emits the current identification state (BehaviorSubject) |
| `guestIdentified$` | `Observable<GuestIdentity>` | Emits the `GuestIdentity` when identification completes |
#### `identifyGuest(data)`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | `string` | Yes | Guest's display name |
| `email` | `string` | Yes | Guest's email address |
| `phone` | `string` | No | Guest's phone number |
Validates input, generates a deterministic guest identifier from the email, persists the identity to localStorage, updates signals/observables, and invokes the `onGuestIdentified` callback if configured.
#### Template Usage with Signals
```typescript
import { Component, OnInit } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { CustomerChatService } from "@syncagent/angular";
import { environment } from "./environments/environment";
@Component({
selector: "app-support-widget",
providers: [CustomerChatService],
imports: [FormsModule],
template: `
@if (!chat.isIdentified()) {
<form (ngSubmit)="identify()">
<h2>Welcome</h2>
<p>Please introduce yourself to get started</p>
<label for="name">Name</label>
<input id="name" [(ngModel)]="name" name="name" required />
<label for="email">Email</label>
<input id="email" [(ngModel)]="email" name="email" type="email" required />
<label for="phone">Phone (optional)</label>
<input id="phone" [(ngModel)]="phone" name="phone" type="tel" />
@if (chat.error() as err) {
<p class="error">{{ err.message }}</p>
}
<button type="submit">Start Chat</button>
</form>
} @else {
<div class="chat">
<div class="messages">
@for (msg of chat.messages(); track msg.content) {
<div [class]="'message ' + msg.role">{{ msg.content }}</div>
}
</div>
<div class="input-row">
<input
[(ngModel)]="input"
(keydown.enter)="send()"
placeholder="How can we help?"
[disabled]="chat.isLoading()"
/>
<button (click)="send()" [disabled]="chat.isLoading() || !input.trim()">Send</button>
</div>
</div>
}
`,
})
export class SupportWidgetComponent implements OnInit {
name = "";
email = "";
phone = "";
input = "";
constructor(public chat: CustomerChatService) {}
ngOnInit() {
this.chat.configure({
apiKey: environment.syncagentKey,
connectionString: environment.databaseUrl,
onGuestIdentified: (identity) => {
console.log("Guest identified:", identity.guestId);
},
});
}
identify() {
this.chat.identifyGuest({
name: this.name,
email: this.email,
phone: this.phone || undefined,
});
}
send() {
if (!this.input.trim()) return;
this.chat.sendMessage(this.input);
this.input = "";
}
}
```
#### Reactive Handling with `guestIdentified$`
```typescript
import { Component, OnInit, OnDestroy } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { CustomerChatService } from "@syncagent/angular";
import { environment } from "./environments/environment";
@Component({
selector: "app-guest-chat",
providers: [CustomerChatService],
template: `<!-- ... -->`,
})
export class GuestChatComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
constructor(public chat: CustomerChatService) {}
ngOnInit() {
this.chat.configure({
apiKey: environment.syncagentKey,
connectionString: environment.databaseUrl,
});
// React to guest identification
this.chat.guestIdentified$
.pipe(takeUntil(this.destroy$))
.subscribe((identity) => {
console.log(`Guest ${identity.name} identified as ${identity.guestId}`);
// e.g., send analytics event, update UI state
});
// Track identification state changes
this.chat.isIdentified$
.pipe(takeUntil(this.destroy$))
.subscribe((identified) => {
console.log("Identification state:", identified);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
```
### Dual Mode (Database + Customer Agent)
> ⚠️ **Deprecated:** `createDual()` will be removed in a future major version. Use `DualChatService` instead — pass `externalUserId` directly to enable both modes on a single instance.
**Legacy pattern (deprecated):**
```typescript
import { Component, OnInit } from "@angular/core";
import { SyncAgentClient } from "@syncagent/js";
import { SyncAgentService, CustomerChatService } from "@syncagent/angular";
import { environment } from "./environments/environment";
@Component({
selector: "app-dual-chat",
providers: [SyncAgentService, CustomerChatService],
template: `<!-- admin + customer UIs -->`,
})
export class DualChatComponent implements OnInit {
constructor(
public admin: SyncAgentService,
public support: CustomerChatService,
) {}
ngOnInit() {
const { db, support } = SyncAgentClient.createDual({
apiKey: environment.syncagentKey,
connectionString: environment.databaseUrl,
externalUserId: "customer_123",
});
// Admin: direct database queries
this.admin.configure({
apiKey: environment.syncagentKey,
connectionString: environment.databaseUrl,
});
// Customer: support pipeline
this.support.configure({
apiKey: environment.syncagentKey,
connectionString: environment.databaseUrl,
customerMode: true,
externalUserId: "customer_123",
});
}
}
```
**Before (deprecated):**
```typescript
const { db, support } = SyncAgentClient.createDual({
apiKey: environment.syncagentKey,
connectionString: environment.databaseUrl,
externalUserId: "customer_123",
});
```
**After (recommended):**
```typescript
import { DualChatService } from "@syncagent/angular";
@Component({
providers: [DualChatService],
})
export class DualChatComponent implements OnInit {
constructor(public dual: DualChatService) {}
ngOnInit() {
this.dual.configure({
apiKey: environment.syncagentKey,
connectionString: environment.databaseUrl,
externalUserId: "customer_123",
});
}
}
```
> **Tip:** In Angular, you can also use `createDual()` to get both client instances and pass them to your services' internal logic, or simply configure each service independently as shown above.
## Unified Dual Mode
The `DualChatService` provides a single injectable service that manages both database agent and customer support chat from one component. Instead of wiring up `SyncAgentService` and `CustomerChatService` separately, inject `DualChatService` and call `configure()` with your config including `externalUserId` — both modes become available immediately.
```typescript
import { Component, OnInit } from "@angular/core";
import { DualChatService } from "@syncagent/angular";
import { environment } from "./environments/environment";
@Component({
selector: "app-dual-chat",
providers: [DualChatService],
template: `
<div class="dual-chat">
<section class="db-panel">
<h3>Database Agent</h3>
<div class="messages">
@for (msg of dual.dbMessages(); track msg.content) {
<div [class]="'message ' + msg.role">{{ msg.content }}</div>
}
</div>
@if (dual.dbError() as err) {
<p class="error">{{ err.message }}</p>
}
<div class="input-row">
<input
[(ngModel)]="dbInput"
(keydown.enter)="sendDb()"
placeholder="Ask about your data..."
[disabled]="dual.dbIsLoading()"
/>
<button (click)="sendDb()" [disabled]="dual.dbIsLoading() || !dbInput.trim()">Send</button>
</div>
</section>
<section class="support-panel">
<h3>Customer Support</h3>
<div class="messages">
@for (msg of dual.supportMessages(); track msg.content) {
<div [class]="'message ' + msg.role">{{ msg.content }}</div>
}
</div>
@if (dual.supportIsEscalated()) {
<p class="notice">Connected to a human agent.</p>
}
@if (dual.supportError() as err) {
<p class="error">{{ err.message }}</p>
}
<div class="input-row">
<input
[(ngModel)]="supportInput"
(keydown.enter)="sendSupport()"
placeholder="How can we help?"
[disabled]="dual.supportIsLoading()"
/>
<button (click)="sendSupport()" [disabled]="dual.supportIsLoading() || !supportInput.trim()">Send</button>
</div>
</section>
</div>
`,
})
export class DualChatComponent implements OnInit {
dbInput = "";
supportInput = "";
constructor(public dual: DualChatService) {}
ngOnInit() {
this.dual.configure({
apiKey: environment.syncagentKey,
connectionString: environment.databaseUrl,
externalUserId: "user_abc123",
onEscalated: () => console.log("Escalated to human agent"),
onResolved: (id) => console.log("Resolved:", id),
});
}
sendDb() {
if (!this.dbInput.trim()) return;
this.dual.sendDbMessage(this.dbInput);
this.dbInput = "";
}
sendSupport() {
if (!this.supportInput.trim()) return;
this.dual.sendSupportMessage(this.supportInput);
this.supportInput = "";
}
}
```
### Error Handling
If `sendDbMessage()`, `sendSupportMessage()`, `stop()`, or `reset()` is called before `configure()`, the service throws:
```
DualChatService: call configure() first
```
If `sendSupportMessage()` is called without `externalUserId` in the config, the service throws:
```
DualChatService: externalUserId is required for customer chat
```
### Angular Signals
| Signal | Type | Description |
|--------|------|-------------|
| `dbMessages()` | `Message[]` | Database agent conversation message history |
| `dbIsLoading()` | `boolean` | True while the database agent is processing |
| `dbError()` | `Error \| null` | Last error from the database agent, if any |
| `supportMessages()` | `Message[]` | Customer support conversation message history |
| `supportIsLoading()` | `boolean` | True while the support agent is processing |
| `supportError()` | `Error \| null` | Last error from the support agent, if any |
| `supportConversationId()` | `string \| null` | Current support conversation ID |
| `supportIsEscalated()` | `boolean` | True when escalated to a human agent |
| `supportIsResolved()` | `boolean` | True when the support conversation is resolved |
### RxJS Observables
| Observable | Type | Description |
|------------|------|-------------|
| `dbMessages$` | `Observable<Message[]>` | Database agent message history stream |
| `dbIsLoading$` | `Observable<boolean>` | Database agent loading state stream |
| `dbError$` | `Observable<Error \| null>` | Database agent error stream |
| `supportMessages$` | `Observable<Message[]>` | Support agent message history stream |
| `supportIsLoading$` | `Observable<boolean>` | Support agent loading state stream |
| `supportError$` | `Observable<Error \| null>` | Support agent error stream |
### `DualChatServiceConfig`
| Property | Type | Description |
|----------|------|-------------|
| `apiKey` | `string` | Your SyncAgent API key |
| `connectionString` | `string` | Database connection string |
| `externalUserId` | `string` | Unique identifier for the end-user (enables customer mode) |
| `context` | `Record<string, any>` | Optional context passed to both agents |
| `onData` | `(data: ToolData) => void` | Optional callback when the database agent returns query results |
| `onEscalated` | `() => void` | Optional callback when the support conversation is escalated to a human agent |
| `onResolved` | `(conversationId: string) => void` | Optional callback when the support conversation is resolved |
### Methods
| Method | Signature | Description |
|--------|-----------|-------------|
| `configure(config)` | `configure(config: DualChatServiceConfig): void` | Initialize the service with dual-mode config. Must be called before other methods. |
| `sendDbMessage(content)` | `sendDbMessage(content: string): Promise<void>` | Send a message to the database agent and receive a streamed response. |
| `sendSupportMessage(content, metadata?)` | `sendSupportMessage(content: string, metadata?: Record<string, any>): Promise<void>` | Send a message to the customer support agent. Requires `externalUserId` in config. |
| `stop()` | `stop(): void` | Abort any in-progress streams for both agents. |
| `reset()` | `reset(): void` | Clear all state (messages, errors, conversation) for both agents and start fresh. |
## API Reference

@@ -213,0 +727,0 @@