@robota-sdk/core
Version:
Core functionality for building AI agents with TypeScript - conversation management, function calling, and multi-provider support for OpenAI, Anthropic, and Google AI
1,645 lines (1,631 loc) • 53.5 kB
JavaScript
'use strict';
var tiktoken = require('@dqbd/tiktoken');
// src/conversation-history.ts
function isUserMessage(message) {
return message.role === "user";
}
function isAssistantMessage(message) {
return message.role === "assistant";
}
function isSystemMessage(message) {
return message.role === "system";
}
function isToolMessage(message) {
return message.role === "tool";
}
function createUserMessage(content, options) {
return {
role: "user",
content,
name: options?.name,
timestamp: /* @__PURE__ */ new Date(),
metadata: options?.metadata
};
}
function createAssistantMessage(content, options) {
return {
role: "assistant",
content,
toolCalls: options?.toolCalls,
timestamp: /* @__PURE__ */ new Date(),
metadata: options?.metadata
};
}
function createSystemMessage(content, options) {
return {
role: "system",
content,
name: options?.name,
timestamp: /* @__PURE__ */ new Date(),
metadata: options?.metadata
};
}
function createToolMessage(content, options) {
return {
role: "tool",
content,
toolCallId: options?.toolCallId,
name: options?.name,
timestamp: /* @__PURE__ */ new Date(),
metadata: options?.metadata
};
}
var BaseConversationHistory = class {
/** Maximum number of messages to store (0 = unlimited) */
maxMessages;
constructor(options) {
this.maxMessages = options?.maxMessages || 0;
}
// Common convenience methods with shared implementation
addUserMessage(content, metadata) {
const message = createUserMessage(content, { metadata });
this.addMessage(message);
}
addAssistantMessage(content, toolCalls, metadata) {
const message = createAssistantMessage(content, { toolCalls, metadata });
this.addMessage(message);
}
addSystemMessage(content, metadata) {
const message = createSystemMessage(content, { metadata });
this.addMessage(message);
}
addToolMessageWithId(content, toolCallId, toolName, metadata) {
const message = createToolMessage(content, {
toolCallId,
name: toolName,
metadata
});
this.addMessage(message);
}
getMessagesByRole(role) {
return this.getMessages().filter((message) => message.role === role);
}
getRecentMessages(count) {
const messages = this.getMessages();
return messages.slice(-count);
}
/**
* Apply message limit by removing oldest messages while preserving system messages
* @internal
*/
applyMessageLimit(messages) {
if (this.maxMessages > 0 && messages.length > this.maxMessages) {
const systemMessages = messages.filter(isSystemMessage);
const nonSystemMessages = messages.filter((msg) => !isSystemMessage(msg));
const availableSlots = Math.max(0, this.maxMessages - systemMessages.length);
const limitedNonSystemMessages = nonSystemMessages.slice(-availableSlots);
return [...systemMessages, ...limitedNonSystemMessages];
}
return messages;
}
};
var SimpleConversationHistory = class extends BaseConversationHistory {
/** @internal Array storing all messages */
messages = [];
/**
* Create a new SimpleConversationHistory instance
*
* @param options - Configuration options
* @param options.maxMessages - Maximum number of messages to keep (0 = unlimited)
*/
constructor(options) {
super(options);
}
/**
* Add a message to conversation history
*
* Appends the message to the history and applies message count limits if configured.
* System messages are always preserved when applying limits.
*
* @param message - Universal message to add
*/
addMessage(message) {
this.messages.push(message);
this._applyMessageLimit();
}
/**
* Get all messages in chronological order
*
* @returns Defensive copy of all messages
*/
getMessages() {
return [...this.messages];
}
/**
* Get total message count
*
* @returns Number of messages currently stored
*/
getMessageCount() {
return this.messages.length;
}
/**
* Clear all conversation history
*
* Removes all messages from the history.
*/
clear() {
this.messages = [];
}
/**
* Apply message count limits while preserving system messages
*
* When maxMessages is set and exceeded, this method removes older messages
* while always preserving system messages which are important for context.
*
* @internal
*/
_applyMessageLimit() {
this.messages = this.applyMessageLimit(this.messages);
}
};
var PersistentSystemConversationHistory = class extends BaseConversationHistory {
/** @internal Underlying conversation history */
history;
/** @internal Current system prompt */
systemPrompt;
/**
* Create a new PersistentSystemConversationHistory instance
*
* @param systemPrompt - Initial system prompt to set
* @param options - Configuration options passed to underlying SimpleConversationHistory
*/
constructor(systemPrompt, options) {
super(options);
this.history = new SimpleConversationHistory(options);
this.systemPrompt = systemPrompt;
this.history.addSystemMessage(this.systemPrompt);
}
/**
* Add a message to conversation history (delegates to underlying history)
*
* @param message - Universal message to add
*/
addMessage(message) {
this.history.addMessage(message);
}
/**
* Get all messages (delegates to underlying history)
*
* @returns Array of all messages including system messages
*/
getMessages() {
return this.history.getMessages();
}
/**
* Get total message count (delegates to underlying history)
*
* @returns Total number of messages including system messages
*/
getMessageCount() {
return this.history.getMessageCount();
}
/**
* Clear conversation history but preserve system prompt
*
* Clears all messages and re-adds the current system prompt.
*/
clear() {
this.history.clear();
this.history.addSystemMessage(this.systemPrompt);
}
/**
* Update the system prompt and refresh system messages
*
* Removes old system messages, updates the system prompt, and adds
* the new system prompt as a system message.
*
* @param systemPrompt - New system prompt to set
*/
updateSystemPrompt(systemPrompt) {
this.systemPrompt = systemPrompt;
const messages = this.history.getMessages();
const nonSystemMessages = messages.filter((msg) => !isSystemMessage(msg));
this.history.clear();
this.history.addSystemMessage(this.systemPrompt);
nonSystemMessages.forEach((message) => this.history.addMessage(message));
}
/**
* Get the current system prompt
*
* @returns Current system prompt string
*/
getSystemPrompt() {
return this.systemPrompt;
}
};
// src/managers/ai-provider-manager.ts
var AIProviderManager = class {
aiProviders = {};
currentProvider;
currentModel;
/**
* Add an AI provider
*
* @param name - Provider name
* @param aiProvider - AI provider instance
*/
addProvider(name, aiProvider) {
this.aiProviders[name] = aiProvider;
}
/**
* Set current AI provider and model
*
* @param providerName - Provider name
* @param model - Model name
*/
setCurrentAI(providerName, model) {
if (!this.aiProviders[providerName]) {
throw new Error(`AI provider '${providerName}' not found.`);
}
this.currentProvider = providerName;
this.currentModel = model;
}
/**
* Get currently configured AI provider and model
*/
getCurrentAI() {
return {
provider: this.currentProvider,
model: this.currentModel
};
}
/**
* Get current AI provider instance
*/
getCurrentProvider() {
if (!this.currentProvider) {
return null;
}
return this.aiProviders[this.currentProvider] || null;
}
/**
* Get current model name
*/
getCurrentModel() {
return this.currentModel || null;
}
/**
* Check if AI provider is configured
*/
isConfigured() {
return !!(this.currentProvider && this.currentModel);
}
/**
* Release resources of all AI providers
*/
async close() {
for (const [_name, aiProvider] of Object.entries(this.aiProviders)) {
if (aiProvider.close && typeof aiProvider.close === "function") {
await aiProvider.close();
}
}
}
};
// src/managers/tool-provider-manager.ts
var ToolProviderManager = class {
toolProviders = [];
allowedFunctions;
logger;
constructor(logger2, allowedFunctions) {
this.logger = logger2;
this.allowedFunctions = allowedFunctions;
}
/**
* Add a Tool Provider
*
* @param toolProvider - Tool provider instance
*/
addProvider(toolProvider) {
this.toolProviders.push(toolProvider);
}
/**
* Add multiple Tool Providers
*
* @param toolProviders - Array of tool providers
*/
addProviders(toolProviders) {
this.toolProviders.push(...toolProviders);
}
/**
* Set allowed function list
*
* @param allowedFunctions - Array of allowed function names
*/
setAllowedFunctions(allowedFunctions) {
this.allowedFunctions = allowedFunctions;
}
/**
* Call a tool
*
* @param toolName - Name of the tool to call
* @param parameters - Parameters to pass to the tool
* @returns Tool call result
*/
async callTool(toolName, parameters) {
if (this.toolProviders.length === 0) {
throw new Error("Tool providers are not configured.");
}
if (this.allowedFunctions && !this.allowedFunctions.includes(toolName)) {
throw new Error(`Tool '${toolName}' is not allowed.`);
}
for (const toolProvider of this.toolProviders) {
if (toolProvider.functions?.some((fn) => fn.name === toolName)) {
try {
const result = await toolProvider.callTool(toolName, parameters);
return result;
} catch (error) {
this.logger.error(`Error calling tool '${toolName}':`, error);
throw new Error(`Tool call failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
throw new Error(`Tool '${toolName}' not found.`);
}
/**
* Get list of available tools
*
* @returns List of tool schemas
*/
getAvailableTools() {
return this.toolProviders.reduce((tools, toolProvider) => {
if (toolProvider.functions) {
tools.push(...toolProvider.functions);
}
return tools;
}, []);
}
/**
* Get the number of registered Tool Providers
*/
getProviderCount() {
return this.toolProviders.length;
}
/**
* Check if Tool Providers are registered
*/
hasProviders() {
return this.toolProviders.length > 0;
}
/**
* Check if a specific tool is available
*
* @param toolName - Name of the tool to check
*/
hasTool(toolName) {
return this.toolProviders.some(
(toolProvider) => toolProvider.functions?.some((fn) => fn.name === toolName)
);
}
/**
* Clean up resources for all tool providers
*/
async close() {
for (const toolProvider of this.toolProviders) {
const provider = toolProvider;
if (provider.close && typeof provider.close === "function") {
try {
await provider.close();
} catch (error) {
this.logger.error("Error closing tool provider:", error);
}
}
}
}
};
// src/managers/system-message-manager.ts
var SystemMessageManager = class {
systemPrompt;
systemMessages;
/**
* Set a single system prompt
*
* @param prompt - System prompt content
*/
setSystemPrompt(prompt) {
this.systemPrompt = prompt;
this.systemMessages = void 0;
}
/**
* Set multiple system messages
*
* @param messages - Array of system messages
*/
setSystemMessages(messages) {
this.systemPrompt = void 0;
this.systemMessages = messages;
}
/**
* Add a new system message to existing system messages
*
* @param content - Content of the system message to add
*/
addSystemMessage(content) {
if (this.systemPrompt) {
if (!this.systemMessages || this.systemMessages.length === 1 && this.systemMessages[0].role === "system" && this.systemMessages[0].content === this.systemPrompt) {
this.systemMessages = [
{ role: "system", content: this.systemPrompt },
{ role: "system", content }
];
} else {
this.systemMessages.push({ role: "system", content });
}
this.systemPrompt = void 0;
} else {
if (!this.systemMessages) {
this.systemMessages = [];
}
this.systemMessages.push({ role: "system", content });
}
}
/**
* Get the current system prompt
*/
getSystemPrompt() {
return this.systemPrompt;
}
/**
* Get the current system messages
*/
getSystemMessages() {
return this.systemMessages;
}
/**
* Check if system messages are configured
*/
hasSystemMessages() {
return !!(this.systemPrompt || this.systemMessages && this.systemMessages.length > 0);
}
/**
* Clear system messages
*/
clear() {
this.systemPrompt = void 0;
this.systemMessages = void 0;
}
};
// src/managers/function-call-manager.ts
var FunctionCallManager = class {
config;
constructor(initialConfig) {
this.config = {
defaultMode: initialConfig?.defaultMode || "auto",
maxCalls: initialConfig?.maxCalls || 10,
timeout: initialConfig?.timeout || 3e4,
allowedFunctions: initialConfig?.allowedFunctions
};
}
/**
* Set function call mode
*
* @param mode - Function call mode ('auto', 'force', 'disabled')
*/
setFunctionCallMode(mode) {
this.config.defaultMode = mode;
}
/**
* Configure function call settings
*
* @param config - Function call configuration options
*/
configure(config) {
if (config.mode) {
this.config.defaultMode = config.mode;
}
if (config.maxCalls !== void 0) {
this.config.maxCalls = config.maxCalls;
}
if (config.timeout !== void 0) {
this.config.timeout = config.timeout;
}
if (config.allowedFunctions) {
this.config.allowedFunctions = config.allowedFunctions;
}
}
/**
* Get current function call mode
*/
getDefaultMode() {
return this.config.defaultMode;
}
/**
* Get maximum call count
*/
getMaxCalls() {
return this.config.maxCalls;
}
/**
* Get timeout setting
*/
getTimeout() {
return this.config.timeout;
}
/**
* Get allowed functions list
*/
getAllowedFunctions() {
return this.config.allowedFunctions;
}
/**
* Get complete configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Check if a specific function is allowed
*
* @param functionName - Function name to check
*/
isFunctionAllowed(functionName) {
if (!this.config.allowedFunctions) {
return true;
}
return this.config.allowedFunctions.includes(functionName);
}
};
// src/managers/analytics-manager.ts
var AnalyticsManager = class {
requestCount = 0;
totalTokensUsed = 0;
tokenUsageHistory = [];
/**
* Record a new request and token usage
* @param tokensUsed - Number of tokens used in this request
* @param provider - AI provider name
* @param model - Model name
*/
recordRequest(tokensUsed, provider, model) {
this.requestCount++;
this.totalTokensUsed += tokensUsed;
this.tokenUsageHistory.push({
timestamp: /* @__PURE__ */ new Date(),
tokens: tokensUsed,
provider,
model
});
}
/**
* Get total number of requests made
*/
getRequestCount() {
return this.requestCount;
}
/**
* Get total number of tokens used
*/
getTotalTokensUsed() {
return this.totalTokensUsed;
}
/**
* Get detailed analytics data
*/
getAnalytics() {
const averageTokensPerRequest = this.requestCount > 0 ? this.totalTokensUsed / this.requestCount : 0;
return {
requestCount: this.requestCount,
totalTokensUsed: this.totalTokensUsed,
averageTokensPerRequest: Math.round(averageTokensPerRequest * 100) / 100,
tokenUsageHistory: [...this.tokenUsageHistory]
};
}
/**
* Reset all analytics data
*/
reset() {
this.requestCount = 0;
this.totalTokensUsed = 0;
this.tokenUsageHistory = [];
}
/**
* Get token usage for a specific time period
* @param startDate - Start date for the period
* @param endDate - End date for the period (optional, defaults to now)
*/
getTokenUsageByPeriod(startDate, endDate) {
const end = endDate || /* @__PURE__ */ new Date();
const filteredHistory = this.tokenUsageHistory.filter(
(entry) => entry.timestamp >= startDate && entry.timestamp <= end
);
const totalTokens = filteredHistory.reduce((sum, entry) => sum + entry.tokens, 0);
return {
totalTokens,
requestCount: filteredHistory.length,
usageHistory: filteredHistory
};
}
};
// src/managers/request-limit-manager.ts
var RequestLimitManager = class {
maxTokens;
maxRequests;
currentTokensUsed = 0;
currentRequestCount = 0;
constructor(maxTokens = 4096, maxRequests = 25) {
this.maxTokens = maxTokens;
this.maxRequests = maxRequests;
}
/**
* Set maximum token limit (0 = unlimited)
*/
setMaxTokens(limit) {
if (limit < 0) {
throw new Error("Max token limit cannot be negative");
}
this.maxTokens = limit;
}
/**
* Set maximum request limit (0 = unlimited)
*/
setMaxRequests(limit) {
if (limit < 0) {
throw new Error("Max request limit cannot be negative");
}
this.maxRequests = limit;
}
/**
* Get current maximum token limit
*/
getMaxTokens() {
return this.maxTokens;
}
/**
* Get current maximum request limit
*/
getMaxRequests() {
return this.maxRequests;
}
/**
* Check if adding estimated tokens would exceed the limit
* @param estimatedTokens - Estimated number of tokens to add
* @throws Error if token limit would be exceeded
*/
checkEstimatedTokenLimit(estimatedTokens) {
if (this.maxTokens === 0) return;
if (this.currentTokensUsed + estimatedTokens > this.maxTokens) {
throw new Error(
`Estimated token limit would be exceeded. Current usage: ${this.currentTokensUsed}, estimated additional tokens: ${estimatedTokens}, limit: ${this.maxTokens}. Request aborted to prevent unnecessary costs.`
);
}
}
/**
* Check if adding new tokens would exceed the limit
* @param tokensToAdd - Number of tokens to add
* @throws Error if token limit would be exceeded
*/
checkTokenLimit(tokensToAdd) {
if (this.maxTokens === 0) return;
if (this.currentTokensUsed + tokensToAdd > this.maxTokens) {
throw new Error(
`Token limit exceeded. Current usage: ${this.currentTokensUsed}, attempting to add: ${tokensToAdd}, limit: ${this.maxTokens}`
);
}
}
/**
* Check if adding a new request would exceed the request limit
* @throws Error if request limit would be exceeded
*/
checkRequestLimit() {
if (this.maxRequests === 0) return;
if (this.currentRequestCount + 1 > this.maxRequests) {
throw new Error(
`Request limit exceeded. Current requests: ${this.currentRequestCount}, limit: ${this.maxRequests}`
);
}
}
/**
* Record a successful request with token usage
* @param tokensUsed - Number of tokens used in this request
*/
recordRequest(tokensUsed) {
this.checkRequestLimit();
this.checkTokenLimit(tokensUsed);
this.currentRequestCount++;
this.currentTokensUsed += tokensUsed;
}
/**
* Get current token usage
*/
getCurrentTokensUsed() {
return this.currentTokensUsed;
}
/**
* Get current request count
*/
getCurrentRequestCount() {
return this.currentRequestCount;
}
/**
* Get remaining tokens (undefined if unlimited)
*/
getRemainingTokens() {
if (this.maxTokens === 0) return void 0;
return Math.max(0, this.maxTokens - this.currentTokensUsed);
}
/**
* Get remaining requests (undefined if unlimited)
*/
getRemainingRequests() {
if (this.maxRequests === 0) return void 0;
return Math.max(0, this.maxRequests - this.currentRequestCount);
}
/**
* Check if tokens are unlimited
*/
isTokensUnlimited() {
return this.maxTokens === 0;
}
/**
* Check if requests are unlimited
*/
isRequestsUnlimited() {
return this.maxRequests === 0;
}
/**
* Reset usage counters (but keep limits)
*/
reset() {
this.currentTokensUsed = 0;
this.currentRequestCount = 0;
}
/**
* Get comprehensive limit information
*/
getLimitInfo() {
return {
maxTokens: this.maxTokens,
maxRequests: this.maxRequests,
currentTokensUsed: this.currentTokensUsed,
currentRequestCount: this.currentRequestCount,
remainingTokens: this.getRemainingTokens(),
remainingRequests: this.getRemainingRequests(),
isTokensUnlimited: this.isTokensUnlimited(),
isRequestsUnlimited: this.isRequestsUnlimited()
};
}
};
// src/utils.ts
function removeUndefined(obj) {
const result = { ...obj };
for (const key in result) {
if (result[key] === void 0) {
delete result[key];
} else if (typeof result[key] === "object" && result[key] !== null) {
result[key] = removeUndefined(result[key]);
}
}
return result;
}
var logger = {
info: (...args) => {
if (process.env.NODE_ENV !== "production") {
console.log("[INFO]", ...args);
}
},
warn: (...args) => {
if (process.env.NODE_ENV !== "production") {
console.warn("[WARN]", ...args);
}
},
error: (...args) => {
if (process.env.NODE_ENV !== "production") {
console.error("[ERROR]", ...args);
}
}
};
function convertUniversalToBaseMessage(universalMessage) {
const baseMessage = {
role: universalMessage.role === "tool" ? "function" : universalMessage.role,
content: universalMessage.content || ""
};
if (isUserMessage(universalMessage) && universalMessage.name) {
baseMessage.name = universalMessage.name;
}
if (isAssistantMessage(universalMessage) && universalMessage.toolCalls) {
baseMessage.toolCalls = universalMessage.toolCalls;
}
if (isToolMessage(universalMessage) && universalMessage.toolCallId) {
baseMessage.toolCallId = universalMessage.toolCallId;
baseMessage.name = universalMessage.name;
}
return baseMessage;
}
function convertUniversalToBaseMessages(universalMessages) {
return universalMessages.map(convertUniversalToBaseMessage);
}
// src/analyzers/token-analyzer.ts
var TokenAnalyzer = class {
/**
* Calculate tokens for a given text using tiktoken
* @param text - Text to calculate tokens for
* @param model - Model name for encoding selection
* @returns Number of tokens
*/
calculateTokens(text, model) {
if (!text) return 0;
try {
let encoding;
if (model) {
const modelMapping = {
"gpt-4": "gpt-4",
"gpt-4-0613": "gpt-4-0613",
"gpt-4-32k": "gpt-4-32k",
"gpt-4-32k-0613": "gpt-4-32k-0613",
"gpt-4-1106-preview": "gpt-4-1106-preview",
"gpt-4-0125-preview": "gpt-4-0125-preview",
"gpt-4-turbo-preview": "gpt-4-0125-preview",
"gpt-3.5-turbo": "gpt-3.5-turbo",
"gpt-3.5-turbo-0613": "gpt-3.5-turbo-0613",
"gpt-3.5-turbo-16k": "gpt-3.5-turbo-16k",
"gpt-3.5-turbo-16k-0613": "gpt-3.5-turbo-16k-0613",
"gpt-3.5-turbo-1106": "gpt-3.5-turbo-1106",
"gpt-3.5-turbo-0125": "gpt-3.5-turbo-0125"
};
const mappedModel = modelMapping[model] || model;
try {
encoding = tiktoken.encoding_for_model(mappedModel);
} catch (error) {
encoding = tiktoken.get_encoding("cl100k_base");
}
} else {
encoding = tiktoken.get_encoding("cl100k_base");
}
const tokens = encoding.encode(text);
const tokenCount = tokens.length;
encoding.free();
return tokenCount;
} catch (error) {
logger.warn("Failed to calculate tokens with tiktoken, using estimation:", error);
return Math.ceil(text.length / 4);
}
}
/**
* Calculate tokens for an array of messages
* @param messages - Array of messages to calculate tokens for
* @param model - Model name for encoding selection
* @returns Number of tokens
*/
calculateMessagesTokens(messages, model) {
if (!messages || messages.length === 0) return 0;
try {
let totalTokens = 0;
for (const message of messages) {
if (message.content) {
totalTokens += this.calculateTokens(message.content, model);
}
totalTokens += 3;
if (message.name) {
totalTokens += this.calculateTokens(message.name, model);
}
}
totalTokens += 3;
return totalTokens;
} catch (error) {
logger.warn("Failed to calculate message tokens, using estimation:", error);
const totalText = messages.map((m) => (m.content || "") + (m.name || "")).join(" ");
return Math.ceil(totalText.length / 4);
}
}
};
// src/services/conversation-service.ts
var ConversationService = class {
temperature;
maxTokens;
logger;
debug;
constructor(temperature, maxTokens, logger2 = console, debug = false) {
this.temperature = temperature;
this.maxTokens = maxTokens;
this.logger = logger2;
this.debug = debug;
}
/**
* Prepare context
*
* @param conversationHistory - ConversationHistory instance
* @param systemPrompt - Optional system prompt
* @param systemMessages - System messages
* @param options - Run options
*/
prepareContext(conversationHistory, systemPrompt, systemMessages, options = {}) {
const universalMessages = conversationHistory.getMessages();
const context = {
messages: universalMessages
};
if (options.systemPrompt) {
context.systemPrompt = options.systemPrompt;
} else if (systemMessages && systemMessages.length > 0) {
context.systemMessages = systemMessages;
} else if (systemPrompt) {
context.systemPrompt = systemPrompt;
}
return context;
}
/**
* Generate response
*
* @param aiProvider - AI provider
* @param model - Model name
* @param context - Conversation context
* @param options - Run options
* @param availableTools - Available tools
* @param onToolCall - Tool call function
* @param conversationHistory - Conversation history to store messages sequentially
*/
async generateResponse(aiProvider, model, context, options = {}, availableTools = [], onToolCall, conversationHistory) {
try {
const response = await aiProvider.chat(model, context, {
...options,
temperature: options.temperature ?? this.temperature,
maxTokens: options.maxTokens ?? this.maxTokens,
tools: availableTools,
functionCallMode: options.functionCallMode,
forcedFunction: options.forcedFunction,
forcedArguments: options.forcedArguments
});
if (onToolCall && response.toolCalls && response.toolCalls.length > 0) {
return await this.handleToolCalls(
response,
context,
aiProvider,
model,
options,
availableTools,
onToolCall,
conversationHistory
);
}
return response;
} catch (error) {
logger.error("Error occurred during AI client call:", error);
throw new Error(`Error during AI client call: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Handle function call (simplified - only tool_calls format)
*/
async handleFunctionCall(response, context, aiProvider, model, options, availableTools, onToolCall, conversationHistory) {
if (response.toolCalls && response.toolCalls.length > 0) {
return await this.handleToolCalls(
response,
context,
aiProvider,
model,
options,
availableTools,
onToolCall,
conversationHistory
);
}
return response;
}
/**
* Handle new tool_calls format (OpenAI tool calling)
*/
async handleToolCalls(response, context, aiProvider, model, options, availableTools, onToolCall, conversationHistory) {
const toolCalls = response.toolCalls;
const assistantMessage = {
role: "assistant",
content: response.content || null,
timestamp: /* @__PURE__ */ new Date(),
toolCalls: toolCalls.map((tc) => ({
id: tc.id,
type: tc.type,
function: tc.function
}))
};
if (this.debug) {
this.logger.info("\u{1F50D} [Debug] Assistant message created:", {
role: assistantMessage.role,
content: assistantMessage.content,
hasToolCalls: !!assistantMessage.toolCalls,
toolCallsCount: assistantMessage.toolCalls?.length || 0,
toolCallIds: assistantMessage.toolCalls?.map((tc) => tc.id) || []
});
}
if (conversationHistory) {
if (this.debug) {
this.logger.info("\u{1F4DD} [ConversationService] Storing assistant message with tool calls");
}
conversationHistory.addAssistantMessage(
assistantMessage.content || "",
assistantMessage.toolCalls
);
}
const toolResultMessages = [];
for (const toolCall of toolCalls) {
const { name, arguments: args } = toolCall.function;
const parsedArgs = typeof args === "string" ? JSON.parse(args) : args;
try {
if (this.debug) {
this.logger.info(`\u{1F527} [Tool Call] ${name} (ID: ${toolCall.id})`, parsedArgs);
}
const toolResult = await onToolCall(name, parsedArgs);
if (this.debug) {
this.logger.info(`\u2705 [Tool Result] ${name} (ID: ${toolCall.id})`, toolResult);
}
const toolResponseMessage = {
role: "tool",
content: typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult),
timestamp: /* @__PURE__ */ new Date(),
toolCallId: toolCall.id,
// This is crucial for OpenAI
name
};
toolResultMessages.push(toolResponseMessage);
if (conversationHistory) {
if (this.debug) {
this.logger.info(`\u{1F4DD} [ConversationService] Storing tool result message (ID: ${toolCall.id})`);
}
conversationHistory.addToolMessageWithId(
toolResponseMessage.content,
toolCall.id,
name
);
}
} catch (toolError) {
logger.error("Error during tool call:", toolError);
const errorMessage = {
role: "tool",
content: JSON.stringify({ error: toolError instanceof Error ? toolError.message : String(toolError) }),
timestamp: /* @__PURE__ */ new Date(),
toolCallId: toolCall.id,
name
};
toolResultMessages.push(errorMessage);
if (conversationHistory) {
if (this.debug) {
this.logger.info(`\u{1F4DD} [ConversationService] Storing tool error message (ID: ${toolCall.id})`);
}
conversationHistory.addToolMessageWithId(
errorMessage.content,
toolCall.id,
name
);
}
}
}
const newContext = {
...context,
messages: [
...context.messages,
assistantMessage,
// Assistant message with tool_calls
...toolResultMessages
// Tool result messages with toolCallId
]
};
if (this.debug) {
this.logger.info("\u{1F50D} [Debug] Messages in new context:", newContext.messages.length);
newContext.messages.slice(-10).forEach((msg, idx) => {
const displayIdx = newContext.messages.length - 10 + idx + 1;
if (msg.role === "tool") {
this.logger.info(` ${displayIdx}. ${msg.role}: [tool_call_id: ${msg.toolCallId}]`);
} else if (msg.role === "assistant" && msg.toolCalls) {
this.logger.info(` ${displayIdx}. ${msg.role}: [has ${msg.toolCalls.length} tool calls]`);
} else {
this.logger.info(` ${displayIdx}. ${msg.role}: ${msg.content?.substring(0, 50) || ""}...`);
}
});
}
const finalResponse = await aiProvider.chat(model, newContext, {
...options,
temperature: options.temperature ?? this.temperature,
maxTokens: options.maxTokens ?? this.maxTokens,
tools: availableTools,
functionCallMode: "disabled"
// Prevent recursive tool calls
});
return finalResponse;
}
/**
* Generate streaming response
*/
async generateStream(aiProvider, model, context, options = {}, availableTools = []) {
if (!aiProvider.chatStream) {
throw new Error(`AI provider does not support streaming.`);
}
try {
return aiProvider.chatStream(model, context, {
...options,
temperature: options.temperature ?? this.temperature,
maxTokens: options.maxTokens ?? this.maxTokens,
tools: availableTools
});
} catch (error) {
this.logger.error("Error occurred during streaming API call:", error);
throw new Error(`Error during streaming API call: ${error instanceof Error ? error.message : String(error)}`);
}
}
};
// src/services/execution-service.ts
var ExecutionService = class {
constructor(aiProviderManager, toolProviderManager, requestLimitManager, analyticsManager, tokenAnalyzer, conversationService, logger2, debug, onToolCall) {
this.aiProviderManager = aiProviderManager;
this.toolProviderManager = toolProviderManager;
this.requestLimitManager = requestLimitManager;
this.analyticsManager = analyticsManager;
this.tokenAnalyzer = tokenAnalyzer;
this.conversationService = conversationService;
this.logger = logger2;
this.debug = debug;
this.onToolCall = onToolCall;
}
/**
* Execute a text prompt and return response
*/
async executePrompt(prompt, context) {
context.conversationHistory.addUserMessage(prompt);
const conversationContext = this.conversationService.prepareContext(
context.conversationHistory,
context.systemMessageManager.getSystemPrompt(),
context.systemMessageManager.getSystemMessages(),
context.options
);
const response = await this.executeWithLimitsAndAnalytics(conversationContext, context.options, context.conversationHistory);
response.toolCalls && response.toolCalls.length > 0;
if (response.content) {
if (this.debug) {
this.logger.info(`\u{1F4DD} [ExecutionService] Adding assistant response to history: ${response.content.substring(0, 100)}...`);
}
context.conversationHistory.addAssistantMessage(response.content);
}
if (this.debug) {
const hasToolCalls2 = response.toolCalls && response.toolCalls.length > 0;
if (hasToolCalls2) {
this.logger.info(`\u{1F50D} [ExecutionService] Tool calling completed. Final response: ${response.content?.substring(0, 100) || "No content"}...`);
} else {
this.logger.info(`\u{1F50D} [ExecutionService] Regular conversation completed. Response: ${response.content?.substring(0, 100) || "No content"}...`);
}
this.logger.info(`\u{1F4CA} [ExecutionService] Total messages in history: ${context.conversationHistory.getMessageCount()}`);
}
return response.content || "";
}
/**
* Execute a streaming prompt and return response stream
*/
async executeStream(prompt, context) {
context.conversationHistory.addUserMessage(prompt);
const conversationContext = this.conversationService.prepareContext(
context.conversationHistory,
context.systemMessageManager.getSystemPrompt(),
context.systemMessageManager.getSystemMessages(),
context.options
);
return this.generateStream(conversationContext, context.options);
}
/**
* Execute with request limits and analytics tracking
*/
async executeWithLimitsAndAnalytics(conversationContext, options, conversationHistory) {
this.requestLimitManager.checkRequestLimit();
const currentAI = this.aiProviderManager.getCurrentAI();
const currentModel = currentAI.model || "unknown";
await this.checkTokenLimits(conversationContext.messages, currentModel);
const response = await this.generateResponse(conversationContext, options, conversationHistory);
this.recordUsageAnalytics(response, currentAI.provider || "unknown", currentModel);
return response;
}
/**
* Check token limits before making API call
*/
async checkTokenLimits(messages, currentModel) {
if (!this.requestLimitManager.isTokensUnlimited()) {
try {
const estimatedTokens = this.tokenAnalyzer.calculateMessagesTokens(
messages,
currentModel
);
if (this.debug) {
this.logger.info(`\u{1F50D} [Token Estimation] Model: ${currentModel}, Estimated tokens: ${estimatedTokens}`);
}
this.requestLimitManager.checkEstimatedTokenLimit(estimatedTokens);
} catch (error) {
this.logger.error("Token limit check failed:", error);
throw error;
}
}
}
/**
* Record usage in analytics and limits
*/
recordUsageAnalytics(response, provider, model) {
if (response.usage?.totalTokens) {
this.requestLimitManager.recordRequest(response.usage.totalTokens);
this.analyticsManager.recordRequest(
response.usage.totalTokens,
provider,
model
);
}
}
/**
* Generate response (internal use)
*/
async generateResponse(context, options = {}, conversationHistory) {
if (!this.aiProviderManager.isConfigured()) {
throw new Error("Current AI provider and model are not configured. Use setCurrentAI() method to configure.");
}
const currentAiProvider = this.aiProviderManager.getCurrentProvider();
const currentModel = this.aiProviderManager.getCurrentModel();
return this.conversationService.generateResponse(
currentAiProvider,
currentModel,
context,
options,
this.toolProviderManager.getAvailableTools(),
async (toolName, params) => {
const result = await this.toolProviderManager.callTool(toolName, params);
if (this.onToolCall) {
this.onToolCall(toolName, params, result);
}
return result;
},
conversationHistory
);
}
/**
* Generate streaming response (internal use)
*/
async generateStream(context, options = {}) {
if (!this.aiProviderManager.isConfigured()) {
throw new Error("Current AI provider and model are not configured. Use setCurrentAI() method to configure.");
}
const currentAiProvider = this.aiProviderManager.getCurrentProvider();
const currentModel = this.aiProviderManager.getCurrentModel();
return this.conversationService.generateStream(
currentAiProvider,
currentModel,
context,
options,
this.toolProviderManager.getAvailableTools()
);
}
};
// src/managers/robota-config-manager.ts
var RobotaConfigManager = class {
config;
constructor(input) {
this.config = this.createConfiguration(input);
this.validateConfiguration();
}
/**
* Create configuration with defaults applied
*/
createConfiguration(input) {
return {
aiProviders: input.aiProviders || {},
currentProvider: input.currentProvider,
currentModel: input.currentModel,
temperature: input.temperature,
maxTokens: input.maxTokens,
toolProviders: input.toolProviders || [],
functionCallConfig: input.functionCallConfig,
onToolCall: input.onToolCall,
systemPrompt: input.systemPrompt,
systemMessages: input.systemMessages,
conversationHistory: input.conversationHistory || new SimpleConversationHistory(),
logger: input.logger || console,
debug: input.debug || false,
maxTokenLimit: input.maxTokenLimit ?? 4096,
maxRequestLimit: input.maxRequestLimit ?? 25
};
}
/**
* Validate configuration consistency
*/
validateConfiguration() {
if (this.config.currentProvider && !this.config.aiProviders[this.config.currentProvider]) {
throw new Error(`Current AI provider '${this.config.currentProvider}' is not registered`);
}
if (this.config.maxTokenLimit < 0) {
throw new Error("Maximum token limit cannot be negative");
}
if (this.config.maxRequestLimit < 0) {
throw new Error("Maximum request limit cannot be negative");
}
}
/**
* Get the complete configuration
*/
getConfiguration() {
return { ...this.config };
}
/**
* Update AI provider configuration
*/
updateAIConfig(updates) {
if (updates.currentProvider !== void 0) {
if (updates.currentProvider && !this.config.aiProviders[updates.currentProvider]) {
throw new Error(`AI provider '${updates.currentProvider}' is not registered`);
}
this.config.currentProvider = updates.currentProvider;
}
if (updates.currentModel !== void 0) {
this.config.currentModel = updates.currentModel;
}
if (updates.temperature !== void 0) {
this.config.temperature = updates.temperature;
}
if (updates.maxTokens !== void 0) {
this.config.maxTokens = updates.maxTokens;
}
}
/**
* Update system configuration
*/
updateSystemConfig(updates) {
if (updates.systemPrompt !== void 0) {
this.config.systemPrompt = updates.systemPrompt;
}
if (updates.systemMessages !== void 0) {
this.config.systemMessages = updates.systemMessages;
}
if (updates.debug !== void 0) {
this.config.debug = updates.debug;
}
}
/**
* Update limit configuration
*/
updateLimits(updates) {
if (updates.maxTokenLimit !== void 0) {
if (updates.maxTokenLimit < 0) {
throw new Error("Maximum token limit cannot be negative");
}
this.config.maxTokenLimit = updates.maxTokenLimit;
}
if (updates.maxRequestLimit !== void 0) {
if (updates.maxRequestLimit < 0) {
throw new Error("Maximum request limit cannot be negative");
}
this.config.maxRequestLimit = updates.maxRequestLimit;
}
}
/**
* Add AI provider
*/
addAIProvider(name, provider) {
this.config.aiProviders[name] = provider;
}
/**
* Remove AI provider
*/
removeAIProvider(name) {
if (this.config.currentProvider === name) {
this.config.currentProvider = void 0;
this.config.currentModel = void 0;
}
delete this.config.aiProviders[name];
}
/**
* Add tool provider
*/
addToolProvider(provider) {
this.config.toolProviders.push(provider);
}
/**
* Remove tool provider
*/
removeToolProvider(provider) {
const index = this.config.toolProviders.indexOf(provider);
if (index !== -1) {
this.config.toolProviders.splice(index, 1);
}
}
/**
* Set tool call callback
*/
setToolCallCallback(callback) {
this.config.onToolCall = callback;
}
/**
* Check if AI is properly configured
*/
isAIConfigured() {
return !!(this.config.currentProvider && this.config.currentModel && this.config.aiProviders[this.config.currentProvider]);
}
};
// src/robota.ts
var Robota = class {
/** @internal Configuration manager handling all settings */
configManager;
/** @internal Service handling execution logic */
executionService;
/** @internal Conversation service for high-level operations */
conversationService;
/** @internal Token analysis utilities */
tokenAnalyzer;
/** @internal Conversation history storage */
conversationHistory;
/** @internal Optional tool call callback */
onToolCall;
/** @internal Logger instance */
logger;
/** @internal Debug mode flag */
debug;
// === FACADE PATTERN: Exposed Managers ===
/**
* AI provider management - register providers, set current provider/model, configure parameters
*
* @see {@link AIProviderManager}
*/
ai;
/**
* System message management - set prompts, manage system instructions
*
* @see {@link SystemMessageManager}
*/
system;
/**
* Function/tool call management - configure modes, timeouts, allowed functions
*
* @see {@link FunctionCallManager}
*/
functions;
/**
* Analytics and metrics - track usage, performance, costs
*
* @see {@link AnalyticsManager}
*/
analytics;
/**
* Tool provider management - register and manage tool providers
*
* @see {@link ToolProviderManager}
*/
tools;
/**
* Request and token limits - control usage and costs
*
* @see {@link RequestLimitManager}
*/
limits;
/**
* Conversation history - direct access to conversation management
*
* @see {@link ConversationHistory}
*/
conversation;
constructor(options = {}) {
this.logger = options.logger || console;
this.debug = options.debug || false;
this.onToolCall = options.onToolCall;
this.conversationHistory = options.conversationHistory || new SimpleConversationHistory();
this.conversation = this.conversationHistory;
this.ai = new AIProviderManager();
this.system = new SystemMessageManager();
this.functions = new FunctionCallManager();
this.analytics = new AnalyticsManager();
this.tools = new ToolProviderManager(this.logger);
this.limits = new RequestLimitManager();
this.tokenAnalyzer = new TokenAnalyzer();
this.conversationService = new ConversationService(
options.temperature,
options.maxTokens,
this.logger,
this.debug
);
this.applyConfiguration(options);
this.configManager = new RobotaConfigManager({
aiProviders: options.aiProviders || {},
currentProvider: options.currentProvider,
currentModel: options.currentModel,
temperature: options.temperature,
maxTokens: options.maxTokens,
debug: this.debug,
maxTokenLimit: options.maxTokenLimit || 4096,
maxRequestLimit: options.maxRequestLimit || 25
});
this.executionService = new ExecutionService(
this.ai,
this.tools,
this.limits,
this.analytics,
this.tokenAnalyzer,
this.conversationService,
this.logger,
this.debug,
this.onToolCall
);
}
/**
* Apply configuration from options
* @internal
*/
applyConfiguration(options) {
if (options.aiProviders) {
for (const [name, provider] of Object.entries(options.aiProviders)) {
this.ai.addProvider(name, provider);
}
}
if (options.currentProvider && options.currentModel) {
this.ai.setCurrentAI(options.currentProvider, options.currentModel);
}
if (options.toolProviders) {
for (const provider of options.toolProviders) {
this.tools.addProvider(provider);
}
}
if (options.provider) {
this.tools.addProvider(options.provider);
}
if (options.systemPrompt) {
this.system.setSystemPrompt(options.systemPrompt);
this.conversationHistory.addSystemMessage(options.systemPrompt);
}
if (options.systemMessages) {
this.system.setSystemMessages(options.systemMessages);
for (const msg of options.systemMessages) {
if (msg.role === "system") {
this.conversationHistory.addSystemMessage(msg.content);
}
}
}
if (options.functionCallConfig) {
this.functions.configure(options.functionCallConfig);
}
if (options.maxTokenLimit) {
this.limits.setMaxTokens(options.maxTokenLimit);
}
if (options.maxRequestLimit) {
this.limits.setMaxRequests(options.maxRequestLimit);
}
}
// ============================================================
// Core Execution Methods
// ============================================================
/**
* Execute a prompt and get a text response
*
* Main method for running AI conversations. Handles the full pipeline:
* - Context preparation with conversation history
* - AI provider execution with tool support
* - Response processing and history updates
* - Analytics and limit tracking
*
* @param prompt - The user prompt to process
* @param options - Optional configuration for this specific request
* @returns Promise resolving to the AI's text response
*
* @throws {Error} When no AI provider is configured
* @throws {Error} When rate limits are exceeded
* @throws {Error} When AI provider fails
*
* @see {@link ../../../apps/examples/01-basic/01-simple-conversation.ts | Basic Usage}
*/
async run(prompt, options = {}) {
return this.executionService.executePrompt(prompt, {
conversationHistory: this.conversationHistory,
systemMessageManager: this.system,
options
});
}
/**
* Execute a prompt and get a streaming response
*
* Similar to run() but returns an async iterator for streaming responses.
* Useful for real-time display of AI responses.
*
* @param prompt - The user prompt to process
* @param options - Optional configuration for this specific request
* @returns Promise resolving to an async iterator of response chunks
*
* @throws {Error} When no AI provider is configured
* @throws {Error} When rate limits are exceeded
* @throws {Error} When AI provider fails
*
* @see {@link ../../../apps/examples/01-basic/01-simple-conversation.ts | Streaming Example}
*/
async runStream(prompt, options = {}) {
return this.executionService.executeStream(prompt, {
conversationHistory: this.conversationHistory,
systemMessageManager: this.system,
options
});
}
/**
* Call a specific tool directly by name
*
* Bypasses AI provider and calls a tool directly with provided parameters.
* Useful for testing tools or direct tool execution.