@sammy-labs/sammy-three
Version:
Sammy Agent Core: AI voice agent with screen capture, memory management, and live API integration
1,648 lines (1,606 loc) • 66 kB
TypeScript
import * as _google_genai from '@google/genai';
import { LiveConnectConfig, Part, Content, LiveServerToolCall, GoogleGenAIOptions, LiveClientToolResponse, LiveServerMessage, GoogleGenAI, Session, LiveServerToolCallCancellation, LiveServerContent, FunctionCall, FunctionResponse, FunctionDeclaration, Modality, StartSensitivity, EndSensitivity, ActivityHandling, TurnCoverage, HarmCategory, HarmBlockThreshold } from '@google/genai';
import { RefObject, ReactNode, ComponentType } from 'react';
import * as eventemitter3 from 'eventemitter3';
import eventemitter3__default, { EventEmitter } from 'eventemitter3';
/**
* Built-in Audio Stutter Analyzer
* Automatically collects events from audio and render systems to help debug stuttering
*/
interface AnalysisEvent {
type: 'audio' | 'render';
subtype: string;
message: string;
data: Record<string, any>;
timestamp: number;
}
declare class AudioStutterAnalyzer {
private static instance;
private events;
private isActive;
private debugMode;
private maxEvents;
private constructor();
static getInstance(): AudioStutterAnalyzer;
setDebugMode(enabled: boolean): void;
addEvent(type: 'audio' | 'render', subtype: string, message: string, data?: Record<string, any>): void;
getAnalysis(): void;
clear(): void;
setActive(active: boolean): void;
getEvents(): AnalysisEvent[];
getSummary(): {
totalEvents: number;
underruns: number;
longCaptures: number;
isActive: boolean;
debugMode: boolean;
};
}
declare const audioStutterAnalyzer: AudioStutterAnalyzer;
/**
* Memory Types
* Type definitions for memory-related operations and data structures.
* Used by memory services for search and retrieval operations.
* Aligned with Memory API v2.0 native Supabase implementation.
*/
declare enum MemorySourceType {
CKB = "CKB",
DIRECT_CSM_ADD = "DIRECT_CSM_ADD",
KNOWLEDGE_HUB = "KNOWLEDGE_HUB",
TRAINING_SESSION = "TRAINING_SESSION",
TRAINING_VIDEO = "TRAINING_VIDEO",
USER_SESSION = "USER_SESSION"
}
declare enum AgentType {
ADMIN = "ADMIN",
USER = "USER"
}
declare enum MemoryCategory {
AGENT_PERSONALITY = "AGENT_PERSONALITY",
BUG_REPORT = "BUG_REPORT",
DEPRECATED_INFO = "DEPRECATED_INFO",
FEATURE_MEMORY = "FEATURE_MEMORY",
FEATURE_REQUEST = "FEATURE_REQUEST",
FEEDBACK = "FEEDBACK",
GLOBAL_CONTEXTUAL_MEMORY = "GLOBAL_CONTEXTUAL_MEMORY",
ONBOARDING_ISSUE = "ONBOARDING_ISSUE",
PRODUCT_INFO = "PRODUCT_INFO",
UPSELL_OPPORTUNITY = "UPSELL_OPPORTUNITY",
USER_GOAL = "USER_GOAL",
USER_PREFERENCES = "USER_PREFERENCES"
}
interface MemoryEntry {
memory_id: string;
organisation_id: string;
agent_type: AgentType;
categories: MemoryCategory[];
created_at: string;
global_memory: boolean;
memory: string;
source: MemorySourceType;
confidence_score?: number;
conversation_id?: string;
sammy_three_organisation_feature_id?: string;
created_by?: string;
importance_score?: number;
similarity?: number;
updated_at?: string;
}
interface MemoryMetadata {
agent_type: AgentType;
global_memory: boolean;
source: MemorySourceType;
confidence_score?: number;
conversation_id?: string;
sammy_three_organisation_feature_id?: string;
categories?: MemoryCategory[];
importance_score?: number;
}
interface MemoryCreate {
content: string;
metadata: MemoryMetadata;
infer?: boolean;
}
interface SearchRequest {
query: string;
feature_id?: string;
categories?: MemoryCategory[];
limit?: number;
search_global_only?: boolean | null;
similarity_threshold?: number;
}
interface SearchResponse {
memories: MemoryEntry[];
}
interface MemoryUpdate {
content?: string;
}
interface MessageSender {
send(parts: Array<{
text: string;
}>, turnComplete: boolean): void;
}
interface MemorySearchConfig {
agentContextPrefix?: string;
agentOutputThreshold?: number;
searchGlobalOnly?: boolean;
searchLimit?: number;
similarityThreshold?: number;
useFeatureIdFilter?: boolean;
userContextPrefix?: string;
userContextSuffix?: string;
userInputThreshold?: number;
}
interface FlushData {
conversationId: string;
endTime: number;
pcmData: ArrayBuffer;
sampleRate: number;
speaker: 'agent' | 'user';
startTime: number;
totalBytesProcessed: number;
turnNumber: number;
timestamps?: {
end: number;
start: number;
}[];
}
/**
* Observability Types
* Comprehensive type definitions for tracking all data flowing into and out of the Gemini Live API
*/
/**
* Configuration for observability features
*/
interface ObservabilityConfig {
/**
* Whether to enable observability tracking
*/
enabled: boolean;
/**
* Audio aggregation configuration
*/
audioAggregation?: {
/**
* How often to flush audio buffers (ms)
* Default: 10000 (10 seconds)
*/
flushIntervalMs?: number;
/**
* Callback when audio buffer is flushed
* Now optional - can be handled by worker instead
*/
onFlush?: (data: FlushData) => Promise<void>;
};
/**
* Worker-specific configuration
*/
workerConfig?: {
/**
* How often to send batched events (ms)
* Default: 5000 (5 seconds)
*/
batchIntervalMs?: number;
/**
* Number of events to batch before sending
* Default: 50
*/
batchSize?: number;
};
/**
* Custom callback for each event
* Now optional - events can be sent to worker instead
*/
callback?: ObservabilityCallback;
/**
* Event types to disable/filter out
*/
disableEventTypes?: TraceEventType[];
/**
* Include raw audio data in events (can be large)
*/
includeAudioData?: boolean;
/**
* Include image data in events (can be large)
*/
includeImageData?: boolean;
/**
* Include system prompts in events (can be sensitive)
*/
includeSystemPrompt?: boolean;
/**
* Log events to console for debugging
*/
logToConsole?: boolean;
/**
* Additional metadata to include in all events
*/
metadata?: Record<string, any>;
/**
* Use web worker for observability (recommended for high-frequency events)
*/
useWorker?: boolean;
}
/**
* Callback function for handling observability events
*/
type ObservabilityCallback = (event: TraceEvent) => Promise<void> | void;
/**
* Types of events that can be traced
*/
type TraceEventType = 'agent.cleanup' | 'agent.mute' | 'agent.streaming_start' | 'agent.streaming_stop' | 'agent.unmute' | 'audio.gate_state_change' | 'audio.receive' | 'audio.recording_error' | 'audio.recording_start' | 'audio.recording_stop' | 'audio.send' | 'audio.volume_change' | 'config.set' | 'connection.close' | 'connection.error' | 'connection.open' | 'content.receive' | 'content.send' | 'context.update' | 'conversation.ready' | 'error' | 'memory.inject' | 'memory.search' | 'screen_capture.critical' | 'screen_capture.error' | 'screen_capture.send' | 'screen_capture.start' | 'screen_capture.stop' | 'session.end' | 'session.start' | 'system_prompt.set' | 'token.expired' | 'token.generated' | 'tool.call' | 'tool.register' | 'tool.response' | 'transcription.input' | 'transcription.output' | 'turn.complete' | 'url.change';
/**
* Base structure for all trace events
*/
interface BaseTraceEvent {
/** Unique event ID */
id: string;
/** Conversation ID this event belongs to */
conversationId: string;
/**
* Sequence number for guaranteed ordering within a session
* Increments with each event, starts at 0 for each session
* Use this for reliable ordering instead of timestamp comparison
*/
sequenceNumber: number;
/** Session ID this event belongs to */
sessionId: string;
/**
* High-resolution timestamp when event occurred
* Uses performance.now() + sequence number for microsecond precision
* to ensure proper ordering of rapid events like transcriptions
*/
timestamp: Date;
/** Event type */
type: TraceEventType;
/** Duration in milliseconds (for timed events) */
durationMs?: number;
/** Additional metadata */
metadata?: Record<string, any>;
}
/**
* Session lifecycle events
*/
interface SessionStartTraceEvent extends BaseTraceEvent {
data: {
isInternal: boolean;
agentMode: 'admin' | 'user';
model: string;
flowId?: string;
};
type: 'session.start';
}
interface SessionEndTraceEvent extends BaseTraceEvent {
data: {
messageCount: number;
reason: 'error' | 'timeout' | 'token_expired' | 'user_initiated';
toolCallCount: number;
totalDuration: number;
};
type: 'session.end';
}
/**
* Configuration events
*/
interface ConfigTraceEvent extends BaseTraceEvent {
data: {
config: LiveConnectConfig;
model: string;
};
type: 'config.set';
}
interface SystemPromptTraceEvent extends BaseTraceEvent {
data: {
characterCount: number;
prompt: string;
source: 'custom' | 'default' | 'service';
};
type: 'system_prompt.set';
}
/**
* Memory events
*/
interface MemorySearchTraceEvent extends BaseTraceEvent {
data: {
limit: number;
query: string;
resultsFound: number;
source: 'agent' | 'user';
threshold: number;
};
type: 'memory.search';
}
interface MemoryInjectTraceEvent extends BaseTraceEvent {
data: {
contextType: 'live' | 'validation';
memories: Array<{
id: string;
memory: string;
score?: number;
}>;
source: 'agent' | 'user';
};
type: 'memory.inject';
}
/**
* Content events
*/
interface ContentSendTraceEvent extends BaseTraceEvent {
data: {
messageType: 'mixed' | 'system' | 'text';
parts: Part[];
turnComplete: boolean;
characterCount?: number;
};
type: 'content.send';
}
interface ContentReceiveTraceEvent extends BaseTraceEvent {
data: {
hasText: boolean;
content: Content;
hasAudio: boolean;
characterCount?: number;
};
type: 'content.receive';
}
/**
* Audio events
*/
interface AudioSendTraceEvent extends BaseTraceEvent {
data: {
mimeType: string;
sizeBytes: number;
turnNumber?: number;
audioData?: string;
durationMs?: number;
};
type: 'audio.send';
}
interface AudioReceiveTraceEvent extends BaseTraceEvent {
data: {
mimeType: string;
sizeBytes: number;
turnNumber?: number;
audioData?: string;
};
type: 'audio.receive';
}
/**
* Screen capture events
*/
interface ScreenCaptureTraceEvent extends BaseTraceEvent {
data: {
width: number;
height: number;
mimeType: string;
sizeBytes: number;
method: 'render' | 'screen' | 'video';
imageData?: string;
};
type: 'screen_capture.send';
}
/**
* Tool events
*/
interface ToolRegistrationTraceEvent extends BaseTraceEvent {
data: {
toolName: string;
parameters?: any;
description?: string;
};
type: 'tool.register';
}
interface ToolCallTraceEvent extends BaseTraceEvent {
data: {
parameters: any;
toolCall: LiveServerToolCall;
toolName: string;
};
type: 'tool.call';
}
interface ToolResponseTraceEvent extends BaseTraceEvent {
data: {
success: boolean;
functionCallId: string;
response: any;
toolName: string;
error?: string;
};
type: 'tool.response';
}
/**
* Transcription events
*/
interface TranscriptionTraceEvent extends BaseTraceEvent {
data: {
finished: boolean;
text: string;
characterCount: number;
};
type: 'transcription.input' | 'transcription.output';
}
/**
* Other events
*/
interface UrlChangeTraceEvent extends BaseTraceEvent {
data: {
from: string;
to: string;
};
type: 'url.change';
}
interface ContextUpdateTraceEvent extends BaseTraceEvent {
data: {
details: any;
updateType: string;
};
type: 'context.update';
}
interface TurnCompleteTraceEvent extends BaseTraceEvent {
data: {
agentMessage: string;
turnNumber: number;
userMessage: string;
toolsUsed: string[];
};
type: 'turn.complete';
}
interface ErrorTraceEvent extends BaseTraceEvent {
data: {
severity: 'error' | 'fatal' | 'warning';
context: string;
error: string;
stack?: string;
};
type: 'error';
}
interface AgentMuteTraceEvent extends BaseTraceEvent {
data: {
muted: boolean;
};
type: 'agent.mute';
}
interface AgentUnmuteTraceEvent extends BaseTraceEvent {
data: {
muted: boolean;
};
type: 'agent.unmute';
}
interface AgentStreamingStartTraceEvent extends BaseTraceEvent {
data: {
streamType: 'audio' | 'both' | 'screen';
};
type: 'agent.streaming_start';
}
interface AgentStreamingStopTraceEvent extends BaseTraceEvent {
data: {
streamType: 'audio' | 'both' | 'screen';
reason?: string;
};
type: 'agent.streaming_stop';
}
interface AgentCleanupTraceEvent extends BaseTraceEvent {
data: {
reason: 'error' | 'timeout' | 'token_expired' | 'user_initiated';
};
type: 'agent.cleanup';
}
interface AudioRecordingStartTraceEvent extends BaseTraceEvent {
data: {
sampleRate: number;
noiseGateEnabled: boolean;
noiseSuppressionEnabled: boolean;
};
type: 'audio.recording_start';
}
interface AudioRecordingStopTraceEvent extends BaseTraceEvent {
data: {
duration?: number;
reason?: string;
};
type: 'audio.recording_stop';
}
interface AudioRecordingErrorTraceEvent extends BaseTraceEvent {
data: {
error: string;
errorType: 'device_error' | 'permission_denied' | 'processing_error' | 'unknown';
};
type: 'audio.recording_error';
}
interface AudioGateStateChangeTraceEvent extends BaseTraceEvent {
data: {
state: 'closed' | 'open';
threshold: number;
volume: number;
};
type: 'audio.gate_state_change';
}
interface AudioVolumeChangeTraceEvent extends BaseTraceEvent {
data: {
source: 'agent' | 'user';
volume: number;
};
type: 'audio.volume_change';
}
interface ConnectionOpenTraceEvent extends BaseTraceEvent {
data: {
status: string;
model: string;
};
type: 'connection.open';
}
interface ConnectionCloseTraceEvent extends BaseTraceEvent {
data: {
wasClean: boolean;
code: number;
reason: string;
};
type: 'connection.close';
}
interface ConnectionErrorTraceEvent extends BaseTraceEvent {
data: {
message: string;
error: string;
};
type: 'connection.error';
}
interface TokenGeneratedTraceEvent extends BaseTraceEvent {
data: {
model: string;
expiresAt?: string;
};
type: 'token.generated';
}
interface TokenExpiredTraceEvent extends BaseTraceEvent {
data: {
expiredAt: string;
};
type: 'token.expired';
}
interface ScreenCaptureStartTraceEvent extends BaseTraceEvent {
data: {
method: 'screen' | 'tab' | 'window';
};
type: 'screen_capture.start';
}
interface ScreenCaptureStopTraceEvent extends BaseTraceEvent {
data: {
duration?: number;
reason?: string;
};
type: 'screen_capture.stop';
}
interface ScreenCaptureErrorTraceEvent extends BaseTraceEvent {
data: {
error: string;
errorType: 'not_supported' | 'permission_denied' | 'unknown';
};
type: 'screen_capture.error';
}
interface ScreenCaptureCriticalTraceEvent extends BaseTraceEvent {
data: {
timeSinceLastCapture: number;
audioActive: boolean;
bypassedThrottling: boolean;
captureMethod: 'dom' | 'placeholder';
reason: 'agent-started-speaking' | 'agent-stopped-speaking' | 'interrupted' | 'turn-complete' | 'user-started-speaking' | 'user-stopped-speaking';
width?: number;
height?: number;
sizeBytes?: number;
};
type: 'screen_capture.critical';
}
interface ConversationReadyTraceEvent extends BaseTraceEvent {
data: {
success: boolean;
conversationId: string;
message?: string;
};
type: 'conversation.ready';
}
/**
* Union type of all trace events
*/
type TraceEvent = AgentCleanupTraceEvent | AgentMuteTraceEvent | AgentStreamingStartTraceEvent | AgentStreamingStopTraceEvent | AgentUnmuteTraceEvent | AudioGateStateChangeTraceEvent | AudioReceiveTraceEvent | AudioRecordingErrorTraceEvent | AudioRecordingStartTraceEvent | AudioRecordingStopTraceEvent | AudioSendTraceEvent | AudioVolumeChangeTraceEvent | ConfigTraceEvent | ConnectionCloseTraceEvent | ConnectionErrorTraceEvent | ConnectionOpenTraceEvent | ContentReceiveTraceEvent | ContentSendTraceEvent | ContextUpdateTraceEvent | ConversationReadyTraceEvent | ErrorTraceEvent | MemoryInjectTraceEvent | MemorySearchTraceEvent | ScreenCaptureCriticalTraceEvent | ScreenCaptureErrorTraceEvent | ScreenCaptureStartTraceEvent | ScreenCaptureStopTraceEvent | ScreenCaptureTraceEvent | SessionEndTraceEvent | SessionStartTraceEvent | SystemPromptTraceEvent | TokenExpiredTraceEvent | TokenGeneratedTraceEvent | ToolCallTraceEvent | ToolRegistrationTraceEvent | ToolResponseTraceEvent | TranscriptionTraceEvent | TurnCompleteTraceEvent | UrlChangeTraceEvent;
/**
* Complete session trace containing all events
*/
interface SessionTrace {
summary: {
errorCount: number;
eventCounts: Record<TraceEventType, number>;
messageCount: number;
toolCallCount: number;
totalEvents: number;
totalDuration?: number;
};
conversationId: string;
events: TraceEvent[];
sessionId: string;
startTime: Date;
endTime?: Date;
}
/**
* Statistics for the current session
*/
interface SessionStatistics {
audioBytesReceived: number;
audioBytesSent: number;
criticalRenders: number;
duration: number;
errors: number;
eventCount: number;
imageBytesSent: number;
memoriesInjected: number;
memoriesSearched: number;
messagesReceived: number;
messagesSent: number;
sessionId: string;
startTime: Date;
toolCalls: number;
}
/**
* Observability Manager
* Central class for tracking all events during an agent session
*/
interface ObservabilityEventMap {
statistics: (stats: SessionStatistics) => void;
trace: (event: TraceEvent) => void;
}
declare class ObservabilityManager {
private config;
private events;
private sessionTrace;
private sessionStats;
private sessionId;
private conversationId;
private startTime;
private sequenceNumber;
private baseTime;
constructor(sessionId: string, conversationId: string, config: ObservabilityConfig);
/**
* Get the event emitter for subscribing to events
*/
getEventEmitter(): EventEmitter<ObservabilityEventMap>;
/**
* Generate a high-resolution timestamp to ensure event ordering
*/
private generateHighResTimestamp;
/**
* Track an event
*/
trackEvent(event: Omit<TraceEvent, 'conversationId' | 'id' | 'sequenceNumber' | 'sessionId' | 'timestamp'>): Promise<void>;
/**
* Apply privacy settings to redact sensitive data
*/
private applyPrivacySettings;
/**
* Update statistics based on event
*/
private updateStatistics;
/**
* End the session and return final trace
*/
endSession(reason?: 'error' | 'timeout' | 'token_expired' | 'user_initiated'): SessionTrace;
/**
* Get current trace
*/
getTrace(): SessionTrace;
/**
* Get current statistics
*/
getStatistics(): SessionStatistics;
/**
* Serialize events for export
*/
serializeEvents(events: TraceEvent[]): string;
/**
* Export trace as JSON
*/
exportAsJson(): string;
/**
* Generate a summary report
*/
generateSummaryReport(): string;
}
/**
* Centralized Context State Manager
*
* This class manages all contextual state for the agent including:
* - User memories from memory searches
* - Page metadata (URL, title, timestamp)
* - User preferences and settings
* - Custom contextual data
*
* It provides a single source of truth for context and handles
* formatting for LLM consumption.
*/
interface PageMetadata {
timestamp: string;
title: string;
url: string;
domain?: string;
path?: string;
}
interface UserPreferences {
name?: string;
preferences?: Record<string, any>;
settings?: Record<string, any>;
}
interface ContextState {
lastUpdated: {
pageMetadata?: Date;
userPreferences?: Date;
memories?: Date;
};
customContext: Record<string, any>;
memories: MemoryEntry[];
pageMetadata: PageMetadata | null;
userPreferences: UserPreferences | null;
}
interface ContextFormattingOptions {
format?: 'compact' | 'detailed';
includeCustomContext?: boolean;
includeMemories?: boolean;
includePageMetadata?: boolean;
includeUserPreferences?: boolean;
memoryLimit?: number;
}
declare class ContextStateManager {
private state;
private observabilityManager?;
constructor(observabilityManager?: ObservabilityManager);
updateMemories(memories: MemoryEntry[]): void;
getMemories(): MemoryEntry[];
clearMemories(): void;
updatePageMetadata(metadata: Partial<PageMetadata>): void;
getPageMetadata(): PageMetadata | null;
updateUserPreferences(preferences: Partial<UserPreferences>): void;
getUserPreferences(): UserPreferences | null;
setCustomContext(key: string, value: any): void;
getCustomContext(key: string): any;
removeCustomContext(key: string): void;
formatContext(options?: ContextFormattingOptions): string;
formatForToolResponse(): string;
formatPageContextOnly(options?: {
includeTimestamp?: boolean;
additionalInfo?: Record<string, any>;
customContext?: string;
}): string;
private formatPageMetadata;
private formatUserPreferences;
private formatMemories;
private formatCustomContext;
getFullState(): ContextState;
clearAll(): void;
hasContext(): boolean;
getLastUpdated(type?: 'memories' | 'pageMetadata' | 'userPreferences'): Date | null;
}
/**
* Sammy API Caller
* A generic HTTP client configured for the Sammy API with built-in authentication,
* error handling, and retry logic. This client is completely route-agnostic and
* provides standard HTTP methods (GET, POST, PUT, PATCH, DELETE).
*/
interface ApiCallOptions {
path: string;
skipValidation?: boolean;
body?: unknown;
headers?: HeadersInit;
retries?: number;
timeout?: number;
}
declare class SammyApiClient {
private token;
private baseUrl;
private onTokenExpired?;
private isTokenValid;
constructor(authConfig: AuthConfig);
/**
* Updates the authentication configuration
*/
updateAuthConfig(authConfig: Partial<AuthConfig>): void;
/**
* Validates the JWT token
*/
private validateToken;
/**
* Generic HTTP request method with retry logic
*/
private makeRequest;
/**
* Recursive helper method for request attempts with retry logic
*/
private attemptRequest;
/**
* Helper method to extract error details from response
*/
private getErrorDetails;
/**
* Utility method for delays
*/
private delay;
/**
* GET request
*/
get<T = unknown>(path: string, options?: Omit<ApiCallOptions, 'body' | 'path'>): Promise<T>;
/**
* POST request
*/
post<T = unknown>(path: string, body?: unknown, options?: Omit<ApiCallOptions, 'body' | 'path'>): Promise<T>;
/**
* PUT request
*/
put<T = unknown>(path: string, body?: unknown, options?: Omit<ApiCallOptions, 'body' | 'path'>): Promise<T>;
/**
* PATCH request
*/
patch<T = unknown>(path: string, body?: unknown, options?: Omit<ApiCallOptions, 'body' | 'path'>): Promise<T>;
/**
* DELETE request
*/
delete<T = unknown>(path: string, options?: Omit<ApiCallOptions, 'body' | 'path'>): Promise<T>;
/**
* Get current token validity status
*/
getTokenValidityStatus(): boolean;
}
/**
* Conversation Service
* Business logic service for conversation lifecycle operations using SammyApiClient.
* Handles conversation status updates and lifecycle management.
*
* API endpoints:
* - markReady: PATCH /{conversation_id}/mark-ready - Updates conversation status to trigger memory processing
*/
interface ConversationService {
markConversationReadyToProcess: (conversationId: string) => Promise<MarkConversationReadyResponse>;
sendMessages: (payload: MessagePayload) => Promise<void>;
escalateConversation: (request: EscalateConversationRequest) => Promise<EscalateConversationResponse>;
}
interface MarkConversationReadyResponse {
success: boolean;
conversationId?: string;
message?: string;
}
interface MessagePayload {
messages: Array<{
sender: 'agent' | 'user';
text: string;
timestamp: string;
}>;
agentMode: AgentMode;
conversationId: string;
sammyThreeOrganisationFeatureId?: string;
guideId?: string;
}
interface EscalateConversationRequest {
conversation_id: string;
}
interface EscalateConversationResponse {
success: boolean;
message: string;
conversation_id: string;
}
/**
* Service interfaces for Sammy Agent
* These define the contracts for external dependencies
*/
interface EphemeralTokenConstraints {
config: {
response_modalities?: string[];
session_resumption?: Record<string, any>;
};
model: string;
}
interface EphemeralTokenResponse$1 {
token: string;
ephemeral_token?: string;
expiresAt?: string;
}
/**
* System prompt query
* This is a loose type to allow users to provide any function structure they want
*/
type SystemPromptQuery = Record<string, any>;
/**
* Schema for instantiating a session request
*/
interface InstantiateSessionRequest {
/** The mode of the agent */
agentMode: AgentMode;
/** The ID of the conversation to instantiate */
conversationId: string;
/** Optional external user ID for the conversation */
externalUserId?: string;
/** Optional feature ID for filtering prompts */
sammyThreeOrganisationFeatureId?: string;
}
/**
* Schema for instantiating a session response
*/
interface InstantiateSessionResponse {
/** The ID of the conversation */
conversationId: string;
}
/**
* Error types that services might throw
*/
declare class ServiceError extends Error {
code: string;
status?: number | undefined;
details?: any | undefined;
constructor(message: string, code: string, status?: number | undefined, details?: any | undefined);
}
declare class TokenExpiredError extends ServiceError {
constructor(message?: string, details?: any);
}
/**
* Core Services
* Business logic services that use the SammyApiCaller for specific API operations.
*
* Services included:
* - generateEphemeralToken: Generates ephemeral tokens for live API connections
* - getSystemPrompt: Retrieves system prompts based on agent mode and context
*/
interface CoreService {
generateEphemeralToken: (constraints: EphemeralTokenConstraints) => Promise<EphemeralTokenResponse>;
getSystemPrompt: (query: SystemPromptQuery) => Promise<{
systemPrompt: string;
}>;
instantiateSession: (instantiateSessionRequest: InstantiateSessionRequest) => Promise<InstantiateSessionResponse>;
}
/**
* Creates the ephemeral token service
*/
interface EphemeralTokenResponse {
token: string;
ephemeral_token?: string;
expiresAt?: string;
}
/**
* Memory Service
* Business logic service for memory search and retrieval operations using SammyApiClient.
* Provides memory-specific functionality for search and getAllMemories operations.
* Configured to work with the native Supabase backend API v2.0.
*/
interface MemoryService {
getAll: (sammyThreeOrganisationFeatureId: string | null, retryOptions?: {
retryOnEmpty?: boolean;
}) => Promise<MemoryEntry[]>;
search: (query: string, options: Omit<SearchRequest, 'query'>) => Promise<MemoryEntry[]>;
}
/**
* Creates all memory services using the provided API caller
*/
declare const createMemoryServices: (apiCaller: SammyApiClient) => MemoryService;
/**
* Guides Service
* Business logic service for walkthrough guide operations using SammyApiClient.
* Handles guide validation and retrieval for walkthrough functionality.
*
* API endpoints:
* - fetchGuide: GET /guides/{id} - Fetches and validates a single guide by ID
* - getUserGuides: GET /guides - Lists user's guides with completion status
*/
interface GuidesService {
fetchGuide: (guideId: string) => Promise<SammyGuide | null>;
getUserGuides: () => Promise<SammyGuide[]>;
}
interface SammyGuide {
guideId: string;
isCompleted: boolean;
organisationId: string;
prompt: string;
title: string;
}
interface StartOptions {
agentMode: AgentMode;
guideId?: string;
sammyThreeOrganisationFeatureId?: string;
}
declare class SammyAgentCore<TEventMap extends Record<string, (...args: any[]) => any> = BaseToolEventMap> {
private config;
private callbacks;
private tools;
private updateAgentState;
private sammyApiClient;
private client;
private coreServices;
private memoryServices;
private conversationServices;
guidesServices: GuidesService;
private audioRecorder;
private audioStreamer;
private screenCapture;
private toolManager;
private contextStateManager;
private contextMemoryManager;
private session;
private isConnected;
private currentToken;
muted: boolean;
isStreaming: boolean;
agentVolume: number;
private outputTranscriptBuffer;
private inputTranscriptBuffer;
constructor(options: {
callbacks: SammyAgentCallbacks;
updateAgentState: UpdateAgentState;
config: SammyAgentConfig;
tools?: ToolDefinition<TEventMap>[];
});
/**
* Update observability manager in context management classes
* Called after the observable client is created
*/
private updateObservabilityInContextManagers;
/**
* Update page metadata in context state
*/
updatePageContext(metadata: {
path?: string;
title?: string;
url?: string;
domain?: string;
}): void;
/**
* Update user preferences in context state
*/
updateUserContext(preferences: {
name?: string;
preferences?: Record<string, unknown>;
settings?: Record<string, unknown>;
}): void;
/**
* Get the context state manager for direct access
*/
getContextStateManager(): ContextStateManager;
/**
* Register the context tool with the tool manager
*/
private registerContextTool;
start(options: StartOptions): Promise<AgentSession>;
destroy(): Promise<void>;
sendMessage(message: string): void;
setMuted(muted: boolean): void;
getToolEventEmitter(): TypedEventEmitter<TEventMap>;
/**
* Get current observability trace
*/
getObservabilityTrace(): SessionTrace | null;
/**
* Export observability trace as JSON
*/
exportObservabilityTrace(): string | null;
/**
* Generate observability summary report
*/
generateObservabilitySummary(): string | null;
/**
* Get current session statistics
*/
getSessionStatistics(): SessionStatistics | null;
/**
* Get observability event emitter
*/
getObservabilityEvents(): eventemitter3.EventEmitter<any, any> | null;
private setupAudioStreaming;
private setupEventHandlers;
private handleOpen;
private handleClose;
private handleError;
private handleAudio;
private handleInterrupted;
private handleOutputTranscription;
private handleInputTranscription;
private handleTurnComplete;
private handleToolCall;
private startAudioRecording;
private handleAudioData;
private sendPendingTranscriptions;
private arrayBufferToBase64;
}
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
type ClientContentLog = {
turns: Part[];
turnComplete: boolean;
};
/**
* the options to initiate the client, ensure apiKey is required
*/
type LiveClientOptions = GoogleGenAIOptions & {
apiKey: string;
};
/** log types */
type StreamingLog = {
message: ClientContentLog | LiveClientToolResponse | Omit<LiveServerMessage, 'data' | 'text'> | string;
date: Date;
type: string;
area?: string;
latencyMs?: number;
count?: number;
};
/**
* Event types that can be emitted by the MultimodalLiveClient.
* Each event corresponds to a specific message from GenAI or client state change.
*/
interface LiveClientEventTypes {
inputtranscription: (transcription: {
finished: boolean;
text: string;
}) => void;
outputtranscription: (transcription: {
finished: boolean;
text: string;
}) => void;
toolcallcancellation: (toolcallCancellation: LiveServerToolCallCancellation) => void;
audio: (data: ArrayBuffer) => void;
close: (event: CloseEvent) => void;
content: (data: LiveServerContent) => void;
error: (error: ErrorEvent) => void;
interrupted: () => void;
log: (log: StreamingLog) => void;
open: () => void;
setupcomplete: () => void;
toolcall: (toolCall: LiveServerToolCall) => void;
turncomplete: () => void;
}
/**
* A event-emitting class that manages the connection to the websocket and emits
* events to the rest of the application.
* If you dont want to use react you can still use this.
*/
declare class GenAILiveClient extends EventEmitter<LiveClientEventTypes> {
private _model;
private _session;
private _status;
protected client: GoogleGenAI;
protected config: LiveConnectConfig | null;
constructor(options: LiveClientOptions);
protected log(type: string, message: StreamingLog['message'], metadata?: {
area?: string;
latencyMs?: number;
}): void;
protected onclose(e: CloseEvent): void;
protected onerror(e: ErrorEvent): void;
protected onmessage(message: LiveServerMessage): Promise<void>;
protected onopen(): void;
connect(model: string, config: LiveConnectConfig): Promise<boolean>;
disconnect(): boolean;
getConfig(): {
httpOptions?: _google_genai.HttpOptions;
abortSignal?: AbortSignal;
generationConfig?: _google_genai.GenerationConfig;
responseModalities?: _google_genai.Modality[];
temperature?: number;
topP?: number;
topK?: number;
maxOutputTokens?: number;
mediaResolution?: _google_genai.MediaResolution;
seed?: number;
speechConfig?: _google_genai.SpeechConfig;
enableAffectiveDialog?: boolean;
systemInstruction?: _google_genai.ContentUnion;
tools?: _google_genai.ToolListUnion;
sessionResumption?: _google_genai.SessionResumptionConfig;
inputAudioTranscription?: _google_genai.AudioTranscriptionConfig;
outputAudioTranscription?: _google_genai.AudioTranscriptionConfig;
realtimeInputConfig?: _google_genai.RealtimeInputConfig;
contextWindowCompression?: _google_genai.ContextWindowCompressionConfig;
proactivity?: _google_genai.ProactivityConfig;
};
/**
* send normal content parts such as { text }
*/
send(parts: Part | Part[], turnComplete?: boolean): void;
/**
* send realtimeInput, this is base64 chunks of "audio/pcm" and/or "image/jpg"
*/
sendRealtimeInput(chunks: Array<{
data: string;
mimeType: string;
}>): void;
/**
* send a response to a function call and provide the id of the functions you are responding to
*/
sendToolResponse(toolResponse: LiveClientToolResponse): void;
get model(): string | null;
get session(): Session | null;
get status(): "connected" | "connecting" | "disconnected";
}
type CaptureMethod = 'render' | 'video';
interface ScreenCaptureConfig {
/**
* Method to capture screen content
* - 'screen': Uses getDisplayMedia() to capture screen/window
* - 'render': Uses modern-screenshot to capture DOM elements
*/
method: CaptureMethod;
/**
* Maximum dimensions for captured images
*/
maxWidth?: number;
/**
* DOM change detection settings (only for 'render' method)
*/
domChangeDetection?: {
/**
* Debounce time after DOM mutations stop before capturing (ms)
*/
debounceMs?: number;
/**
* MutationObserver configuration
*/
observerConfig?: MutationObserverInit;
};
/**
* Automatically download captured images to local storage
* Set to false to prevent automatic downloads (useful for S3 integration later)
*/
autoDownload?: boolean;
/**
* How often to check for frames (ms)
* For 'render' method, this is the fallback interval when no DOM changes detected
*/
checkInterval?: number;
debugAudioPerformance?: boolean;
/**
* Enable debug logging
*/
debugLogs?: boolean;
/**
* Enable audio-aware adaptive capture settings
* When true, reduces capture frequency and quality during audio playback
* to prevent audio stuttering. Default: true
*/
enableAudioAdaptation?: boolean;
/**
* JPEG quality (0.0 to 1.0)
*/
jpegQuality?: number;
maxHeight?: number;
/**
* Maximum interval between frames (ms)
*/
maxInterval?: number;
/**
* Minimum interval between frames (ms)
*/
minInterval?: number;
/**
* Scope of capture. Either entire document or context wrapped content
*/
scope?: 'context' | 'document';
/**
* Enable smart deduplication using image hashing
*/
useHashing?: boolean;
/**
* Number of worker threads to use (only used if workerUrl is provided)
* Default: 2
*/
workerNumber?: number;
/**
* Worker URL for modern-screenshot (only for 'render' method)
* When provided, captures will be processed in web workers for better performance
* Example for Vite: import workerUrl from 'modern-screenshot/worker?url'
*/
workerUrl?: string;
}
interface ScreenCaptureRefs {
videoRef: RefObject<HTMLVideoElement | null>;
canvasRef: RefObject<HTMLCanvasElement | null>;
contextElementRef: RefObject<HTMLDivElement | null>;
}
interface ScreenCapture extends ScreenCaptureRefs {
setVideoStream: (stream: MediaStream | null) => void;
videoStream: MediaStream | null;
isStreaming: boolean;
startStreaming: (client: GenAILiveClient) => Promise<MediaStream | void>;
stopStreaming: () => void;
stream: MediaStream | null;
}
interface ScreenCaptureCallbacks {
startStreaming: (client: GenAILiveClient) => Promise<MediaStream | void>;
stopStreaming: () => void;
}
/**
* Core types for the Sammy Agent
*/
interface AgentState {
activeSession: AgentSession | null;
agentVolume: number;
error: Error | null;
isConnected: boolean;
isConnecting: boolean;
isStreaming: boolean;
userVolume: number;
}
interface UpdateAgentState {
(variable: keyof AgentState, value: AgentState[keyof AgentState]): void;
}
declare enum AgentMode {
ADMIN = "ADMIN",
SAMMY = "SAMMY",
USER = "USER"
}
interface AgentSession {
agentMode: AgentMode;
conversationId: string;
sessionId: string;
startedAt: Date;
sammyThreeOrganisationFeatureId?: string;
guideId?: string;
}
interface AuthConfig {
token: string;
baseUrl?: string;
onTokenExpired?: () => void;
}
interface SammyAgentCallbacks {
onConnectionStateChange?: (connected: boolean) => void;
onError?: (error: unknown) => void;
onMemoryUpdate?: (result: unknown) => void;
onObservabilityEvent?: (event: TraceEvent) => void;
onSessionTraceComplete?: (trace: SessionTrace) => void;
onTokenExpired?: () => void;
onToolCall?: (tool: LiveServerToolCall) => void;
onTurnComplete?: (summary: {
agent: string;
user: string;
}) => void;
}
interface SammyAgentConfigProps {
auth: AuthConfig;
captureConfig?: {
frameRate?: number;
quality?: number;
};
captureMethod?: CaptureMethod;
debugAudioPerformance?: boolean;
debugLogs?: boolean;
defaultVoice?: string;
model?: string;
observability?: ObservabilityConfig;
}
interface SammyAgentConfig extends SammyAgentConfigProps {
screenCaptureCallbacks: ScreenCaptureCallbacks;
}
declare enum ToolName {
END_SESSION = "endSession",
GET_CONTEXT = "getContext",
ESCALATE = "escalate"
}
interface BaseToolEventMap extends Record<string, (...args: any[]) => any> {
[ToolName.END_SESSION]: () => void;
[ToolName.ESCALATE]: (data: {
reason: string;
context?: string;
apiResponse?: any;
error?: string;
}) => void;
}
interface ToolEventMap<_TCustomEvents = {}> extends BaseToolEventMap {
}
type ExtendedToolEventMap<TCustomEvents = {}> = BaseToolEventMap & TCustomEvents;
interface TypedEventEmitter<TEventMap extends Record<string, (...args: unknown[]) => unknown>> {
emit<K extends keyof TEventMap>(event: K, ...args: Parameters<TEventMap[K]>): boolean;
off<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): this;
on<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): this;
}
type TypedFunctionDeclaration = Omit<FunctionDeclaration, 'name'> & {
name: ToolName;
};
type ToolCategory = 'action' | 'context';
declare const ToolCategories: {
readonly ACTION: "action";
readonly CONTEXT: "context";
};
interface ToolServices {
conversationServices: ConversationService;
coreServices: CoreService;
memoryServices: MemoryService;
}
interface ToolContext<TEventMap extends Record<string, (...args: unknown[]) => unknown> = BaseToolEventMap> {
emit: <K extends keyof TEventMap>(event: K, ...args: Parameters<TEventMap[K]>) => void;
getState: () => AgentSession;
services: ToolServices;
}
interface ToolDefinition<TEventMap extends Record<string, (...args: any[]) => any> = BaseToolEventMap> {
handler: (fc: FunctionCall, context: ToolContext<TEventMap>) => Promise<FunctionResponse>;
category: ToolCategory;
declaration: TypedFunctionDeclaration;
}
/**
* Configuration settings for the Sammy Agent
*
* This file contains the base configuration for the Gemini Live API connection,
* including audio settings, activity detection, safety settings, and tool declarations.
* The configuration is optimized for natural conversation flow with proper turn-taking
* and reduced interruptions.
*/
/**
* Base configuration for customer success agent sessions
* Optimized for natural conversation flow with proper turn-taking
*/
declare const getAgentConfig: (functionDeclarations: TypedFunctionDeclaration[], systemPrompt: string) => {
responseModalities: Modality[];
temperature: number;
topP: number;
topK: number;
maxOutputTokens: number;
seed: number;
speechConfig: {
voiceConfig: {
prebuiltVoiceConfig: {
voiceName: string;
};
};
languageCode: string;
};
realtimeInputConfig: {
automaticActivityDetection: {
disabled: boolean;
startOfSpeechSensitivity: StartSensitivity;
endOfSpeechSensitivity: EndSensitivity;
prefixPaddingMs: number;
silenceDurationMs: number;
};
activityHandling: ActivityHandling;
turnCoverage: TurnCoverage;
};
tools: {
functionDeclarations: TypedFunctionDeclaration[];
}[];
systemInstruction: {
parts: {
text: string;
}[];
};
inputAudioTranscription: boolean;
outputAudioTranscription: boolean;
safetySettings: {
category: HarmCategory;
threshold: HarmBlockThreshold;
}[];
};
/**
* Hook for updating context state based on page changes
*
* This hook returns a function that should be called from within
* the SammyAgentProvider where agentCoreRef is available.
*
* Usage:
* const contextUpdater = useContextUpdater();
* // Inside provider: contextUpdater(agentCoreRef.current);
*/
interface UseContextUpdaterOptions {
includeDomain?: boolean;
includePath?: boolean;
includeTitle?: boolean;
updateInterval?: number;
}
declare const useContextUpdater: (options?: UseContextUpdaterOptions) => {
startMonitoring: (agent: SammyAgentCore | null) => void;
stopMonitoring: () => void;
updateUserContext: (userData: {
name?: string;
preferences?: Record<string, any>;
settings?: Record<string, any>;
}) => void;
setCustomContext: (key: string, value: any) => void;
};
/**
* Hook to provide always-on guide data with automatic fetching
* Provides guide data and state management for walkthrough functionality
*/
interface UseGuidesProps {
authConfig?: AuthConfig;
walkthroughId?: string;
}
interface UseGuidesReturn {
currentGuide: SammyGuide | null;
guideError: Error | null;
isLoadingGuide: boolean;
isLoadingUserGuides: boolean;
markGuideAsCompleted: (guideId: string) => void;
refreshGuide: () => void;
refreshUserGuides: () => void;
userGuides: SammyGuide[];
userGuidesError: Error | null;
}
declare function useGuides({ authConfig, walkthroughId, }: UseGuidesProps): UseGuidesReturn;
/**
* Hook to manage guides functionality without requiring a separate context.
* Combines guide data management, URL parameter detection, and walkthrough starting.
*/
interface UseGuidesManagerOptions {
onStartAgent?: (options: {
guideId: string;
agentMode: AgentMode;
}) => Promise<boolean>;
authConfig?: AuthConfig;
autoStartFromURL?: boolean;
debug?: boolean;
enabled?: boolean;
onWalkthroughStart?: (guideId: string) => void;
queryParamName?: string;
}
interface GuidesState {
checkForGuideInUrl: () => Promise<boolean>;
currentGuide: SammyGuide | null;
guideError: Error | null;
isLoadingGuide: boolean;
isLoadingUserGuides: boolean;
markGuideAsCompleted: (guideId: string) => void;
refreshGuide: () => void;
refreshUserGuides: () => void;
userGuides: SammyGuide[];
userGuidesError: Error | null;
cleanupUrlParams: () => void;
detectedWalkthroughId: string | null;
startWalkthrough: (guideId: string) => Promise<boolean>;
}
declare function useGuidesManager({ authConfig, autoStartFromURL, onStartAgent, onWalkthroughStart, queryParamName, debug, enabled, }: UseGuidesManagerOptions): GuidesState | null;
/**
* Hook to handle guides-related query parameters
* Detects ?walkthrough=guide-id in URL and triggers guide start
*/
interface UseGuidesQueryParamsOptions {
onGuideDetected?: (guideId: string) => Promise<boolean>;
checkQueryOnMount?: boolean;
debug?: boolean;
queryParamName?: string;
}
interface UseGuidesQueryParamsReturn {
checkQueryParameters: () => Promise<boolean>;
cleanupQueryParams: () => void;
}
/**
* Hook to detect and handle guide walkthrough query parameters
*
* Example URLs:
* - https://app.com?walkthrough=guide-123
* - https://app.com/page?walkthrough=onboarding-guide
*/
declare function useGuidesQueryParams({ checkQueryOnMount, onGuideDetected, queryParamName, debug, }?: UseGuidesQueryParamsOptions): UseGuidesQueryParamsReturn;
interface UseRenderCaptureProps {
canvasRef: RefObject<HTMLCanvasElement | null>;
config: ScreenCaptureConfig;
contextElementRef: RefObject<HTMLDivElement | null>;
}
interface CriticalRenderEvent {
reason: 'agent-started-speaking' | 'agent-stopped-speaking' | 'interrupted' | 'turn-complete' | 'user-started-speaking' | 'user-stopped-speaking';
bypassAudioThrottling: true;
timestamp: number;
}
interface UseRenderCaptureReturn {
isCapturing: boolean;
startRenderCapture: (client: GenAILiveClient) => void;
stopRenderCapture: () => void;
triggerCriticalRender: (event: CriticalRenderEvent) => void;
}
declare function useRenderCapture({ contextElementRef, canvasRef, config, }: UseRenderCaptureProps): UseRenderCaptureReturn;
interface UseScreenCaptureProps {
config: ScreenCaptureConfig;
}
declare function useScreenCapture({ config, }: UseScreenCaptureProps): ScreenCapture;
/**
* useVideoCapture Hook
* Captures video frames from screen recording and sends them to the AI client
* Returns callbacks to start/