@astreus-ai/astreus
Version:
Open-source AI agent framework for building autonomous systems that solve real-world tasks effectively.
1,520 lines (1,508 loc) • 615 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
Agent: () => Agent,
Context: () => ContextManager,
ContextCompressor: () => ContextCompressor,
ContextManager: () => ContextManager,
Graph: () => Graph,
Knowledge: () => Knowledge,
Memory: () => Memory,
Plugin: () => Plugin,
SubAgent: () => SubAgent,
Task: () => Task,
clearLLMInstances: () => clearLLMInstances,
default: () => agent_default,
getDatabase: () => getDatabase,
getLLM: () => getLLM,
getLogger: () => getLogger,
getMCP: () => getMCP,
getPlugin: () => getPlugin,
initializeLogger: () => initializeLogger,
knowledgeSearchTool: () => knowledgeSearchTool,
knowledgeTools: () => knowledgeTools,
parseScheduleString: () => parseScheduleString,
resetLogger: () => resetLogger,
shutdownLogger: () => shutdownLogger
});
module.exports = __toCommonJS(index_exports);
// src/agent/defaults.ts
var DEFAULT_AGENT_CONFIG = {
model: "gpt-4o-mini",
temperature: 0.7,
maxTokens: 2e3,
useTools: true,
memory: false,
knowledge: false,
vision: false,
autoContextCompression: false,
debug: false
};
// src/plugin/defaults.ts
var DEFAULT_PLUGIN_CONFIG = {
defaultVersion: "unknown",
defaultDescription: "none",
missingName: "[missing]",
defaultTimeout: 3e4
// 30 seconds
};
// src/errors/index.ts
var _AstreusError = class _AstreusError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = "AstreusError";
Object.setPrototypeOf(this, new.target.prototype);
}
/**
* Get the full error chain as a string for logging
*/
getFullStack() {
let fullStack = this.stack || this.message;
if (this.cause instanceof Error) {
fullStack += `
Caused by: ${this.cause.stack || this.cause.message}`;
}
return fullStack;
}
};
__name(_AstreusError, "AstreusError");
var AstreusError = _AstreusError;
var _LLMApiError = class _LLMApiError extends AstreusError {
constructor(message, provider, cause) {
super(message, cause);
this.provider = provider;
this.name = "LLMApiError";
}
};
__name(_LLMApiError, "LLMApiError");
var LLMApiError = _LLMApiError;
var _SubAgentError = class _SubAgentError extends AstreusError {
constructor(message, agentId, cause) {
super(message, cause);
this.agentId = agentId;
this.name = "SubAgentError";
}
};
__name(_SubAgentError, "SubAgentError");
var SubAgentError = _SubAgentError;
var _VisionError = class _VisionError extends AstreusError {
constructor(message, provider, cause) {
super(message, cause);
this.provider = provider;
this.name = "VisionError";
}
};
__name(_VisionError, "VisionError");
var VisionError = _VisionError;
var _GraphNodeError = class _GraphNodeError extends AstreusError {
constructor(message, nodeId, nodeName, step, graphId, parentNodeId, cause) {
super(message, cause);
this.nodeId = nodeId;
this.nodeName = nodeName;
this.step = step;
this.graphId = graphId;
this.parentNodeId = parentNodeId;
this.name = "GraphNodeError";
}
/**
* Get error chain information for debugging
*/
getErrorChain() {
return {
nodeId: this.nodeId,
nodeName: this.nodeName,
step: this.step,
graphId: this.graphId,
parentNodeId: this.parentNodeId,
message: this.message,
cause: this.cause instanceof Error ? this.cause.message : void 0
};
}
};
__name(_GraphNodeError, "GraphNodeError");
var GraphNodeError = _GraphNodeError;
var _ToolError = class _ToolError extends AstreusError {
constructor(message, toolName, toolType, errorType, recoverable = true, cause) {
super(message, cause);
this.toolName = toolName;
this.toolType = toolType;
this.errorType = errorType;
this.recoverable = recoverable;
this.name = "ToolError";
}
/**
* Get normalized error response for LLM
*/
toToolResult() {
return {
success: false,
error: this.message,
recoverable: this.recoverable,
toolType: this.toolType
};
}
};
__name(_ToolError, "ToolError");
var ToolError = _ToolError;
// src/plugin/index.ts
var _Plugin = class _Plugin {
// Lock for preventing race conditions
constructor(agent) {
this.agent = agent;
this.name = "plugin";
this.plugins = /* @__PURE__ */ new Map();
this.configs = /* @__PURE__ */ new Map();
this.tools = /* @__PURE__ */ new Map();
this.registrationLock = /* @__PURE__ */ new Set();
this.logger = agent.logger;
this.logger.info("Plugin manager initialized");
this.logger.debug("Plugin manager initialized", {
agentId: agent.id,
agentName: agent.name
});
}
async initialize() {
this.logger.info("Plugin manager ready");
this.logger.debug("Plugin manager initialization completed");
}
async registerPlugin(plugin, config) {
this.logger.info(`Registering plugin: ${plugin.name}`);
this.logger.debug("Registering plugin", {
name: plugin.name,
version: plugin.version,
description: plugin.description,
toolCount: plugin.tools.length,
toolNames: plugin.tools.map((t) => t.name),
hasConfig: !!config,
hasInitialize: !!plugin.initialize,
hasCleanup: !!plugin.cleanup
});
if (this.plugins.has(plugin.name)) {
this.logger.error(`Plugin already registered: ${plugin.name}`);
this.logger.debug("Plugin registration failed - already exists", {
pluginName: plugin.name,
existingPlugins: Array.from(this.plugins.keys())
});
throw new Error(`Plugin '${plugin.name}' is already registered`);
}
this.logger.debug("Validating plugin structure", { pluginName: plugin.name });
this.validatePlugin(plugin);
const pluginConfig = config || {
name: plugin.name,
enabled: true
};
this.logger.debug("Plugin config prepared", {
pluginName: plugin.name,
enabled: pluginConfig.enabled,
hasCustomConfig: !!pluginConfig.config
});
if (plugin.initialize) {
this.logger.debug("Initializing plugin", { pluginName: plugin.name });
await plugin.initialize(pluginConfig.config);
this.logger.debug("Plugin initialized successfully", { pluginName: plugin.name });
}
this.plugins.set(plugin.name, plugin);
this.configs.set(plugin.name, pluginConfig);
if (pluginConfig.enabled) {
this.logger.debug("Registering plugin tools", {
pluginName: plugin.name,
toolCount: plugin.tools.length
});
for (const tool of plugin.tools) {
const maxWaitTime = 5e3;
const startWait = Date.now();
while (this.registrationLock.has(tool.name)) {
if (Date.now() - startWait > maxWaitTime) {
this.logger.error(`Tool registration lock timeout: ${tool.name}`);
throw new Error(`Tool registration timeout for '${tool.name}': lock held for too long`);
}
await new Promise((r) => setTimeout(r, 10));
}
this.registrationLock.add(tool.name);
try {
if (this.tools.has(tool.name)) {
this.logger.error(`Tool name conflict: ${tool.name}`);
this.logger.debug("Tool registration failed - name conflict", {
toolName: tool.name,
pluginName: plugin.name,
existingTools: Array.from(this.tools.keys())
});
throw new Error(`Tool '${tool.name}' is already registered by another plugin`);
}
this.tools.set(tool.name, tool);
this.logger.debug("Tool registered", {
toolName: tool.name,
pluginName: plugin.name,
description: tool.description
});
} finally {
this.registrationLock.delete(tool.name);
}
}
} else {
this.logger.debug("Plugin disabled, tools not registered", {
pluginName: plugin.name,
toolCount: plugin.tools.length
});
}
this.logger.info(`Plugin registered: ${plugin.name} (${plugin.tools.length} tools)`);
this.logger.debug("Plugin registered successfully", {
pluginName: plugin.name,
version: plugin.version,
toolsRegistered: pluginConfig.enabled ? plugin.tools.length : 0,
totalPlugins: this.plugins.size,
totalTools: this.tools.size
});
}
async unregisterPlugin(name) {
this.logger.info(`Unregistering plugin: ${name}`);
this.logger.debug("Unregistering plugin", {
pluginName: name,
isRegistered: this.plugins.has(name)
});
const plugin = this.plugins.get(name);
if (!plugin) {
this.logger.error(`Plugin not found: ${name}`);
this.logger.debug("Plugin unregistration failed - not found", {
pluginName: name,
availablePlugins: Array.from(this.plugins.keys())
});
throw new Error(`Plugin '${name}' is not registered`);
}
const removedTools = [];
for (const tool of plugin.tools) {
if (this.tools.has(tool.name)) {
this.tools.delete(tool.name);
removedTools.push(tool.name);
this.logger.debug("Tool unregistered", {
toolName: tool.name,
pluginName: name
});
}
}
this.logger.debug("Plugin tools removed", {
pluginName: name,
removedTools,
removedCount: removedTools.length
});
if (plugin.cleanup) {
this.logger.debug("Running plugin cleanup", { pluginName: name });
await plugin.cleanup();
this.logger.debug("Plugin cleanup completed", { pluginName: name });
}
this.plugins.delete(name);
this.configs.delete(name);
this.logger.info(`Plugin unregistered: ${name}`);
this.logger.debug("Plugin unregistered successfully", {
pluginName: name,
removedToolCount: removedTools.length,
remainingPlugins: this.plugins.size,
remainingTools: this.tools.size
});
}
getPlugin(name) {
const plugin = this.plugins.get(name);
this.logger.debug("Plugin lookup", {
pluginName: name,
found: !!plugin,
version: plugin?.version ?? DEFAULT_PLUGIN_CONFIG.defaultVersion
});
return plugin;
}
getTools() {
const tools = Array.from(this.tools.values());
this.logger.debug("Retrieved all tools", {
toolCount: tools.length,
toolNames: tools.map((t) => t.name)
});
return tools;
}
getTool(name) {
const tool = this.tools.get(name);
this.logger.debug("Tool lookup", {
toolName: name,
found: !!tool,
description: tool?.description ?? DEFAULT_PLUGIN_CONFIG.defaultDescription
});
return tool;
}
async executeTool(toolCall, context) {
const startTime = Date.now();
this.logger.info(`Executing tool: ${toolCall.name}`);
this.logger.debug("Executing tool", {
toolName: toolCall.name,
callId: toolCall.id,
parameters: Object.keys(toolCall.parameters),
parameterCount: Object.keys(toolCall.parameters).length,
hasContext: !!context
});
try {
const tool = this.getTool(toolCall.name);
if (!tool) {
this.logger.error(`Tool not found: ${toolCall.name}`);
this.logger.debug("Tool execution failed - tool not found", {
toolName: toolCall.name,
callId: toolCall.id,
availableTools: Array.from(this.tools.keys())
});
return {
id: toolCall.id,
name: toolCall.name,
result: {
success: false,
error: `Tool '${toolCall.name}' not found`
},
executionTime: Date.now() - startTime
};
}
this.logger.debug("Validating tool parameters", {
toolName: toolCall.name,
callId: toolCall.id
});
const validationError = this.validateToolParameters(tool, toolCall.parameters);
if (validationError) {
this.logger.error(`Tool parameter validation failed: ${toolCall.name}`);
this.logger.debug("Tool parameter validation error", {
toolName: toolCall.name,
callId: toolCall.id,
validationError,
parameters: toolCall.parameters
});
return {
id: toolCall.id,
name: toolCall.name,
result: {
success: false,
error: validationError
},
executionTime: Date.now() - startTime
};
}
if (!this.agent || typeof this.agent.id !== "string") {
this.logger.error("Agent not available for tool execution");
return {
id: toolCall.id,
name: toolCall.name,
result: {
success: false,
error: "Agent not available for tool execution"
},
executionTime: Date.now() - startTime
};
}
const isolatedContext = {
// Copy existing context properties if provided
...context || {},
// Add execution isolation metadata
agentId: context?.agentId || this.agent.id,
agent: context?.agent || this.agent,
// Add unique execution ID for this specific call
executionId: `${toolCall.id}-${Date.now()}`,
// Isolate any shared state by creating fresh copies
toolName: toolCall.name,
callTimestamp: /* @__PURE__ */ new Date()
};
this.logger.debug("Calling tool handler with isolated context", {
toolName: toolCall.name,
callId: toolCall.id,
timeout: DEFAULT_PLUGIN_CONFIG.defaultTimeout,
executionId: isolatedContext.executionId ?? null
});
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(
new Error(
`Tool '${toolCall.name}' execution timed out after ${DEFAULT_PLUGIN_CONFIG.defaultTimeout}ms`
)
);
}, DEFAULT_PLUGIN_CONFIG.defaultTimeout);
});
let result;
try {
result = await Promise.race([
tool.handler(toolCall.parameters, isolatedContext),
timeoutPromise
]);
clearTimeout(timeoutId);
} catch (raceError) {
clearTimeout(timeoutId);
throw raceError;
}
const executionTime = Date.now() - startTime;
this.logger.info(`Tool completed: ${toolCall.name} (${executionTime}ms)`);
this.logger.debug("Tool execution successful", {
toolName: toolCall.name,
callId: toolCall.id,
executionTime,
success: result.success,
hasData: !!result.data,
hasError: !!result.error,
executionId: isolatedContext.executionId ?? null
});
return {
id: toolCall.id,
name: toolCall.name,
result,
executionTime
};
} catch (error) {
const executionTime = Date.now() - startTime;
const originalError = error instanceof Error ? error : new Error("Unknown error occurred");
let errorType = "execution";
if (originalError.message.includes("timed out") || originalError.message.includes("timeout")) {
errorType = "timeout";
} else if (originalError.message.includes("not found") || originalError.message.includes("not available")) {
errorType = "not_found";
} else if (originalError.message.includes("Invalid") || originalError.message.includes("validation")) {
errorType = "validation";
}
const recoverable = errorType !== "not_found";
const toolError = new ToolError(
`Plugin tool '${toolCall.name}' failed: ${originalError.message}`,
toolCall.name,
"plugin",
errorType,
recoverable,
originalError
);
this.logger.error(`Tool execution failed: ${toolCall.name}`, toolError);
this.logger.debug("Tool execution error", {
toolName: toolCall.name,
callId: toolCall.id,
executionTime,
errorType,
recoverable,
error: originalError.message,
hasStack: !!originalError.stack
});
const errorResult = toolError.toToolResult();
return {
id: toolCall.id,
name: toolCall.name,
result: {
success: false,
error: errorResult.error
},
executionTime
};
}
}
listPlugins() {
const plugins = Array.from(this.plugins.values());
this.logger.debug("Listed plugins", {
pluginCount: plugins.length,
pluginNames: plugins.map((p) => p.name)
});
return plugins;
}
// Get tools formatted for LLM function calling
getToolsForLLM() {
const tools = this.getTools();
const llmTools = tools.map((tool) => ({
type: "function",
function: {
name: tool.name,
description: tool.description,
parameters: this.convertParametersToJsonSchema(tool.parameters)
}
}));
this.logger.debug("Generated LLM tool schemas", {
toolCount: llmTools.length,
toolNames: llmTools.map((t) => t.function.name)
});
return llmTools;
}
validatePlugin(plugin) {
this.logger.debug("Validating plugin structure", {
pluginName: plugin.name,
hasName: !!plugin.name,
hasVersion: !!plugin.version,
hasTools: Array.isArray(plugin.tools),
toolCount: Array.isArray(plugin.tools) ? plugin.tools.length : 0
});
if (!plugin.name || typeof plugin.name !== "string" || plugin.name.trim().length === 0) {
this.logger.debug("Plugin validation failed - invalid name", {
pluginName: plugin.name ?? DEFAULT_PLUGIN_CONFIG.missingName,
nameType: typeof plugin.name
});
throw new Error("Plugin must have a valid non-empty name");
}
if (!plugin.version || typeof plugin.version !== "string" || plugin.version.trim().length === 0) {
this.logger.debug("Plugin validation failed - invalid version", {
pluginName: plugin.name,
version: plugin.version ?? DEFAULT_PLUGIN_CONFIG.defaultVersion,
versionType: typeof plugin.version
});
throw new Error(`Plugin '${plugin.name}' must have a valid non-empty version`);
}
const namePattern = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
if (!namePattern.test(plugin.name)) {
this.logger.debug("Plugin validation failed - invalid name format", {
pluginName: plugin.name
});
throw new Error(
`Plugin name '${plugin.name}' must start with a letter and contain only alphanumeric characters, hyphens, or underscores`
);
}
if (!Array.isArray(plugin.tools)) {
this.logger.debug("Plugin validation failed - invalid tools array", {
pluginName: plugin.name,
toolsType: typeof plugin.tools,
isArray: Array.isArray(plugin.tools)
});
throw new Error(`Plugin '${plugin.name}' must have tools array`);
}
const toolNames = /* @__PURE__ */ new Set();
for (const tool of plugin.tools) {
if (toolNames.has(tool.name)) {
this.logger.debug("Plugin validation failed - duplicate tool name", {
pluginName: plugin.name,
duplicateToolName: tool.name
});
throw new Error(`Plugin '${plugin.name}' has duplicate tool name '${tool.name}'`);
}
toolNames.add(tool.name);
}
for (const tool of plugin.tools) {
this.logger.debug("Validating tool definition", {
pluginName: plugin.name,
toolName: tool.name,
hasName: !!tool.name,
hasDescription: !!tool.description,
hasHandler: !!tool.handler
});
if (!tool.name || typeof tool.name !== "string" || tool.name.trim().length === 0) {
this.logger.debug("Tool validation failed - invalid name", {
pluginName: plugin.name,
toolName: tool.name ?? DEFAULT_PLUGIN_CONFIG.missingName
});
throw new Error(`Invalid tool name in plugin '${plugin.name}'`);
}
if (!tool.description || typeof tool.description !== "string") {
this.logger.debug("Tool validation failed - invalid description", {
pluginName: plugin.name,
toolName: tool.name
});
throw new Error(
`Tool '${tool.name}' in plugin '${plugin.name}' must have a valid description`
);
}
if (!tool.handler || typeof tool.handler !== "function") {
this.logger.debug("Tool validation failed - invalid handler", {
pluginName: plugin.name,
toolName: tool.name,
handlerType: typeof tool.handler
});
throw new Error(
`Tool '${tool.name}' in plugin '${plugin.name}' must have a valid handler function`
);
}
if (tool.parameters && typeof tool.parameters === "object") {
this.validateToolParameterDefinitions(plugin.name, tool.name, tool.parameters);
}
}
this.logger.debug("Plugin validation successful", {
pluginName: plugin.name,
version: plugin.version,
toolCount: plugin.tools.length
});
}
validateToolParameterDefinitions(pluginName, toolName, parameters, depth = 0) {
const MAX_DEPTH = 50;
if (depth > MAX_DEPTH) {
throw new Error(`Parameter nesting too deep in tool '${toolName}' (max depth: ${MAX_DEPTH})`);
}
const validTypes = ["string", "number", "boolean", "object", "array"];
for (const [paramName, paramDef] of Object.entries(parameters)) {
if (!paramDef.type || !validTypes.includes(paramDef.type)) {
this.logger.debug("Parameter validation failed - invalid type", {
pluginName,
toolName,
paramName,
paramType: paramDef.type,
validTypes
});
throw new Error(
`Parameter '${paramName}' in tool '${toolName}' has invalid type '${paramDef.type}'`
);
}
if (!paramDef.description || typeof paramDef.description !== "string") {
this.logger.debug("Parameter validation failed - missing description", {
pluginName,
toolName,
paramName
});
throw new Error(`Parameter '${paramName}' in tool '${toolName}' must have a description`);
}
if (paramDef.type === "object" && paramDef.properties) {
this.validateToolParameterDefinitions(pluginName, toolName, paramDef.properties, depth + 1);
}
if (paramDef.type === "array" && paramDef.items) {
if (!validTypes.includes(paramDef.items.type)) {
this.logger.debug("Parameter validation failed - invalid array items type", {
pluginName,
toolName,
paramName,
itemsType: paramDef.items.type
});
throw new Error(
`Array parameter '${paramName}' in tool '${toolName}' has invalid items type`
);
}
}
}
}
validateToolParameters(tool, parameters) {
this.logger.debug("Validating tool parameters", {
toolName: tool.name,
expectedParams: Object.keys(tool.parameters),
providedParams: Object.keys(parameters),
parameterCount: Object.keys(parameters).length
});
for (const [paramName, paramDef] of Object.entries(tool.parameters)) {
const value = parameters[paramName];
this.logger.debug("Validating parameter", {
toolName: tool.name,
paramName,
paramType: paramDef.type,
required: !!paramDef.required,
hasValue: value !== void 0 && value !== null,
valueType: typeof value
});
if (paramDef.required && (value === void 0 || value === null)) {
this.logger.debug("Required parameter missing", {
toolName: tool.name,
paramName,
paramType: paramDef.type
});
return `Required parameter '${paramName}' is missing`;
}
if (value !== void 0 && value !== null) {
if (!this.isToolParameterValue(value) || !this.validateParameterType(value, paramDef)) {
this.logger.debug("Parameter type validation failed", {
toolName: tool.name,
paramName,
expectedType: paramDef.type,
actualType: typeof value,
value: String(value).slice(0, 100)
// Truncate long values
});
return `Parameter '${paramName}' has invalid type. Expected ${paramDef.type}`;
}
}
}
this.logger.debug("Tool parameter validation successful", {
toolName: tool.name,
validatedParams: Object.keys(tool.parameters).length
});
return null;
}
isToolParameterValue(value, depth = 0) {
const MAX_DEPTH = 50;
if (depth > MAX_DEPTH) {
this.logger.warn("Maximum nesting depth exceeded for parameter validation", { depth });
return false;
}
if (value === null) return true;
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return true;
}
if (Array.isArray(value)) {
return value.every((item) => this.isToolParameterValue(item, depth + 1));
}
if (typeof value === "object") {
return Object.values(value).every(
(v) => this.isToolParameterValue(v, depth + 1)
);
}
return false;
}
validateParameterType(value, paramDef) {
switch (paramDef.type) {
case "string":
return typeof value === "string";
case "number":
return typeof value === "number";
case "boolean":
return typeof value === "boolean";
case "object":
return typeof value === "object" && value !== null && !Array.isArray(value);
case "array":
return Array.isArray(value);
default:
return false;
}
}
convertParametersToJsonSchema(parameters, depth = 0) {
const MAX_DEPTH = 50;
if (depth > MAX_DEPTH) {
this.logger.warn("Maximum nesting depth exceeded for JSON schema conversion", { depth });
return { type: "object", properties: {}, required: [] };
}
const properties = {};
for (const [name, param] of Object.entries(parameters)) {
properties[name] = {
type: param.type,
description: param.description
};
if (param.enum) {
properties[name].enum = param.enum;
}
if (param.properties) {
properties[name].properties = this.convertParametersToJsonSchema(
param.properties,
depth + 1
).properties;
}
if (param.items) {
properties[name].items = {
type: param.items.type
};
}
}
return {
type: "object",
properties,
required: Object.entries(parameters).filter(([, param]) => param.required).map(([name]) => name)
};
}
};
__name(_Plugin, "Plugin");
var Plugin = _Plugin;
var pluginInstances = /* @__PURE__ */ new Map();
function getPlugin(agent) {
if (!agent) {
throw new Error("Agent required for plugin initialization");
}
const agentId = agent.id;
if (!pluginInstances.has(agentId)) {
pluginInstances.set(agentId, new Plugin(agent));
}
return pluginInstances.get(agentId);
}
__name(getPlugin, "getPlugin");
async function cleanupPluginForAgent(agentId) {
const plugin = pluginInstances.get(agentId);
if (plugin) {
const plugins = plugin.listPlugins();
for (const p of plugins) {
await plugin.unregisterPlugin(p.name);
}
pluginInstances.delete(agentId);
}
}
__name(cleanupPluginForAgent, "cleanupPluginForAgent");
async function cleanupPlugin(agentId) {
if (agentId) {
await cleanupPluginForAgent(agentId);
} else {
const agentIds = Array.from(pluginInstances.keys());
for (const id of agentIds) {
await cleanupPluginForAgent(id);
}
}
}
__name(cleanupPlugin, "cleanupPlugin");
function convertToolParametersToJsonSchema(parameters, depth = 0) {
const MAX_DEPTH = 50;
if (depth > MAX_DEPTH) {
return { type: "object", properties: {}, required: [] };
}
const properties = {};
for (const [name, param] of Object.entries(parameters)) {
properties[name] = {
type: param.type,
description: param.description
};
if (param.enum) {
properties[name].enum = param.enum;
}
if (param.properties) {
properties[name].properties = convertToolParametersToJsonSchema(
param.properties,
depth + 1
).properties;
}
if (param.items) {
properties[name].items = {
type: param.items.type
};
}
}
return {
type: "object",
properties,
required: Object.entries(parameters).filter(([, param]) => param.required).map(([name]) => name)
};
}
__name(convertToolParametersToJsonSchema, "convertToolParametersToJsonSchema");
// src/task/index.ts
var import_crypto3 = __toESM(require("crypto"), 1);
// src/database/index.ts
var import_knex = __toESM(require("knex"), 1);
var import_crypto2 = __toESM(require("crypto"), 1);
// src/database/defaults.ts
var DEFAULT_DATABASE_CONFIG = {
defaultTemperature: 0.7,
defaultMaxTokens: 2e3,
defaultModel: "gpt-4o-mini",
defaultBooleanValue: false
};
// src/logger/index.ts
var import_pino = __toESM(require("pino"), 1);
var _Logger = class _Logger {
constructor(config = {}) {
this.transportWorker = null;
const envLevel = process.env.LOG_LEVEL;
const validLevels = ["debug", "info", "warn", "error", "silent", "success"];
const isValidLogLevel = /* @__PURE__ */ __name((level) => level !== void 0 && validLevels.includes(level), "isValidLogLevel");
this.config = {
level: isValidLogLevel(envLevel) ? envLevel : "info",
debug: config.debug ?? false,
enableConsole: true,
enableFile: false,
...config
};
if (envLevel === "silent") {
this.pino = (0, import_pino.default)({ level: "silent" });
return;
}
const isProduction = process.env.NODE_ENV === "production";
const pinoConfig = {
level: this.config.level === "success" ? "info" : this.config.level,
formatters: {
level: /* @__PURE__ */ __name((label) => {
return { level: label };
}, "level")
},
base: {
framework: "Astreus"
},
timestamp: this.config.debug ? import_pino.default.stdTimeFunctions.isoTime : false
};
if (!isProduction && this.config.enableConsole) {
this.pino = (0, import_pino.default)({
...pinoConfig,
transport: {
target: "pino-pretty",
options: {
colorize: true,
translateTime: false,
ignore: this.config.debug ? "pid,hostname,framework,level,time,module,agent,err" : "pid,hostname,framework,level,time,module,agent,data,err",
messageFormat: "\x1B[36mAstreus [\x1B[34m{agent}\x1B[36m] {module}\x1B[0m \u2192 \x1B[32m{msg}\x1B[0m",
customColors: "info:cyan,warn:yellow,error:red",
hideObject: !this.config.debug,
singleLine: false,
messageKey: "msg"
}
}
});
this.transportWorker = this.pino;
} else {
this.pino = (0, import_pino.default)(pinoConfig);
}
}
formatLogObject(message, module2 = "Core", data, error, agentName) {
const logObject = {
msg: message,
module: module2,
framework: "Astreus",
agent: agentName ?? this.config.agentName ?? "System"
};
if (data) {
logObject.data = data;
}
if (error) {
logObject.err = {
message: error.message,
stack: error.stack ?? null,
name: error.name
};
}
return logObject;
}
debug(message, data, agentName) {
const logObj = this.formatLogObject(message, "Core", data, void 0, agentName);
this.pino.debug(logObj);
}
info(message, data, agentName) {
const logObj = this.formatLogObject(message, "Core", data, void 0, agentName);
this.pino.info(logObj);
}
warn(message, data, agentName) {
const logObj = this.formatLogObject(message, "Core", data, void 0, agentName);
this.pino.warn(logObj);
}
error(message, error, data, agentName) {
const logObj = this.formatLogObject(message, "Core", data, error, agentName);
this.pino.error(logObj);
}
success(message, data, agentName) {
const logObj = this.formatLogObject(message, "Core", data, void 0, agentName);
logObj.level = "success";
this.pino.info(logObj);
}
// Add a public log method for custom module names
log(level, message, module2 = "Core", data, error, agentName) {
const logObj = this.formatLogObject(message, module2, data, error, agentName);
switch (level) {
case "debug":
this.pino.debug(logObj);
break;
case "info":
case "success":
this.pino.info(logObj);
break;
case "warn":
this.pino.warn(logObj);
break;
case "error":
this.pino.error(logObj);
break;
case "silent":
break;
default: {
const _exhaustiveCheck = level;
throw new Error(`Unknown log level: ${_exhaustiveCheck}`);
}
}
}
setLevel(level) {
this.config.level = level;
this.pino.level = level === "success" ? "info" : level;
}
setDebug(debug) {
if (this.config.debug === debug) return;
this.cleanupTransportWorker();
this.config.debug = debug;
const isProduction = process.env.NODE_ENV === "production";
const pinoConfig = {
level: this.config.level === "success" ? "info" : this.config.level,
formatters: {
level: /* @__PURE__ */ __name((label) => {
return { level: label };
}, "level")
},
base: {
framework: "Astreus"
},
timestamp: debug ? import_pino.default.stdTimeFunctions.isoTime : false
};
if (!isProduction && this.config.enableConsole) {
this.pino = (0, import_pino.default)({
...pinoConfig,
transport: {
target: "pino-pretty",
options: {
colorize: true,
translateTime: false,
ignore: debug ? "pid,hostname,framework,level,time,module,agent,err" : "pid,hostname,framework,level,time,module,agent,data,err",
messageFormat: "\x1B[36mAstreus [\x1B[34m{agent}\x1B[36m] {module}\x1B[0m \u2192 \x1B[32m{msg}\x1B[0m",
customColors: "info:cyan,warn:yellow,error:red",
hideObject: !debug,
singleLine: false,
messageKey: "msg"
}
}
});
this.transportWorker = this.pino;
} else {
this.pino = (0, import_pino.default)(pinoConfig);
this.transportWorker = null;
}
}
/**
* Cleanup the transport worker to prevent memory leaks
*/
cleanupTransportWorker() {
if (this.transportWorker) {
if (this.transportWorker.flush) {
this.transportWorker.flush();
}
const pinoStreamSymbol = /* @__PURE__ */ Symbol.for("pino.stream");
const transportInstance = this.transportWorker;
const destination = transportInstance[pinoStreamSymbol];
if (destination?.end) {
destination.end();
}
this.transportWorker = null;
}
}
/**
* Flush any pending log entries and cleanup resources.
* Call this before application shutdown.
*/
async flush() {
return new Promise((resolve5, reject) => {
if (this.pino.flush) {
this.pino.flush((err) => {
if (err) {
reject(err);
} else {
resolve5();
}
});
} else {
resolve5();
}
});
}
/**
* Dispose of the logger instance and cleanup resources.
*/
dispose() {
this.cleanupTransportWorker();
if (this.pino.flush) {
this.pino.flush();
}
}
};
__name(_Logger, "Logger");
var Logger = _Logger;
var globalLogger = null;
var _AsyncMutex = class _AsyncMutex {
constructor() {
this.locked = false;
this.queue = [];
}
async acquire() {
return new Promise((resolve5) => {
if (!this.locked) {
this.locked = true;
resolve5();
} else {
this.queue.push(resolve5);
}
});
}
release() {
if (this.queue.length > 0) {
const next = this.queue.shift();
if (next) next();
} else {
this.locked = false;
}
}
};
__name(_AsyncMutex, "AsyncMutex");
var AsyncMutex = _AsyncMutex;
var loggerMutex = new AsyncMutex();
function getLogger(config) {
if (globalLogger) return globalLogger;
if (!globalLogger) {
globalLogger = new Logger({ agentName: "System", ...config });
}
return globalLogger;
}
__name(getLogger, "getLogger");
async function initializeLogger(config) {
await loggerMutex.acquire();
try {
if (globalLogger) {
globalLogger.dispose();
}
globalLogger = new Logger(config);
return globalLogger;
} finally {
loggerMutex.release();
}
}
__name(initializeLogger, "initializeLogger");
async function shutdownLogger() {
const logger = globalLogger;
globalLogger = null;
if (logger) {
await logger.flush();
logger.dispose();
}
}
__name(shutdownLogger, "shutdownLogger");
function resetLogger() {
if (globalLogger) {
globalLogger.dispose();
globalLogger = null;
}
}
__name(resetLogger, "resetLogger");
// src/database/knex.ts
function detectDatabaseType(config) {
if (config.type) {
return config.type;
}
if (config.connectionString) {
if (config.connectionString.startsWith("sqlite://")) {
return "sqlite";
}
if (config.connectionString.startsWith("postgresql://") || config.connectionString.startsWith("postgres://")) {
return "postgres";
}
}
return "sqlite";
}
__name(detectDatabaseType, "detectDatabaseType");
var _ConnectionPoolManager = class _ConnectionPoolManager {
constructor(maxPoolSize, leakThresholdMs = 3e4) {
this.activeConnections = /* @__PURE__ */ new Map();
this.totalAcquired = 0;
this.totalReleased = 0;
this.leakCheckInterval = null;
this.logger = getLogger();
this.maxPoolSize = maxPoolSize;
this.leakThresholdMs = leakThresholdMs;
this.startLeakDetection();
}
static getInstance(maxPoolSize = 10, leakThresholdMs = 3e4) {
if (!_ConnectionPoolManager.instance) {
_ConnectionPoolManager.instance = new _ConnectionPoolManager(maxPoolSize, leakThresholdMs);
}
return _ConnectionPoolManager.instance;
}
/**
* Track when a connection is acquired from the pool
*/
onAcquire(connectionId) {
const id = connectionId || `conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const stack = new Error().stack;
this.activeConnections.set(id, {
acquiredAt: Date.now(),
stack
});
this.totalAcquired++;
if (this.activeConnections.size >= this.maxPoolSize * 0.8) {
this.logger.warn("Connection pool utilization high", {
active: this.activeConnections.size,
max: this.maxPoolSize,
utilization: `${(this.activeConnections.size / this.maxPoolSize * 100).toFixed(1)}%`
});
}
this.logger.debug("Database connection acquired", {
connectionId: id,
activeConnections: this.activeConnections.size,
totalAcquired: this.totalAcquired
});
}
/**
* Track when a connection is released back to the pool
*/
onRelease(connectionId) {
if (!connectionId && this.activeConnections.size > 0) {
const keysIterator = this.activeConnections.keys().next();
if (!keysIterator.done && keysIterator.value !== void 0) {
connectionId = keysIterator.value;
}
}
if (connectionId && this.activeConnections.has(connectionId)) {
this.activeConnections.delete(connectionId);
this.totalReleased++;
this.logger.debug("Database connection released", {
connectionId,
activeConnections: this.activeConnections.size,
totalReleased: this.totalReleased
});
}
}
/**
* Start periodic leak detection
*/
startLeakDetection() {
this.leakCheckInterval = setInterval(() => {
this.detectLeaks();
}, 1e4);
this.leakCheckInterval.unref();
}
/**
* Detect and log potential connection leaks
*/
detectLeaks() {
const now = Date.now();
const leaks = [];
for (const [id, info] of this.activeConnections.entries()) {
const heldForMs = now - info.acquiredAt;
if (heldForMs > this.leakThresholdMs) {
leaks.push({ id, heldForMs, stack: info.stack });
}
}
if (leaks.length > 0) {
const leakDetails = leaks.map((l) => ({
connectionId: l.id,
heldForSeconds: (l.heldForMs / 1e3).toFixed(1),
stackPreview: l.stack?.split("\n").slice(2, 5).join(" -> ") ?? null
}));
this.logger.warn("Potential database connection leaks detected", {
leakCount: leaks.length,
leakSummary: JSON.stringify(leakDetails)
});
}
}
/**
* Check if pool can accept new connections
*/
canAcquire() {
return this.activeConnections.size < this.maxPoolSize;
}
/**
* Get current pool statistics
*/
getStats() {
return {
activeConnections: this.activeConnections.size,
maxPoolSize: this.maxPoolSize,
totalAcquired: this.totalAcquired,
totalReleased: this.totalReleased,
utilization: this.activeConnections.size / this.maxPoolSize
};
}
/**
* Cleanup resources
*/
destroy() {
if (this.leakCheckInterval) {
clearInterval(this.leakCheckInterval);
this.leakCheckInterval = null;
}
this.activeConnections.clear();
_ConnectionPoolManager.instance = null;
}
};
__name(_ConnectionPoolManager, "ConnectionPoolManager");
_ConnectionPoolManager.instance = null;
var ConnectionPoolManager = _ConnectionPoolManager;
var poolManager = null;
function getPoolManager(maxPoolSize = 10) {
if (!poolManager) {
poolManager = ConnectionPoolManager.getInstance(maxPoolSize);
}
return poolManager;
}
__name(getPoolManager, "getPoolManager");
function createKnexConfig(config) {
const dbType = detectDatabaseType(config);
switch (dbType) {
case "sqlite": {
let filename = config.filename || ":memory:";
if (config.connectionString && config.connectionString.startsWith("sqlite://")) {
filename = config.connectionString.replace("sqlite://", "");
}
return {
client: "sqlite3",
connection: {
filename
},
useNullAsDefault: true,
migrations: {
directory: "./migrations"
}
};
}
case "postgres": {
const maxPoolSize = config.maxPoolSize ?? 10;
const minPoolSize = config.minPoolSize ?? 2;
const manager = getPoolManager(maxPoolSize);
if (!config.connectionString) {
throw new Error("PostgreSQL requires DB_URL connection string to be set");
}
return {
client: "pg",
connection: config.connectionString,
pool: {
min: minPoolSize,
max: maxPoolSize,
acquireTimeoutMillis: 3e4,
idleTimeoutMillis: 3e4,
// Track connection creation for pool monitoring
// Note: Knex/tarn pool only supports afterCreate hook natively
// Release tracking is handled via periodic leak detection in ConnectionPoolManager
// The leak detection timer (every 10 seconds) will identify connections held too long
afterCreate: /* @__PURE__ */ __name((conn, done) => {
manager.onAcquire();
done(null, conn);
}, "afterCreate")
},
migrations: {
directory: "./migrations"
}
};
}
default:
throw new Error(`Unsupported database type: ${dbType}`);
}
}
__name(createKnexConfig, "createKnexConfig");
// src/database/encryption.ts
var import_crypto = require("crypto");
var import_util = require("util");
var scryptAsync = (0, import_util.promisify)(import_crypto.scrypt);
var pbkdf2Async = (0, import_util.promisify)(import_crypto.pbkdf2);
var _EncryptionService = class _EncryptionService {
// OWASP recommended minimum
constructor(config) {
this.keyCache = /* @__PURE__ */ new Map();
// Prevent unbounded memory growth
// Current encryption version for future key rotation support
this.CURRENT_VERSION = 1;
this.ALGORITHM = "aes-256-gcm";
this.IV_LENGTH = 12;
// 12 bytes for GCM
this.TAG_LENGTH = 16;
// 16 bytes for GCM auth tag
this.SALT_LENGTH = 32;
// 32 bytes for salt
this.PBKDF2_ITERATIONS = 1e5;
this.config = config;
if (config.enabled && !config.masterKey) {
throw new Error("ENCRYPTION_MASTER_KEY is required when encryption is enabled");
}
if (config.enabled && config.masterKey.length < 32) {
throw new Error("ENCRYPTION_MASTER_KEY must be at least 32 characters long");
}
}
/**
* Check if encryption is enabled
*/
isEnabled() {
return this.config.enabled;
}
/**
* Derive a field-specific encryption key using secure salt generation
* Uses LRU (Least Recently Used) cache eviction strategy
*/
async deriveKey(fieldName) {
const cacheKey = `${fieldName}_v${this.CURRENT_VERSION}`;
if (this.keyCache.has(cacheKey)) {
const cachedKey = this.keyCache.get(cacheKey);
this.keyCache.delete(cacheKey);
this.keyCache.set(cacheKey, cachedKey);
return cachedKey;
}
const fieldBuffer = Buffer.from(fieldName, "utf8");
const versionBuffer = Buffer.from(this.CURRENT_VERSION.toString(), "utf8");
const contextBuffer = Buffer.concat([fieldBuffer, versionBuffer]);
const salt = await pbkdf2Async(
this.config.masterKey,
contextBuffer,
this.PBKDF2_ITERATIONS,
this.SALT_LENGTH,
"sha256"
);
const derivedKey = await scryptAsync(this.config.masterKey, salt, 32);
if (this.keyCache.size >= _EncryptionService.MAX_CACHE_SIZE) {
const keysIterator = this.keyCache.keys().next();
if (!keysIterator.done && keysIterator.value !== void 0) {
this.keyCache.delete(keysIterator.value);
}
}
this.keyCache.set(cacheKey, derivedKey);
return derivedKey;
}
/**
* Encrypt a string value
*/
async encrypt(value, fieldName) {
if (!this.config.enabled) {
return value;
}
if (value === null || value === void 0 || value === "") {
return value;
}
if (this.isEncrypted(value)) {
return value;
}
try {
const key = await this.deriveKey(fieldName);
const iv = (0, import_crypto.randomBytes)(this.IV_LENGTH);
const cipher = (0, import_crypto.createCipheriv)(this.ALGORITHM, key, iv);
let encrypted = cipher.update(value, "utf8", "base64");
encrypted += cipher.final("base64");
const tag = cipher.getAuthTag();
const encryptedData = {
iv: iv.toString("base64"),
encrypted,
tag: tag.toString("base64"),
version: this.CURRENT_VERSION
};
return `enc:${encryptedData.version}:${encryptedData.iv}:${encryptedData.encrypted}:${encryptedData.tag}`;
} catch (error) {
throw new Error(
`Encryption failed for field ${fieldName}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Decrypt a string value
*/
async decrypt(value, fieldName) {
if (!this.config.enabled) {
return value;
}
if (value === null || value === void 0 || value === "") {
return value;
}
if (!this.isEncrypted(value)) {
return value;
}
try {
const encryptedData = this.parseEncryptedData(value);
const key = await this.deriveKey(fieldName);
const decipher = (0, import_crypto.createDecipheriv)(
this.ALGORITHM,
key,
Buffer.from(encryptedData.iv, "base64")
);
decipher.setAuthTag(Buffer.from(encryptedData.tag, "base64"));
let decrypted = decipher.update(encryptedData.encrypted, "base64", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
} catch (error) {
throw new Error(
`Decryption failed for field ${fieldName}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Check if a value appears to be encrypted
*/
isEncrypted(value) {
if (typeof value !== "string") return false;
return value.startsWith("enc:") && value.split(":").length === 5;
}
/**
* Parse encrypted data string into components
*/
parseEncryptedData(encryptedString) {
const parts = encryptedString.split(":");
if (parts.length !== 5 || parts[0] !== "enc") {
throw new Error("Invalid encrypted data format");
}
return {
version: parseInt(parts[1], 10),