heylock
Version:
Zero‑friction AI agent integration for JavaScript & TypeScript projects.
665 lines (614 loc) • 22.8 kB
TypeScript
//#region Interfaces
/**
* Configuration options for a Heylock agent.
* All properties are optional.
* @property useStorage - Whether to use localStorage in the browser.
* @property suppressWarnings - Whether to suppress warnings in the console.
* @property useMessageHistory - Whether to use chat history when generating a response.
* @property agentId - Optional ID for multi-agent setups, defaults to 'default'
* @remarks Messages will still be saved to messageHistory unless you specify otherwise when using message(), messageStream(), or greet().
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY', {
* useStorage: true, // enable localStorage in the browser
* useMessageHistory: true, // use message history for better answers
* suppressWarnings: false // show helpful warnings in console
* });
*/
export interface AgentOptions {
useStorage?: boolean;
useMessageHistory?: boolean;
suppressWarnings?: boolean;
agentId?: string;
}
/**
* Represents a single message stored in the agent's message history.
* Includes the message content and the role of the sender ('user' or 'assistant').
*
* @example
* // Add and read messages
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.addMessage('Hello!', 'user');
* agent.addMessage('Hi there!', 'assistant');
* console.log(agent.messageHistory); // [{content:'Hello!', role:'user'}, {content:'Hi there!', role:'assistant'}]
*/
export interface Message {
content: string;
role: 'user' | 'assistant';
}
/**
* Represents a single entry in the agent's context.
* Includes the context content and a timestamp.
*
* @example
* // Add a context entry so the agent can personalize responses
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.addContextEntry('User expanded the model description');
* console.log(agent.getContextString()); // "User expanded the model description now."
*/
export interface ContextEntry {
content: string;
timestamp: number;
}
/**
* Represents the remaining usage limits for a Heylock agent.
* @property messages - Number of messages remaining, or null if unlimited/unknown.
* @property sorts - Number of sorts remaining, or null if unlimited/unknown.
* @property rewrites - Number of rewrites remaining, or null if unlimited/unknown.
*
* @example
* // Fetch and read usage remaining
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.onInitialized(async (success) => {
* if (!success) return;
*
* const usage = await agent.fetchUsageRemaining();
*
* console.log('Messages left:', usage.messages);
* console.log('Sorts left:', usage.sorts);
* console.log('Rewrites left:', usage.rewrites);
* });
*/
export interface UsageRemaining {
readonly messages: number | null;
readonly sorts: number | null;
readonly rewrites: number | null;
}
/**
* Represents the result of a shouldEngage check.
* @property shouldEngage - Whether the agent should engage (true/false).
* @property reasoning - Explanation for the decision.
* @property warning - Optional warning message (e.g., throttling).
* @property fallback - Whether a fallback was used due to throttling or error.
*
* @example
* // Decide if a chatbot should pop up now
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.onInitialized(async () => {
* // Note: calls are throttled (15s). Avoid calling repeatedly inside loops.
* const res = await agent.shouldEngage('Only engage if user seems stuck on checkout');
*
* if (res.warning) console.warn(res.warning); // e.g., throttling notice
*
* if (res.shouldEngage === true) {
* agent.addMessage(await agent.greet("Help the visitor with checkout."), 'assistant');
* }
* });
*/
export interface ShouldEngageResult {
shouldEngage: boolean;
reasoning: string;
warning?: string;
fallback: boolean;
}
/**
* Represents the result of an AI-powered sort.
* @property array - Sorted array of items.
* @property indexes - Indexes mapping original array to sorted order.
* @property reasoning - Optional explanation for the sort order.
* @property warning - Optional warning message.
* @property fallback - True if a fallback was used.
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.onInitialized(async () => {
* const products = [
* { name: 'Eco Bottle', price: 12 },
* { name: 'Steel Bottle', price: 25 },
* { name: 'Plastic Bottle', price: 3 }
* ];
*
* const result = await agent.sort(products, 'Order these products by which one the user is most likely to buy');
*
* console.log(result.array);
* });
*/
export interface SortResult {
array: any[];
indexes: number[];
reasoning?: string;
warning?: string;
fallback?: boolean;
}
//#endregion
/**
* This class enables easy integration of AI agents into your application.
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.onInitialized(async (success) => {
* if (!success) return console.error('Failed to initialize');
*
* const reply = await agent.message('Hello!');
*
* console.log('Agent:', reply);
* });
*
* @example
* // Browser (bundlers like Vite/Next) — ESM import is the same
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY', { useStorage: true });
*
* // It takes a moment to initialize; you can wait via callback or a simple check
* agent.onInitialized(async () => {
* const hello = await agent.message('Hi there!');
*
* console.log(hello);
* });
*
* // Or: simple check (only if you need to call immediately after initialization)
* const waitForInit = async (agentInstance) => { while (!agentInstance.isInitialized) await new Promise(resolve => setTimeout(resolve, 50)); };
* await waitForInit(agent);
*
* console.log(await agent.message('Ready!'));
*
* @param agentKey - The key for authenticating the agent.
* @param options - Configuration options for the agent.
*/
export default class Heylock {
/**
* Creates an instance of the Heylock agent and immediately starts the initialization.
*
* @param agentKey - The authentication key for the agent. Must be a non-empty string.
* @param options - Optional configuration settings for the agent.
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.onInitialized((success) => {
* if (!success) console.error('Init failed');
* });
*/
constructor(agentKey: string, options?: AgentOptions);
//#region Agent properties
/**
* Indicates whether the agent has been successfully initialized.
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* // Simple wait loop if you need to start right away
* while (!agent.isInitialized) {
* await new Promise(resolve => setTimeout(resolve, 50));
* }
*
* console.log('Ready');
*/
isInitialized: boolean;
/**
* Gets the remaining usage for the agent.
* @readonly
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.onInitialized(async () => {
* const usage = await agent.fetchUsageRemaining();
*
* console.log(usage.messages, usage.sorts, usage.rewrites);
* });
*/
readonly usageRemaining: UsageRemaining;
/**
* Gets the message history for the agent.
* Each message contains content and a role ('user' or 'assistant').
* @readonly
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.addMessage('Hi', 'user');
*
* console.log(agent.messageHistory); // [{ content: 'Hi', role: 'user' }]
*/
readonly messageHistory: ReadonlyArray<Message>;
/**
* Gets the context for the agent.
* Each context entry contains content and a timestamp.
* @readonly
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.addContextEntry('User loves sci-fi books');
*
* console.log(agent.context[0].content); // 'User loves sci-fi books'
*/
readonly context: ReadonlyArray<ContextEntry>;
//#endregion
//#region Callbacks
/**
* Registers a callback to be called when the agent is initialized.
* @param callback - Function called when initialization completes. Receives a boolean indicating success (true) or failure (false).
* @returns Unsubscribe function to remove the callback.
* @remarks The callback is triggered for both success and failure. Edge cases to consider include invalid agentKey, network errors, or accidental agent deletion.
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* const unsubscribe = agent.onInitialized(async (success) => {
* if (!success) return console.error('Initialization failed');
*
* console.log('Initialized!');
*
* unsubscribe(); // unsubscribe if no longer needed
* });
*/
onInitialized(callback: (success: boolean) => void): () => void;
/**
* Registers a callback to be called when the message history changes.
* @param callback - Function called when the message history changes. Receives the new message history array.
* @returns Unsubscribe function to remove the callback.
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* const unsubscribe = agent.onMessageHistoryChange((history) => {
* console.log('History size:', history.length);
* });
*
* agent.addMessage('Hello', 'user');
*/
onMessageHistoryChange(callback: (messageHistory: Array<Message>) => void): () => void;
/**
* Registers a callback to be called when the context changes.
* @param callback - Function called with the new context array.
* @returns Unsubscribe function to remove the callback.
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY', { useStorage: true });
*
* const unsubscribe = agent.onContextChange((context) => {
* console.log('Context entries:', context.length);
* });
*
* agent.addContextEntry('User hovers over low-sugar snacks');
*/
onContextChange(callback: (context: Array<ContextEntry>) => void): () => void;
//#endregion
//#region Message history management
/**
* Adds a message to the agent's message history.
* @param content - The message content.
* @param role - The role of the sender ('user' or 'assistant'). Defaults to 'user'.
* @returns The index of the newly added message.
* @throws Error if content is not a string, too long, or role is invalid.
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* const index = agent.addMessage('Hey!', 'user');
*
* console.log('Added at index', index);
*/
addMessage(content: string, role?: 'user' | 'assistant'): number;
/**
* Removes a message from the message history by index.
* @param index - The index of the message to remove.
* @throws Error if index is out of bounds or invalid.
*
* @example
* agent.removeMessage(0);
*/
removeMessage(index: number): void;
/**
* Modifies an existing message in the message history.
* @param index - The index of the message to modify.
* @param content - The new message content.
* @param role - The new role ('user' or 'assistant'). Optional.
* @throws Error if index/content/role are invalid.
*
* @example
* agent.modifyMessage(0, 'Updated text');
*/
modifyMessage(index: number, content: string, role?: 'user' | 'assistant'): void;
/**
* Replaces the entire message history with a new array.
* @param messageHistory - Array of message objects.
* @throws Error if the array or its contents are invalid.
*
* @example
* agent.setMessageHistory([
* { content: 'Hi', role: 'user' },
* { content: 'Hello!', role: 'assistant' }
* ]);
*/
setMessageHistory(messageHistory: Array<Message>): void;
/**
* Clears all messages from the message history.
*
* @example
* agent.clearMessageHistory();
*/
clearMessageHistory(): void;
//#endregion
//#region Context management
/**
* Adds a new entry to the agent's context.
* @param content - The context information to add. Must be a non-empty string.
* @param timestamp - Optional timestamp in milliseconds since Unix epoch (January 1, 1970 UTC). If not provided, the current time is used.
* @returns The index of the added context entry.
* @throws Error if content is invalid (e.g., not a string or empty), or if timestamp is not a valid number.
*
* @example
* // Add context to personalize agent responses
* const index = agent.addContextEntry('User opened Berlin accommodations catalog');
*
* console.log('Context index:', index);
*/
addContextEntry(content: string, timestamp?: number): number;
/**
* Removes a context entry by index.
* @param index - The index of the context entry to remove.
* @throws Error if index is out of bounds or invalid.
*
* @example
* agent.removeContextEntry(0);
*/
removeContextEntry(index: number): void;
/**
* Modifies an existing context entry.
* @param index - The index of the context entry to modify.
* @param content - The new context content.
* @param timestamp - The new timestamp (optional).
* @throws Error if index/content/timestamp are invalid.
*
* @example
* agent.modifyContextEntry(0, 'Switched to dark mode');
*/
modifyContextEntry(index: number, content: string, timestamp?: number): void;
/**
* Replaces the entire context with a new array.
* @param contextArray - Array of context entries.
* @throws Error if the array or its contents are invalid.
*
* @example
* agent.setContext([
* { content: 'Played jazz', timestamp: Date.now() - 60000 },
* { content: 'Opened gifts catalog', timestamp: Date.now() }
* ]);
*/
setContext(contextArray: Array<ContextEntry>): void;
/**
* Clears all context entries.
*
* @example
* agent.clearContext();
*/
clearContext(): void;
/**
* Returns a formatted string representation of the context.
* @returns A string describing the context entries and their timestamps.
*
* @example
* console.log(agent.getContextString()); // "Played jazz 1 minute ago. Opened gifts catalog now."
*/
getContextString(): string;
//#endregion
//#region Usage route
/**
* Fetches the agent's remaining usage limits from the server.
* @returns Promise that resolves to a UsageRemaining object with the current limits for messages, sorts, and rewrites.
* @throws Error if the agent is not initialized, authorization fails, or a network/server error occurs.
*
* @example
* try {
* const usage = await agent.fetchUsageRemaining();
*
* console.log('Remaining:', usage);
* } catch (err) {
* console.error('Could not fetch limits', err);
* }
*/
fetchUsageRemaining(): Promise<UsageRemaining>;
//#endregion
//#region Message route
/**
* Sends a message to the agent and receives a response.
* @param content - The message content to send.
* @param useContext - Whether to include context in the request. Defaults to true.
* @param saveToMessageHistory - Whether to save the message and response to history. Defaults to true.
* @returns Promise that resolves to the agent's response string.
* @throws Error if the agent is not initialized, arguments are invalid, or a network/server error occurs.
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.onInitialized(async () => {
* // Optionally add context
* agent.addContextEntry('User likes quick, concise answers about jazz');
*
* try {
* const reply = await agent.message('Give me a one-sentence tip');
* console.log(reply);
* } catch (error) {
* console.error('Message failed', error);
* }
* });
*/
message(content: string, useContext?: boolean, saveToMessageHistory?: boolean): Promise<string>;
/**
* Sends a message to the agent and receives a streamed response (async generator).
* @param content - The message content to send.
* @param useContext - Whether to include context in the request. Defaults to true.
* @param saveToMessageHistory - Whether to save the message and response to history. Defaults to true.
* @returns Async generator yielding response chunks as strings.
* @throws Error if the agent is not initialized, arguments are invalid, or a network/server error occurs.
*
* @example
* // Stream and log chunks as they arrive (for-await)
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.onInitialized(async () => {
* for await (const chunk of agent.messageStream('Explain in small steps...')) {
* console.log(chunk);
* }
* // messageHistory is updated internally as the stream progresses
* });
*
* @example
* // Collect streamed chunks into a single string
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.onInitialized(async () => {
* let full = '';
*
* for await (const chunk of agent.messageStream('List three tips about focus')) {
* full += chunk;
* }
*
* console.log('\nFull:', full);
* });
*/
messageStream(content: string, useContext?: boolean, saveToMessageHistory?: boolean): AsyncGenerator<string, string, unknown>;
/**
* Returns a greeting message from the agent.
* @param instructions - Optional instructions for the greeting.
* @param useContext - Whether to include context in the request. Defaults to true.
* @param saveToMessageHistory - Whether to save the greeting to history. Defaults to true.
* @returns Promise that resolves to the agent's greeting string.
* @throws Error if the arguments are invalid or a network/server error occurs.
*
* @example
* const greeting = await agent.greet();
* console.log(greeting); // "Hi! I'm here to help you!"
*
* @example
* // With instructions and context
* agent.addContextEntry('Visitor opened pricing page');
*
* const personalized = await agent.greet('Mention the current discount');
* console.log(personalized);
*/
greet(instructions?: string, useContext?: boolean, saveToMessageHistory?: boolean): Promise<string>;
//#endregion
//#region Should-Engage route
/**
* Checks if the agent should engage with the user, based on instructions and context.
* Throttles requests to prevent excessive calls.
* @param instructions - Optional instructions for engagement.
* @returns Promise that resolves to a ShouldEngageResult object.
* @throws Error if arguments are invalid, throttling is in effect, or a network/server error occurs.
*
* @example
* // Note: calls are throttled (about 15s). Avoid calling repeatedly inside loops.
* const decision = await agent.shouldEngage('Engage if user idles on checkout for >30s');
*
* if (decision.warning) console.warn(decision.warning);
*
* console.log('Engage?', decision.shouldEngage, 'Reason:', decision.reasoning);
*/
shouldEngage(instructions?: string): Promise<ShouldEngageResult>;
//#endregion
//#region Rewrite route
/**
* Rewrites the given content according to optional instructions and context.
* @param content - The text content to rewrite. Must be a non-empty string.
* @param instructions - Optional instructions for the rewrite. Must be a string if provided.
* @param useContext - Whether to include context in the rewrite request. Defaults to true.
* @returns Promise that resolves to the rewritten text string.
* @throws Error if arguments are invalid, usage limits are exceeded, authorization fails, or a network/server error occurs.
*
* @example
* agent.addContextEntry('Tone preference: friendly and concise');
*
* const improved = await agent.rewrite('pls ship my order fast', 'Fix grammar and be polite');
*
* console.log(improved); // "Please ship my order quickly. Thank you!"
*/
rewrite(content: string, instructions?: string, useContext?: boolean): Promise<string>;
//#endregion
//#region Sort route
/**
* Sorts an array using AI, with optional instructions and context for personalization.
* Returns a SortResult containing the sorted array and reasoning.
* @param array - The array to sort.
* @param instructions - Optional instructions for sorting (e.g., "Order by the likelihood of purchase").
* @param useContext - Whether to use context for sorting (default: true).
* @returns Promise resolving to a SortResult.
*
* @example
* import Heylock from 'heylock';
*
* const agent = new Heylock('YOUR_AGENT_KEY');
*
* agent.onInitialized(async () => {
* const products = [
* { name: 'Eco Bottle', price: 12 },
* { name: 'Steel Bottle', price: 25 },
* { name: 'Plastic Bottle', price: 3 }
* ];
*
* const result = await agent.sort(products, 'Order these products by which one the user is most likely to buy');
*
* console.log(result.array);
* });
*/
sort(array: any[], instructions?: string, useContext?: boolean): Promise<SortResult>;
//#endregion
}