Compare commits

...
6 Commits
39 changed files with 2312 additions and 305 deletions

No files matched your search

+15 -6
View File
@@ -185,6 +185,7 @@ Methods:
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">run</a>(**kwargs) -> Any</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">clear</a>() -> None</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">discover_plugins</a>() -> int</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">discover_single_file_plugins</a>() -> int</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get</a>(name: str) -> Optional[Union[BaseTool, Callable]]</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_all</a>() -> Dict[str, Union[BaseTool, Callable]]</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">list_base_tools</a>() -> List[BaseTool]</code>
@@ -667,7 +668,7 @@ Methods:
Types:
```python
from praisonaiagents import AgentFlow, AgentManager, AgentTeam, EmbeddingResult, aembedding, aembeddings, embedding, embeddings, get_dimensions
from praisonaiagents import AgentAppConfig, AgentAppProtocol, AgentFlow, AgentManager, AgentOSConfig, AgentOSProtocol, AgentTeam, EmbeddingResult, aembedding, aembeddings, embedding, embeddings, get_dimensions
```
Methods:
@@ -690,6 +691,8 @@ Methods:
* <code title="class AgentFlow">AgentFlow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">to_dict</a>() -> Dict[str, Any]</code>
* <code title="class AgentFlow">AgentFlow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">verbose</a>() -> bool</code>
* <code title="class AgentFlow">AgentFlow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">verbose</a>(value: bool)</code>
* <code title="class AgentOSProtocol">AgentOSProtocol.<a href="./src/praisonai-agents/praisonaiagents/app/protocols.py">get_app</a>() -> Any</code>
* <code title="class AgentOSProtocol">AgentOSProtocol.<a href="./src/praisonai-agents/praisonaiagents/app/protocols.py">serve</a>(host: Optional[str] = None, port: Optional[int] = None, reload: bool = False, **kwargs: Any) -> None</code>
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">add_task</a>(task)</code>
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">aexecute_task</a>(task_id)</code>
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">append_to_state</a>(key: str, value: Any, max_length: Optional[int] = None) -> List[Any]</code>
@@ -739,7 +742,7 @@ Methods:
Types:
```python
from praisonai import AgentOS, CloudProvider, Deploy, DeployConfig, DeployType, PraisonAI, __version__
from praisonai import AgentApp, AgentOS, CloudProvider, Deploy, DeployConfig, DeployType, PraisonAI, __version__
```
# CLI
@@ -881,12 +884,16 @@ Methods:
* <code title="cli">praisonai package install <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
* <code title="cli">praisonai package list <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
* <code title="cli">praisonai package uninstall <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
* <code title="cli">praisonai plugins create <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins disable <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins discover <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins doctor <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins enable <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins info <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins install <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins list <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins plugins-callback <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins remove <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai profile imports <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
* <code title="cli">praisonai profile optimize <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
* <code title="cli">praisonai profile profile-callback <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
@@ -1014,8 +1021,8 @@ Methods:
Types/Exports:
```ts
export { Agent, Agents, PraisonAIAgents, Router } from "./agent";
export type { PraisonAIAgentsConfig, SimpleAgentConfig, SimpleRouteConfig, SimpleRouterConfig } from "./agent";
export { Agent, AgentTeam, Agents, PraisonAIAgents, Router } from "./agent";
export type { AgentTeamConfig, PraisonAIAgentsConfig, SimpleAgentConfig, SimpleRouteConfig, SimpleRouterConfig } from "./agent";
export { AudioAgent, createAudioAgent } from "./agent/audio";
export type { AudioAgentConfig, AudioProvider, AudioSpeakOptions, AudioSpeakResult, AudioTranscribeOptions, AudioTranscribeResult } from "./agent/audio";
export { ContextAgent, createContextAgent } from "./agent/context";
@@ -1040,7 +1047,7 @@ export { // Agent loop
ApprovalManager, // Tools
defineTool, // UI Message (AI SDK v6 parity)
convertToModelMessages, AIAgentStep, AIEmbedManyResult, AIEmbedOptions, AIEmbedResult, AIFilePart, AIGenerateImageOptions, AIGenerateImageResult, AIGenerateObjectOptions, AIGenerateObjectResult, AIGenerateTextOptions, AIGenerateTextResult, AIImagePart, AIMiddleware, AIMiddlewareConfig, AIModelMessage, AISpan, AISpanKind, AISpanOptions, AISpanStatus, AIStreamObjectOptions, AIStreamObjectResult, AIStreamTextOptions, AIStreamTextResult, AITelemetryEvent, AITelemetrySettings, AITextPart, AIToolDefinition, AITracer, AgentLoop, DANGEROUS_PATTERNS, MCPClientType, MODEL_ALIASES, SPEECH_MODELS, TRANSCRIPTION_MODELS, ToolApprovalDeniedError, ToolApprovalTimeoutError, aiEmbed, aiEmbedMany, aiGenerateImage, aiGenerateObject, aiGenerateText, aiStreamObject, aiStreamText, applyMiddleware, autoEnableDevTools, base64ToUint8Array, clearAICache, clearEvents, closeAllMCPClients, closeMCPClient, convertToUIMessages, createAILoggingMiddleware, createAISpan, createApprovalResponse, createDangerousPatternChecker, createDevToolsMiddleware, createExpressHandler, createFastifyHandler, createFilePart, createHonoHandler, createMultimodalMessage, createNestHandler, createPagesHandler, createPdfPart, createSystemMessage, createTelemetryMiddleware, createTelemetrySettings, createTextMessage, createTextPart, createToolSet, disableAITelemetry, disableDevTools, enableAITelemetry, functionToTool, getAICacheStats, getApprovalManager, getDevToolsState, getDevToolsUrl, getEvents, getMCPClient, getModel, getTelemetrySettings, getToolsNeedingApproval, getTracer, hasModelAlias, hasPendingApprovals, initOpenTelemetry, isDangerous, isDataUrl, isDevToolsEnabled, isTelemetryEnabled, isUrl, listModelAliases, mcpToolsToAITools, parseModel, pipeUIMessageStreamToResponse, recordEvent, resolveModelAlias, safeValidateUIMessages, setApprovalManager, stopAfterSteps, stopWhen, stopWhenNoToolCalls, toMessageContent, toUIMessageStreamResponse, transcribe, uint8ArrayToBase64, validateUIMessages, withApproval, withSpan, wrapModel } from "./ai";
export { AutoAgents, createAutoAgents } from "./auto";
export { AutoAgents, AutoTaskConfig, createAutoAgents } from "./auto";
export { BaseCache, FileCache, MemoryCache, createFileCache, createMemoryCache } from "./cache";
export { CLI_SPEC_VERSION, executeCommand, parseArgs } from "./cli";
export { // Autonomy Mode
@@ -1098,6 +1105,8 @@ export { // Adapters
OBSERVABILITY_TOOLS, // Global adapter management
setObservabilityAdapter, // Types
type SpanKind, ConsoleObservabilityAdapter, MemoryObservabilityAdapter, clearAdapterCache, createConsoleAdapter, createMemoryAdapter, createObservabilityAdapter, getObservabilityAdapter, getObservabilityToolInfo, hasObservabilityToolEnvVar, listObservabilityTools, noopAdapter, resetObservabilityAdapter, trace } from "./observability";
export { AgentApp, AgentAppConfig, AgentAppProtocol, AgentOS, AgentOSConfig, AgentOSProtocol, DEFAULT_AGENTOS_CONFIG, mergeConfig } from "./os";
export type { AgentAppOptions, AgentOSOptions } from "./os";
export { Plan, PlanStep, PlanStorage, PlanningAgent, TaskAgent, TodoItem, TodoList, createPlan, createPlanStorage, createPlanningAgent, createTaskAgent, createTodoList } from "./planning";
export { SkillManager, createSkillManager, parseSkillFile } from "./skills";
export { AgentTelemetry, PerformanceMonitor, TelemetryCollector, TelemetryIntegration, cleanupTelemetry, createAgentTelemetry, createConsoleSink, createHTTPSink, createPerformanceMonitor, createTelemetryIntegration, disableTelemetry, enableTelemetry, getTelemetry } from "./telemetry";
@@ -1109,7 +1118,7 @@ export type { InstallHints, PraisonTool, RedactionHooks, RegisteredTool, ToolCap
export { registerBuiltinTools, tools } from "./tools/tools";
export { // New: Python-parity Loop and Repeat classes
Loop, // Task class
Task, Repeat, Workflow, loop, loopPattern, parallel, repeat, repeatPattern, route } from "./workflows";
Task, AgentFlow, Pipeline, Repeat, Workflow, loop, loopPattern, parallel, repeat, repeatPattern, route } from "./workflows";
export type { LoopConfig, LoopResult, RepeatConfig, RepeatContext, RepeatResult, StepContextConfig, StepExecutionConfig, StepOutputConfig, StepResult, StepRoutingConfig, TaskConfig, WorkflowContext } from "./workflows";
export { createWorkflowFromYAML, loadWorkflowFromFile, parseYAMLWorkflow, validateWorkflowDefinition } from "./workflows/yaml-parser";
```
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.2.0" \
"praisonai>=4.2.2" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.2.0" \
"praisonai>=4.2.2" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.2.0" \
"praisonai>=4.2.2" \
"praisonai[ui]" \
"praisonai[crewai]"
@@ -205,6 +205,7 @@ class AgentTeam:
manager_llm=None,
name: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
llm: Optional[str] = None, # Default LLM for all agents (API consistency)
# Consolidated feature params (agent-centric API)
memory: Optional[Any] = False, # Union[bool, MultiAgentMemoryConfig]
planning: Optional[Any] = False, # Union[bool, MultiAgentPlanningConfig]
@@ -228,6 +229,7 @@ class AgentTeam:
tasks: Optional list of Task instances (auto-generated from agents if None)
process: Execution process type ("sequential", "parallel", "hierarchical")
manager_llm: LLM model for manager agent
llm: Default LLM model for all agents
name: Name for this agent collection
variables: Global variables for substitution
memory: Memory configuration (bool | MultiAgentMemoryConfig)
@@ -243,6 +245,7 @@ class AgentTeam:
reflection: Self-reflection configuration (bool | ReflectionConfig)
"""
# Store new params for propagation to agents
self.llm = llm # Store default LLM for API consistency
self._autonomy = autonomy
self._knowledge = knowledge
self._guardrails = guardrails
@@ -2058,7 +2061,7 @@ Context:
backstory=agent_config.get('backstory'),
llm=agent_config.get('llm'),
tools=agent_config.get('tools'),
verbose=self.verbose >= 1,
output='verbose' if self.verbose >= 1 else 'silent',
memory=self.memory
)
@@ -154,6 +154,7 @@ class AutoAgents(AgentManager):
tasks=tasks,
process=process,
manager_llm=manager_llm or self.llm,
llm=self.llm, # Pass llm to parent for API consistency
output=self._output, # Consolidated output param
execution=self._execution,
hooks=self._hooks,
@@ -29,9 +29,9 @@ def internet_search(query: str, max_results: int = 5, retries: int = MAX_RETRIES
Returns:
List of search results with title, url, and snippet
"""
# Check if duckduckgo_search is installed
if util.find_spec("duckduckgo_search") is None:
error_msg = "DuckDuckGo search is not available. Please install duckduckgo_search package using: pip install duckduckgo_search"
# Check if ddgs is installed
if util.find_spec("ddgs") is None:
error_msg = "DuckDuckGo search is not available. Please install ddgs package using: pip install ddgs"
logging.error(error_msg)
return [{"error": error_msg}]
@@ -39,7 +39,7 @@ def internet_search(query: str, max_results: int = 5, retries: int = MAX_RETRIES
for attempt in range(retries):
try:
# Import only when needed
from duckduckgo_search import DDGS
from ddgs import DDGS
results = []
ddgs = DDGS()
@@ -8,7 +8,7 @@ Search Provider Priority:
2. Brave (requires BRAVE_API_KEY + requests)
3. Exa (requires EXA_API_KEY + exa_py)
4. You.com (requires YDC_API_KEY + youdotcom)
5. DuckDuckGo (requires duckduckgo_search package, no API key)
5. DuckDuckGo (requires ddgs package, no API key)
6. SearxNG (requires requests + running SearxNG instance)
Usage:
@@ -58,8 +58,8 @@ def _check_youdotcom() -> tuple[bool, Optional[str]]:
def _check_duckduckgo() -> tuple[bool, Optional[str]]:
"""Check if DuckDuckGo is available."""
if util.find_spec("duckduckgo_search") is None:
return False, "duckduckgo_search package not installed"
if util.find_spec("ddgs") is None:
return False, "ddgs package not installed"
return True, None
@@ -217,7 +217,7 @@ def _search_brave(query: str, max_results: int = 5, extra_snippets: bool = True)
def _search_duckduckgo(query: str, max_results: int = 5) -> List[Dict[str, Any]]:
"""Search using DuckDuckGo with retry logic."""
import time
from duckduckgo_search import DDGS
from ddgs import DDGS
max_retries = 3
retry_delay = 1.0
@@ -545,7 +545,7 @@ class AgentFlow:
# Default configuration for all steps
default_agent_config: Optional[Dict[str, Any]] = None # Default agent for all steps
default_llm: Optional[str] = None # Default LLM model
llm: Optional[str] = None # Default LLM model (renamed from default_llm for consistency)
# Process type: "sequential" (default) or "hierarchical" (manager-based validation)
process: str = "sequential" # "sequential", "hierarchical"
@@ -746,7 +746,7 @@ class AgentFlow:
"variables": self.variables,
"file_path": self.file_path,
"default_agent_config": self.default_agent_config,
"default_llm": self.default_llm,
"default_llm": self.llm,
"process": self.process,
"manager_llm": self.manager_llm,
"output": {"verbose": self._verbose, "stream": self._stream},
@@ -948,8 +948,8 @@ class AgentFlow:
Dict with 'output' (final result) and 'steps' (all step results)
"""
# Use default LLM if not specified
model = llm or self.default_llm or "gpt-4o-mini"
logger.debug(f"Workflow using model: {model} (llm={llm}, default_llm={self.default_llm})")
model = llm or self.llm or "gpt-4o-mini"
logger.debug(f"Workflow using model: {model} (llm={llm}, default_llm={self.llm})")
# Use workflow verbose setting if not overridden
verbose = verbose or self.verbose
@@ -1247,10 +1247,16 @@ class AgentFlow:
goal=config.get("goal", "Complete the task"),
llm=config.get("llm", model),
tools=step_tools if step_tools else None,
output=self.output, # Propagate output config to child agents
reasoning=self.reasoning,
stream=stream,
context=self.context, # Propagate context management to child agents
output=self.output, # Propagate output config
context=self.context, # Propagate context management
memory=self.memory, # Propagate memory config
knowledge=self.knowledge, # Propagate knowledge/RAG
guardrails=self.guardrails, # Propagate guardrails
web=self.web, # Propagate web search/fetch
reflection=self.reflection, # Propagate self-reflection
autonomy=self.autonomy, # Propagate autonomy settings
caching=self.caching, # Propagate caching config
hooks=self.hooks, # Propagate hooks/callbacks
)
# Substitute variables in action
action = step.action
@@ -1720,7 +1726,6 @@ Create a brief execution plan (2-3 sentences) describing how to best accomplish
role="Workflow Planner",
goal="Create efficient execution plans",
llm=self.planning_llm or model,
verbose=False
)
plan = planner.chat(planning_prompt)
@@ -2090,8 +2095,16 @@ Create a brief execution plan (2-3 sentences) describing how to best accomplish
role=config.get("role", "Assistant"),
goal=config.get("goal", "Complete the task"),
llm=config.get("llm", model),
stream=stream,
context=self.context, # Propagate context management to child agents
output=self.output, # Propagate output config
context=self.context, # Propagate context management
memory=self.memory, # Propagate memory config
knowledge=self.knowledge, # Propagate knowledge/RAG
guardrails=self.guardrails, # Propagate guardrails
web=self.web, # Propagate web search/fetch
reflection=self.reflection, # Propagate self-reflection
autonomy=self.autonomy, # Propagate autonomy settings
caching=self.caching, # Propagate caching config
hooks=self.hooks, # Propagate hooks/callbacks
)
action = normalized.action
for key, value in all_variables.items():
@@ -3371,8 +3384,8 @@ class WorkflowManager:
if isinstance(variables, str):
variables = {}
# Parse workflow-level configuration
default_llm = frontmatter.get("default_llm")
# Parse workflow-level configuration (llm preferred, default_llm for backward compat)
llm = frontmatter.get("llm") or frontmatter.get("default_llm")
planning_enabled = frontmatter.get("planning", False)
planning_llm = frontmatter.get("planning_llm")
memory_config = frontmatter.get("memory_config")
@@ -3402,7 +3415,7 @@ class WorkflowManager:
steps=steps,
variables=variables,
file_path=str(file_path),
default_llm=default_llm,
llm=llm,
planning=planning_config,
memory=memory_cfg,
default_agent_config=default_agent_config
@@ -3701,7 +3714,7 @@ class WorkflowManager:
# Use workflow-level defaults if not provided
if default_llm is None:
default_llm = workflow.default_llm
default_llm = workflow.llm
if planning is False and workflow.planning:
planning = workflow.planning
@@ -4077,7 +4090,7 @@ class WorkflowManager:
# Use workflow-level defaults if not provided
if default_llm is None:
default_llm = workflow.default_llm
default_llm = workflow.llm
if planning is False and workflow.planning:
planning = workflow.planning
@@ -250,7 +250,7 @@ class YAMLWorkflowParser:
workflow_config = data.get('workflow', {})
planning = workflow_config.get('planning', False)
planning_llm = workflow_config.get('planning_llm')
default_llm = workflow_config.get('default_llm')
llm = workflow_config.get('llm') or workflow_config.get('default_llm') # llm preferred, default_llm for backward compat
reasoning = workflow_config.get('reasoning', False)
verbose = workflow_config.get('verbose', False)
memory_config = workflow_config.get('memory_config')
@@ -353,7 +353,7 @@ class YAMLWorkflowParser:
steps=steps,
variables=variables,
planning=planning_value,
default_llm=default_llm,
llm=llm,
process=process, # Process type: sequential or hierarchical
manager_llm=manager_llm, # LLM for manager agent (hierarchical mode)
output=workflow_output, # Pass output mode to Workflow
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "1.2.0"
version = "1.2.2"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
readme = "README.md"
requires-python = ">=3.10"
@@ -130,7 +130,7 @@ class TestPluginDiscovery:
from praisonaiagents.plugins.discovery import discover_plugins
with tempfile.TemporaryDirectory() as tmpdir:
plugins = discover_plugins([tmpdir])
plugins = discover_plugins([tmpdir], include_defaults=False)
assert plugins == []
def test_discover_plugins_with_valid_plugin(self):
@@ -153,7 +153,7 @@ def get_weather(city: str) -> str:
return f"Sunny in {city}"
''')
plugins = discover_plugins([tmpdir])
plugins = discover_plugins([tmpdir], include_defaults=False)
assert len(plugins) == 1
assert plugins[0]["name"] == "Weather Plugin"
@@ -182,7 +182,7 @@ def foo():
pass
''')
plugins = discover_plugins([tmpdir])
plugins = discover_plugins([tmpdir], include_defaults=False)
assert len(plugins) == 1
assert plugins[0]["name"] == "Valid Plugin"
@@ -201,7 +201,7 @@ Version: 1.0.0
"""
''')
plugins = discover_plugins([tmpdir])
plugins = discover_plugins([tmpdir], include_defaults=False)
assert len(plugins) == 0
+1 -1
View File
@@ -2549,7 +2549,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "1.2.0"
version = "1.2.2"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
+1
View File
@@ -5,6 +5,7 @@ const config: Config.InitialOptions = {
testEnvironment: 'node',
roots: ['<rootDir>/tests'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
testPathIgnorePatterns: ['/node_modules/', '/tests/live/'],
moduleFileExtensions: ['ts', 'js', 'json'],
coverageDirectory: 'coverage',
collectCoverageFrom: [
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "praisonai",
"version": "1.5.4",
"version": "1.6.0",
"description": "PraisonAI TypeScript AI Agents Framework - Node.js, npm, and Javascript AI Agents Framework",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -114,4 +114,4 @@
"README.md",
"LICENSE"
]
}
}
+10
View File
@@ -61,6 +61,16 @@ export type {
StepContextConfig, StepOutputConfig, StepExecutionConfig, StepRoutingConfig
} from './workflows';
// AgentOS - Production deployment platform for AI agents
// AgentOS is the primary class (Python parity), AgentApp is a silent alias
export {
AgentOS, AgentApp,
AgentOSConfig, AgentAppConfig,
AgentOSProtocol, AgentAppProtocol,
DEFAULT_AGENTOS_CONFIG, mergeConfig,
} from './os';
export type { AgentOSOptions, AgentAppOptions } from './os';
// Database factory - Python-like db() shortcut
export { db, createDbAdapter, getDefaultDbAdapter, setDefaultDbAdapter } from './db';
export type { DbAdapter, DbConfig, DbMessage, DbRun, DbTrace } from './db';
+24 -9
View File
@@ -238,22 +238,37 @@ export class ProviderRegistry implements IProviderRegistry {
* Constructors have a prototype with constructor
*/
private isLoaderFunction(value: ProviderConstructor | ProviderLoader): boolean {
// If it has a prototype with more than just constructor, it's likely a class
// Check if it's a class by looking at the string representation
const str = value.toString();
// Classes start with 'class ' in their toString
if (str.startsWith('class ')) {
return false;
}
// If it has a prototype with methods (more than just constructor), it's a class
if (value.prototype && Object.getOwnPropertyNames(value.prototype).length > 1) {
return false;
}
// Check if it looks like a class (has 'prototype' that is an object with constructor)
if (value.prototype && value.prototype.constructor === value) {
// Additional check: classes typically have a name and their toString starts with 'class'
const str = value.toString();
if (str.startsWith('class ') || str.includes('function ')) {
return false;
// Jest mock functions and arrow functions are loaders
// They don't have a meaningful prototype or their prototype doesn't match class pattern
if (typeof value === 'function') {
// Arrow functions have no prototype or an empty prototype
if (!value.prototype) {
return true;
}
// Jest mocks have _isMockFunction property
if ((value as any)._isMockFunction) {
return true;
}
// Regular functions that aren't classes are loaders
if (!str.startsWith('class ') && !str.startsWith('function ')) {
return true;
}
}
// Arrow functions and simple functions are loaders
return typeof value === 'function' && !value.prototype?.constructor;
return false;
}
}
+383
View File
@@ -0,0 +1,383 @@
/**
* AgentOS Implementation
*
* Production platform for deploying AI agents as web services.
* Implements the AgentOSProtocol for serving agents via HTTP.
*
* @example Basic Usage
* ```typescript
* import { AgentOS, Agent } from 'praisonai';
*
* const assistant = new Agent({
* name: 'assistant',
* instructions: 'Be helpful'
* });
*
* const app = new AgentOS({
* name: 'My AI App',
* agents: [assistant]
* });
*
* await app.serve({ port: 8000 });
* ```
*
* @example With Teams and Flows
* ```typescript
* const app = new AgentOS({
* name: 'My AI App',
* agents: [assistant],
* teams: [myTeam],
* flows: [myFlow],
* config: { port: 9000, debug: true }
* });
* ```
*/
import type { AgentOSProtocol } from './protocols';
import type { AgentOSConfig } from './config';
import { DEFAULT_AGENTOS_CONFIG, mergeConfig } from './config';
// Type imports for Agent, AgentTeam, AgentFlow (avoid circular deps)
type Agent = {
name?: string;
role?: string;
instructions?: string;
chat: (message: string) => Promise<string>;
};
type AgentTeam = {
agents?: Agent[];
start: () => Promise<string[]>;
};
type AgentFlow = {
name?: string;
run: (input: any) => Promise<any>;
};
/**
* Chat request body
*/
interface ChatRequest {
message: string;
agent_name?: string;
session_id?: string;
}
/**
* Chat response body
*/
interface ChatResponse {
response: string;
agent_name: string;
session_id?: string;
}
/**
* AgentOS constructor options
*/
export interface AgentOSOptions {
/** Name of the application */
name?: string;
/** List of Agent instances to serve */
agents?: any[];
/** List of AgentTeam instances to serve */
teams?: any[];
/** List of AgentFlow instances to serve */
flows?: any[];
/** Server configuration */
config?: AgentOSConfig;
// Backward compatibility aliases
/** @deprecated Use `teams` instead */
managers?: any[];
/** @deprecated Use `flows` instead */
workflows?: any[];
}
/**
* Production platform for deploying AI agents as web services.
*
* AgentOS wraps agents, teams, and flows into a unified HTTP
* application with REST endpoints.
*/
export class AgentOS implements AgentOSProtocol {
/** Application name */
readonly name: string;
/** List of Agent instances */
readonly agents: Agent[];
/** List of AgentTeam instances */
readonly teams: AgentTeam[];
/** List of AgentFlow instances */
readonly flows: AgentFlow[];
/** Merged configuration */
readonly config: Required<Omit<AgentOSConfig, 'metadata'>> & { metadata: Record<string, any> };
/** HTTP server instance (lazy initialized) */
private _server: any = null;
/** Express app instance (lazy initialized) */
private _app: any = null;
/**
* Create a new AgentOS instance.
*
* @param options - AgentOS options
*/
constructor(options: AgentOSOptions = {}) {
this.name = options.name || DEFAULT_AGENTOS_CONFIG.name;
this.agents = options.agents || [];
// Support both new names and legacy aliases
this.teams = options.teams || options.managers || [];
this.flows = options.flows || options.workflows || [];
// Merge config with defaults
this.config = mergeConfig({
name: this.name,
...options.config,
});
}
/**
* Create the Express application.
*
* @returns Express app instance
*/
private _createApp(): any {
// Lazy import express
let express: any;
let cors: any;
try {
express = require('express');
} catch {
throw new Error(
'Express is required for AgentOS. ' +
'Install with: npm install express'
);
}
try {
cors = require('cors');
} catch {
// cors is optional, we'll handle manually if not available
cors = null;
}
const app = express();
// Parse JSON bodies
app.use(express.json());
// Add CORS middleware
if (cors) {
app.use(cors({
origin: this.config.corsOrigins,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
} else {
// Manual CORS handling
app.use((req: any, res: any, next: any) => {
res.header('Access-Control-Allow-Origin', this.config.corsOrigins[0] || '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
}
// Register routes
this._registerRoutes(app);
return app;
}
/**
* Register API routes.
*
* @param app - Express app instance
*/
private _registerRoutes(app: any): void {
const { apiPrefix } = this.config;
// GET / - Root info
app.get('/', (req: any, res: any) => {
res.json({
name: this.name,
status: 'running',
agents: this.agents.map(a => a.name || 'unnamed'),
teams: this.teams.length,
flows: this.flows.length,
});
});
// GET /health - Health check
app.get('/health', (req: any, res: any) => {
res.json({ status: 'healthy' });
});
// GET /api/agents - List agents
app.get(`${apiPrefix}/agents`, (req: any, res: any) => {
res.json({
agents: this.agents.map((agent, i) => ({
name: agent.name || `agent_${i}`,
role: agent.role || null,
instructions: agent.instructions
? (agent.instructions.length > 100
? agent.instructions.substring(0, 100) + '...'
: agent.instructions)
: null,
})),
});
});
// POST /api/chat - Chat with an agent
app.post(`${apiPrefix}/chat`, async (req: any, res: any) => {
try {
const { message, agent_name, session_id }: ChatRequest = req.body;
if (!message) {
return res.status(400).json({ error: 'Message is required' });
}
// Find the agent
let agent: Agent | undefined;
if (agent_name) {
agent = this.agents.find(a => a.name === agent_name);
if (!agent) {
return res.status(404).json({ error: `Agent '${agent_name}' not found` });
}
} else if (this.agents.length > 0) {
agent = this.agents[0];
} else {
return res.status(400).json({ error: 'No agents available' });
}
// Call the agent
const response = await agent.chat(message);
const result: ChatResponse = {
response: String(response),
agent_name: agent.name || 'unknown',
session_id,
};
res.json(result);
} catch (error: any) {
console.error('Chat error:', error);
res.status(500).json({ error: error.message || 'Internal server error' });
}
});
// GET /api/teams - List teams
app.get(`${apiPrefix}/teams`, (req: any, res: any) => {
res.json({
teams: this.teams.map((team, i) => ({
name: `team_${i}`,
agents: team.agents?.length || 0,
})),
});
});
// GET /api/flows - List flows
app.get(`${apiPrefix}/flows`, (req: any, res: any) => {
res.json({
flows: this.flows.map((flow, i) => ({
name: flow.name || `flow_${i}`,
})),
});
});
}
/**
* Get the Express application instance.
*
* @returns The Express application instance for custom mounting or configuration
*/
getApp(): any {
if (!this._app) {
this._app = this._createApp();
}
return this._app;
}
/**
* Start the AgentOS server.
*
* @param options - Server options
* @returns Promise that resolves when server is listening
*/
async serve(options: {
host?: string;
port?: number;
reload?: boolean;
} = {}): Promise<void> {
const host = options.host || this.config.host;
const port = options.port || this.config.port;
const app = this.getApp();
return new Promise((resolve, reject) => {
try {
this._server = app.listen(port, host, () => {
console.log(`🚀 AgentOS "${this.name}" running at http://${host}:${port}`);
console.log(` Agents: ${this.agents.length}`);
console.log(` Teams: ${this.teams.length}`);
console.log(` Flows: ${this.flows.length}`);
console.log(` API: http://${host}:${port}${this.config.apiPrefix}`);
resolve();
});
this._server.on('error', (error: any) => {
reject(error);
});
} catch (error) {
reject(error);
}
});
}
/**
* Stop the AgentOS server.
*
* @returns Promise that resolves when server is stopped
*/
async stop(): Promise<void> {
if (this._server) {
return new Promise((resolve) => {
this._server.close(() => {
this._server = null;
resolve();
});
});
}
}
}
/**
* AgentApp - Silent alias for AgentOS (backward compatibility)
* @deprecated Use AgentOS instead
*/
export const AgentApp = AgentOS;
/**
* AgentAppOptions - Silent alias for AgentOSOptions (backward compatibility)
* @deprecated Use AgentOSOptions instead
*/
export type AgentAppOptions = AgentOSOptions;
+100
View File
@@ -0,0 +1,100 @@
/**
* AgentOS Configuration
*
* This module defines the configuration interface for AgentOS.
* It follows PraisonAI's principle of sensible defaults with explicit overrides.
*
* @example
* ```typescript
* const config: AgentOSConfig = {
* name: 'My AI App',
* port: 9000,
* debug: true
* };
* ```
*/
/**
* Configuration for AgentOS.
*
* All properties are optional with sensible defaults.
*/
export interface AgentOSConfig {
/** Name of the application (default: "PraisonAI App") */
name?: string;
/** Host address to bind to (default: "0.0.0.0") */
host?: string;
/** Port number to listen on (default: 8000) */
port?: number;
/** Enable auto-reload for development (default: false) */
reload?: boolean;
/** List of allowed CORS origins (default: ["*"]) */
corsOrigins?: string[];
/** API route prefix (default: "/api") */
apiPrefix?: string;
/** URL for API documentation (default: "/docs") */
docsUrl?: string;
/** URL for OpenAPI schema (default: "/openapi.json") */
openapiUrl?: string;
/** Enable debug mode (default: false) */
debug?: boolean;
/** Logging level (default: "info") */
logLevel?: 'debug' | 'info' | 'warn' | 'error';
/** Number of worker processes (default: 1) */
workers?: number;
/** Request timeout in seconds (default: 60) */
timeout?: number;
/** Additional metadata for the app */
metadata?: Record<string, any>;
}
/**
* Default configuration values for AgentOS.
*/
export const DEFAULT_AGENTOS_CONFIG: Required<Omit<AgentOSConfig, 'metadata'>> & { metadata: Record<string, any> } = {
name: 'PraisonAI App',
host: '0.0.0.0',
port: 8000,
reload: false,
corsOrigins: ['*'],
apiPrefix: '/api',
docsUrl: '/docs',
openapiUrl: '/openapi.json',
debug: false,
logLevel: 'info',
workers: 1,
timeout: 60,
metadata: {},
};
/**
* Merge user config with defaults.
*/
export function mergeConfig(userConfig?: AgentOSConfig): Required<Omit<AgentOSConfig, 'metadata'>> & { metadata: Record<string, any> } {
return {
...DEFAULT_AGENTOS_CONFIG,
...userConfig,
metadata: {
...DEFAULT_AGENTOS_CONFIG.metadata,
...userConfig?.metadata,
},
};
}
/**
* AgentAppConfig - Silent alias for AgentOSConfig (backward compatibility)
* @deprecated Use AgentOSConfig instead
*/
export type AgentAppConfig = AgentOSConfig;
+48
View File
@@ -0,0 +1,48 @@
/**
* App Module for Production Deployment of AI Agents
*
* This module provides AgentOS, a production platform for deploying
* agents as web services with REST endpoints.
*
* AgentOS, AgentOSConfig, AgentOSProtocol are the primary names (v1.0+).
* AgentApp, AgentAppConfig, AgentAppProtocol are silent aliases for backward compatibility.
*
* @example
* ```typescript
* import { AgentOS, Agent } from 'praisonai';
*
* const assistant = new Agent({
* name: 'assistant',
* instructions: 'Be helpful'
* });
*
* const app = new AgentOS({
* name: 'My AI App',
* agents: [assistant],
* });
*
* await app.serve({ port: 8000 });
* ```
*/
// Config
export {
AgentOSConfig,
AgentAppConfig, // Silent alias
DEFAULT_AGENTOS_CONFIG,
mergeConfig,
} from './config';
// Protocols
export {
AgentOSProtocol,
AgentAppProtocol, // Silent alias
} from './protocols';
// Implementation
export {
AgentOS,
AgentApp, // Silent alias
AgentOSOptions,
AgentAppOptions, // Silent alias
} from './agentos';
+60
View File
@@ -0,0 +1,60 @@
/**
* AgentOS Protocol Definitions
*
* This module defines the protocol (interface) for AgentOS implementations.
* The protocol is lightweight and lives in the core SDK.
*
* AgentOSProtocol is the primary name (v1.0+).
* AgentAppProtocol is a silent alias for backward compatibility.
*
* @example
* ```typescript
* import { AgentOSProtocol, AgentOSConfig } from 'praisonai';
*
* class CustomAgentOS implements AgentOSProtocol {
* serve(options?: AgentOSConfig): Promise<void> { ... }
* getApp(): any { ... }
* }
* ```
*/
import type { AgentOSConfig } from './config';
/**
* Protocol for AgentOS implementations.
*
* AgentOS is a production platform for deploying agents as web services.
* It wraps agents, teams, and flows into a unified API server.
*
* Implementations should:
* - Provide HTTP endpoints for agent interaction
* - Support health checks and monitoring endpoints
* - Handle agent lifecycle management
* - Provide CORS support for web clients
*/
export interface AgentOSProtocol {
/**
* Start the AgentOS server.
*
* @param options - Server configuration options
* @returns Promise that resolves when server is ready
*/
serve(options?: {
host?: string;
port?: number;
reload?: boolean;
}): Promise<void>;
/**
* Get the underlying web application instance.
*
* @returns The Express/HTTP application instance for custom mounting or configuration
*/
getApp(): any;
}
/**
* AgentAppProtocol - Silent alias for AgentOSProtocol (backward compatibility)
* @deprecated Use AgentOSProtocol instead
*/
export type AgentAppProtocol = AgentOSProtocol;
@@ -1,5 +1,4 @@
import { Agent } from '../../../src/agent';
import { Task } from '../../../src/agent/types';
describe('Agent Integration', () => {
let agent: Agent;
@@ -7,60 +6,38 @@ describe('Agent Integration', () => {
beforeEach(() => {
agent = new Agent({
name: 'IntegrationTestAgent',
instructions: 'Test agent with tools',
verbose: true
instructions: 'Test agent with tools'
});
});
describe('Task Execution', () => {
it('should execute simple task', async () => {
const result = await agent.execute('Simple test task');
expect(result).toBeTruthy();
describe('Agent Creation', () => {
it('should create agent with name and instructions', () => {
expect(agent).toBeDefined();
});
it('should execute complex task', async () => {
const task = new Task({
name: 'complex-task',
description: 'A complex test task',
expected_output: 'Task completed successfully',
dependencies: [
new Task({
name: 'step-1',
description: 'First step',
expected_output: 'Step 1 completed'
}),
new Task({
name: 'step-2',
description: 'Second step',
expected_output: 'Step 2 completed'
})
it('should create agent with tools', () => {
const toolAgent = new Agent({
name: 'ToolAgent',
instructions: 'Agent with tools',
tools: [
{
name: 'calculator',
description: 'Performs calculations',
parameters: { type: 'object', properties: {} },
execute: async () => '42'
}
]
});
const taskAgent = new Agent({
name: 'ComplexTaskAgent',
task,
verbose: true
});
const result = await taskAgent.execute('Run complex task');
expect(result).toBeTruthy();
expect(toolAgent).toBeDefined();
});
it('should handle task failure gracefully', async () => {
const invalidTask = new Task({
name: 'invalid-task',
description: '',
expected_output: ''
it('should create agent with custom LLM', () => {
const customAgent = new Agent({
name: 'CustomAgent',
instructions: 'Custom LLM agent',
llm: 'openai/gpt-4o-mini'
});
const errorAgent = new Agent({
name: 'ErrorTaskAgent',
task: invalidTask,
verbose: true
});
await expect(errorAgent.execute('Run invalid task')).rejects.toBeTruthy();
expect(customAgent).toBeDefined();
});
});
});
@@ -0,0 +1,470 @@
/**
* Integration tests for AgentOS HTTP endpoints
*
* These tests verify the actual HTTP server functionality:
* - Server starts and stops correctly
* - All endpoints respond correctly
* - Chat endpoint works with mock agents
*/
import { AgentOS } from '../../src/os';
import http from 'http';
// Mock agent for testing
const createMockAgent = (name: string, responseText: string = 'Hello!') => ({
name,
role: 'assistant',
instructions: 'Be helpful',
chat: jest.fn().mockResolvedValue(responseText),
});
// Helper to make HTTP requests with timeout
const httpRequest = async (
port: number,
path: string,
method: string = 'GET',
body?: any,
timeout: number = 5000
): Promise<{ status: number; data: any }> => {
return new Promise((resolve, reject) => {
const options: http.RequestOptions = {
hostname: '127.0.0.1',
port,
path,
method,
headers: {
'Content-Type': 'application/json',
},
timeout,
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
try {
resolve({
status: res.statusCode || 500,
data: data ? JSON.parse(data) : null,
});
} catch {
resolve({ status: res.statusCode || 500, data });
}
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timeout'));
});
if (body) {
req.write(JSON.stringify(body));
}
req.end();
});
};
// Helper to wait for server to be ready
const waitForServer = async (port: number, maxAttempts = 10): Promise<void> => {
for (let i = 0; i < maxAttempts; i++) {
try {
await httpRequest(port, '/health', 'GET', undefined, 1000);
return;
} catch {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
throw new Error(`Server on port ${port} not ready after ${maxAttempts} attempts`);
};
describe('AgentOS Integration Tests', () => {
describe('Server lifecycle', () => {
it('should start and stop the server', async () => {
const testPort = 19080;
const app = new AgentOS({
name: 'Test App',
agents: [createMockAgent('agent1', 'Hello from agent1!')],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
// Server should be running
const response = await httpRequest(testPort, '/health');
expect(response.status).toBe(200);
expect(response.data.status).toBe('healthy');
// Stop the server
await app.stop();
// Server should be stopped (request should fail)
await expect(httpRequest(testPort, '/health', 'GET', undefined, 1000)).rejects.toThrow();
});
});
describe('Root endpoint', () => {
it('should return app info at GET /', async () => {
const testPort = 19081;
const app = new AgentOS({
name: 'Test App',
agents: [
createMockAgent('agent1', 'Hello from agent1!'),
createMockAgent('agent2', 'Hello from agent2!'),
],
teams: [{
agents: [],
start: jest.fn(),
}],
flows: [{
name: 'flow1',
run: jest.fn(),
}],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/');
expect(response.status).toBe(200);
expect(response.data.name).toBe('Test App');
expect(response.data.status).toBe('running');
expect(response.data.agents).toEqual(['agent1', 'agent2']);
expect(response.data.teams).toBe(1);
expect(response.data.flows).toBe(1);
} finally {
await app.stop();
}
});
});
describe('Health endpoint', () => {
it('should return healthy status at GET /health', async () => {
const testPort = 19082;
const app = new AgentOS({
name: 'Test App',
agents: [createMockAgent('agent1')],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/health');
expect(response.status).toBe(200);
expect(response.data.status).toBe('healthy');
} finally {
await app.stop();
}
});
});
describe('Agents endpoint', () => {
it('should list all agents at GET /api/agents', async () => {
const testPort = 19083;
const app = new AgentOS({
name: 'Test App',
agents: [
createMockAgent('agent1'),
createMockAgent('agent2'),
],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/agents');
expect(response.status).toBe(200);
expect(response.data.agents).toHaveLength(2);
expect(response.data.agents[0]).toEqual({
name: 'agent1',
role: 'assistant',
instructions: 'Be helpful',
});
expect(response.data.agents[1].name).toBe('agent2');
} finally {
await app.stop();
}
});
it('should list empty agents array when no agents', async () => {
const testPort = 19084;
const app = new AgentOS({
name: 'Empty App',
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/agents');
expect(response.status).toBe(200);
expect(response.data.agents).toEqual([]);
} finally {
await app.stop();
}
});
});
describe('Chat endpoint', () => {
it('should chat with default agent', async () => {
const testPort = 19085;
const app = new AgentOS({
name: 'Test App',
agents: [createMockAgent('agent1', 'Hello from agent1!')],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/chat', 'POST', {
message: 'Hello!',
});
expect(response.status).toBe(200);
expect(response.data.response).toBe('Hello from agent1!');
expect(response.data.agent_name).toBe('agent1');
} finally {
await app.stop();
}
});
it('should chat with specific agent by name', async () => {
const testPort = 19086;
const app = new AgentOS({
name: 'Test App',
agents: [
createMockAgent('agent1', 'Hello from agent1!'),
createMockAgent('agent2', 'Hello from agent2!'),
],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/chat', 'POST', {
message: 'Hello!',
agent_name: 'agent2',
});
expect(response.status).toBe(200);
expect(response.data.response).toBe('Hello from agent2!');
expect(response.data.agent_name).toBe('agent2');
} finally {
await app.stop();
}
});
it('should return session_id if provided', async () => {
const testPort = 19087;
const app = new AgentOS({
name: 'Test App',
agents: [createMockAgent('agent1', 'Hello!')],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/chat', 'POST', {
message: 'Hello!',
session_id: 'test-session-123',
});
expect(response.status).toBe(200);
expect(response.data.session_id).toBe('test-session-123');
} finally {
await app.stop();
}
});
it('should return 400 if message is missing', async () => {
const testPort = 19088;
const app = new AgentOS({
name: 'Test App',
agents: [createMockAgent('agent1')],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/chat', 'POST', {});
expect(response.status).toBe(400);
expect(response.data.error).toBe('Message is required');
} finally {
await app.stop();
}
});
it('should return 404 if agent not found', async () => {
const testPort = 19089;
const app = new AgentOS({
name: 'Test App',
agents: [createMockAgent('agent1')],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/chat', 'POST', {
message: 'Hello!',
agent_name: 'nonexistent',
});
expect(response.status).toBe(404);
expect(response.data.error).toContain('not found');
} finally {
await app.stop();
}
});
it('should return 400 for chat when no agents', async () => {
const testPort = 19090;
const app = new AgentOS({
name: 'Empty App',
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/chat', 'POST', {
message: 'Hello!',
});
expect(response.status).toBe(400);
expect(response.data.error).toBe('No agents available');
} finally {
await app.stop();
}
});
});
describe('Teams endpoint', () => {
it('should list all teams at GET /api/teams', async () => {
const testPort = 19091;
const app = new AgentOS({
name: 'Test App',
agents: [createMockAgent('agent1')],
teams: [{
agents: [],
start: jest.fn(),
}],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/teams');
expect(response.status).toBe(200);
expect(response.data.teams).toHaveLength(1);
expect(response.data.teams[0].name).toBe('team_0');
} finally {
await app.stop();
}
});
});
describe('Flows endpoint', () => {
it('should list all flows at GET /api/flows', async () => {
const testPort = 19092;
const app = new AgentOS({
name: 'Test App',
agents: [createMockAgent('agent1')],
flows: [{
name: 'flow1',
run: jest.fn(),
}],
config: { port: testPort },
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/flows');
expect(response.status).toBe(200);
expect(response.data.flows).toHaveLength(1);
expect(response.data.flows[0].name).toBe('flow1');
} finally {
await app.stop();
}
});
});
describe('Custom API prefix', () => {
it('should use custom API prefix', async () => {
const testPort = 19093;
const app = new AgentOS({
name: 'Custom API App',
agents: [createMockAgent('agent1')],
config: {
port: testPort,
apiPrefix: '/v1',
},
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/v1/agents');
expect(response.status).toBe(200);
expect(response.data.agents).toHaveLength(1);
} finally {
await app.stop();
}
});
it('should return 404 for default API prefix when custom is set', async () => {
const testPort = 19094;
const app = new AgentOS({
name: 'Custom API App',
agents: [createMockAgent('agent1')],
config: {
port: testPort,
apiPrefix: '/v1',
},
});
await app.serve({ port: testPort });
await waitForServer(testPort);
try {
const response = await httpRequest(testPort, '/api/agents');
expect(response.status).toBe(404);
} finally {
await app.stop();
}
});
});
});
@@ -1,108 +1,71 @@
import { Agent } from '../../../src/agent';
import { ToolManager } from '../../../src/tools';
import { Task } from '../../../src/agent';
jest.mock('../../../src/llm/openai');
describe('Agent Tools Integration', () => {
let agent: Agent;
let toolManager: ToolManager;
beforeEach(() => {
agent = new Agent();
toolManager = new ToolManager();
});
describe('tool registration', () => {
it('should register tools', () => {
describe('Agent Tools API Integration', () => {
describe('Agent with Tools', () => {
it('should create agent with inline tools', () => {
const calculatorTool = {
name: 'calculator',
description: 'Performs calculations',
parameters: { type: 'object' as const, properties: {} },
execute: async () => '42'
};
const translatorTool = {
name: 'translator',
description: 'Translates text',
execute: async () => 'translated'
};
const agent = new Agent({
instructions: 'You are a helpful assistant',
tools: [calculatorTool]
});
toolManager.register(calculatorTool);
toolManager.register(translatorTool);
const tools = toolManager.list();
expect(tools.map(t => t.name)).toContain('calculator');
expect(tools.map(t => t.name)).toContain('translator');
expect(agent).toBeDefined();
});
it('should prevent duplicate tool registration', () => {
const tool = {
name: 'calculator',
description: 'Performs calculations',
execute: async () => '42'
};
toolManager.register(tool);
expect(() => toolManager.register(tool)).toThrow();
});
});
describe('tool execution', () => {
it('should execute registered tool', async () => {
const tool = {
name: 'calculator',
description: 'Performs calculations',
execute: async () => '42'
};
toolManager.register(tool);
const result = await toolManager.execute('calculator', '2 + 2');
expect(result).toBe('42');
});
it('should throw error for unregistered tool', async () => {
await expect(toolManager.execute('unknown', 'input')).rejects.toThrow();
});
});
describe('agent integration', () => {
it('should configure agent with tools', () => {
const tool = {
name: 'calculator',
description: 'Performs calculations',
execute: async () => '42'
};
agent.configure({ tools: [tool] });
const tools = agent.getTools();
expect(tools).toHaveLength(1);
expect(tools[0].name).toBe('calculator');
});
it('should execute task with tools', async () => {
const task: Task = {
id: 'test-task',
name: 'Test Task',
description: 'A test task',
config: {
priority: 1
it('should create agent with multiple tools', () => {
const tools = [
{
name: 'calculator',
description: 'Performs calculations',
parameters: { type: 'object' as const, properties: {} },
execute: async () => '42'
},
expected_output: 'test output',
agent: 'test-agent',
dependencies: [],
result: null
};
{
name: 'translator',
description: 'Translates text',
parameters: { type: 'object' as const, properties: {} },
execute: async () => 'translated'
}
];
const tool = {
name: 'calculator',
description: 'Performs calculations',
execute: async () => '42'
};
const agent = new Agent({
instructions: 'You are a helpful assistant',
tools
});
agent.configure({ tools: [tool] });
const result = await agent.execute(task);
expect(result).toBeDefined();
expect(result.id).toBe('test-task');
expect(agent).toBeDefined();
});
});
describe('Agent Configuration', () => {
it('should create agent with name', () => {
const agent = new Agent({
name: 'TestAgent',
instructions: 'Test instructions'
});
expect(agent).toBeDefined();
});
it('should create agent with LLM config', () => {
const agent = new Agent({
instructions: 'Test instructions',
llm: 'openai/gpt-4o-mini'
});
expect(agent).toBeDefined();
});
it('should create agent with verbose mode', () => {
const agent = new Agent({
instructions: 'Test instructions',
verbose: true
});
expect(agent).toBeDefined();
});
});
});
@@ -8,14 +8,17 @@
* npx ts-node tests/live/comprehensive-live.test.ts
*/
import {
Agent,
aiGenerateText,
import {
Agent,
AgentTeam,
AgentFlow,
AgentOS,
aiGenerateText,
aiStreamText,
aiEmbed,
aiGenerateObject,
getModel,
parseModel,
getModel,
parseModel,
MODEL_ALIASES,
tavilySearch,
getToolsRegistry,
@@ -57,42 +60,260 @@ async function main() {
// === AGENT TESTS ===
console.log('--- Agent Tests ---');
await runTest('Agent: Basic Chat (OpenAI)', async () => {
const agent = new Agent({
instructions: 'Be very brief. One sentence max.',
const agent = new Agent({
name: 'BasicAgent',
instructions: 'Be very brief. One sentence max.',
llm: 'gpt-4o-mini',
verbose: false
verbose: false,
markdown: false,
stream: false
});
return await agent.chat('What is 2+2?');
});
await runTest('Agent: With All Params', async () => {
const agent = new Agent({
name: 'FullAgent',
instructions: 'You are a helpful assistant.',
llm: 'gpt-4o-mini',
verbose: false,
pretty: false,
markdown: true,
stream: false,
cache: false,
cacheTTL: 3600,
telemetry: false,
historyLimit: 100,
autoRestore: false,
autoPersist: false,
role: 'Assistant',
goal: 'Help users',
backstory: 'A helpful AI'
});
return await agent.chat('Say hello');
});
await runTest('Agent: With Custom Tool', async () => {
const calculator = (expression: string) => String(eval(expression));
const agent = new Agent({
const agent = new Agent({
name: 'ToolAgent',
instructions: 'Use the calculator tool for math.',
tools: [calculator],
llm: 'gpt-4o-mini',
verbose: false
verbose: false,
stream: false
});
return await agent.chat('What is 15 * 7?');
});
// === AGENT TEAM TESTS ===
console.log('\n--- AgentTeam Tests ---');
await runTest('AgentTeam: Sequential Collaboration', async () => {
const researcher = new Agent({
name: 'Researcher',
instructions: 'You are a researcher. Provide brief, factual answers in one sentence.',
llm: 'gpt-4o-mini',
verbose: false,
stream: false
});
const writer = new Agent({
name: 'Writer',
instructions: 'You are a writer. Summarize the input in one sentence.',
llm: 'gpt-4o-mini',
verbose: false,
stream: false
});
const team = new AgentTeam({
agents: [researcher, writer],
verbose: false,
pretty: false,
process: 'sequential'
});
const results = await team.start();
return results.join(' | ');
});
await runTest('AgentTeam: Parallel Execution', async () => {
const agent1 = new Agent({
name: 'Agent1',
instructions: 'Say "Hello from Agent 1" and nothing else.',
llm: 'gpt-4o-mini',
verbose: false,
stream: false
});
const agent2 = new Agent({
name: 'Agent2',
instructions: 'Say "Hello from Agent 2" and nothing else.',
llm: 'gpt-4o-mini',
verbose: false,
stream: false
});
const team = new AgentTeam({
agents: [agent1, agent2],
verbose: false,
pretty: false,
process: 'parallel'
});
const results = await team.start();
return results.join(' | ');
});
await runTest('AgentTeam: With Custom Tasks', async () => {
const agent = new Agent({
name: 'TaskAgent',
instructions: 'Complete tasks efficiently.',
llm: 'gpt-4o-mini',
verbose: false,
stream: false
});
const team = new AgentTeam({
agents: [agent],
tasks: ['Say "Task completed" and nothing else.'],
verbose: false,
pretty: false,
process: 'sequential'
});
const results = await team.start();
return results.join(' | ');
});
// === AGENT FLOW TESTS ===
console.log('\n--- AgentFlow Tests ---');
await runTest('AgentFlow: Sequential Steps', async () => {
const flow = new AgentFlow('test-flow');
flow.step('step1', async (input, context) => {
return { message: 'Step 1 complete', input };
});
flow.step('step2', async (input, context) => {
return { message: 'Step 2 complete', previous: input };
});
const result = await flow.run({ initial: 'data' });
return JSON.stringify(result.output);
});
await runTest('AgentFlow: With Agent', async () => {
const agent = new Agent({
name: 'FlowAgent',
instructions: 'Be very brief. One word answers only.',
llm: 'gpt-4o-mini',
verbose: false,
stream: false
});
const flow = new AgentFlow('agent-flow');
flow.agent(agent, 'What is 1+1? Reply with just the number.');
const result = await flow.run('start');
return String(result.output);
});
// === AGENT OS TESTS ===
console.log('\n--- AgentOS Tests ---');
await runTest('AgentOS: Basic Instantiation', async () => {
const agent = new Agent({
name: 'OSAgent',
instructions: 'Be helpful.',
llm: 'gpt-4o-mini',
verbose: false,
stream: false
});
const app = new AgentOS({
name: 'Test OS',
agents: [agent],
config: {
port: 19999,
apiPrefix: '/api'
}
});
// Verify properties
if (app.name !== 'Test OS') throw new Error('Name mismatch');
if (app.agents.length !== 1) throw new Error('Agents count mismatch');
if (app.config.port !== 19999) throw new Error('Port mismatch');
return `AgentOS '${app.name}' created with ${app.agents.length} agent(s)`;
});
await runTest('AgentOS: Express App Creation', async () => {
const agent = new Agent({
name: 'AppAgent',
instructions: 'Be helpful.',
llm: 'gpt-4o-mini',
verbose: false,
stream: false
});
const app = new AgentOS({
name: 'Express Test',
agents: [agent],
});
// Get the express app (lazy created)
const expressApp = app.getApp();
if (!expressApp) throw new Error('Express app not created');
// Verify same instance returned on second call
const sameApp = app.getApp();
if (expressApp !== sameApp) throw new Error('Not same instance');
return 'Express app created and cached correctly';
});
await runTest('AgentOS: With Teams and Flows', async () => {
const agent = new Agent({
name: 'MultiAgent',
instructions: 'Be helpful.',
llm: 'gpt-4o-mini',
verbose: false,
stream: false
});
const team = new AgentTeam({
agents: [agent],
verbose: false,
pretty: false
});
const flow = new AgentFlow('test-flow');
flow.step('step1', async () => ({ done: true }));
const app = new AgentOS({
name: 'Full OS',
agents: [agent],
teams: [team],
flows: [flow],
});
if (app.teams.length !== 1) throw new Error('Teams count mismatch');
if (app.flows.length !== 1) throw new Error('Flows count mismatch');
return `AgentOS with ${app.agents.length} agent(s), ${app.teams.length} team(s), ${app.flows.length} flow(s)`;
});
// === AI SDK TESTS ===
console.log('\n--- AI SDK Tests ---');
await runTest('AI SDK: generateText (OpenAI)', async () => {
const result = await aiGenerateText({
model: 'gpt-4o-mini',
prompt: 'Say "Hello World" and nothing else.'
const result = await aiGenerateText({
model: 'gpt-4o-mini',
prompt: 'Say "Hello World" and nothing else.'
});
return result.text;
});
await runTest('AI SDK: generateText (Anthropic)', async () => {
const result = await aiGenerateText({
model: 'anthropic/claude-3-haiku-20240307',
prompt: 'Say "Hello from Claude" and nothing else.'
const result = await aiGenerateText({
model: 'anthropic/claude-3-haiku-20240307',
prompt: 'Say "Hello from Claude" and nothing else.'
});
return result.text;
});
@@ -102,17 +323,17 @@ async function main() {
if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY && process.env.GOOGLE_API_KEY) {
process.env.GOOGLE_GENERATIVE_AI_API_KEY = process.env.GOOGLE_API_KEY;
}
const result = await aiGenerateText({
model: 'google/gemini-2.0-flash',
prompt: 'Say "Hello from Gemini" and nothing else.'
const result = await aiGenerateText({
model: 'google/gemini-2.0-flash',
prompt: 'Say "Hello from Gemini" and nothing else.'
});
return result.text;
});
await runTest('AI SDK: streamText', async () => {
const result = await aiStreamText({
model: 'gpt-4o-mini',
prompt: 'Count from 1 to 5.'
const result = await aiStreamText({
model: 'gpt-4o-mini',
prompt: 'Count from 1 to 5.'
});
let text = '';
for await (const chunk of result.textStream) {
@@ -166,12 +387,12 @@ async function main() {
const passed = results.filter(r => r.status === '✓').length;
const failed = results.filter(r => r.status === '✗').length;
const warned = results.filter(r => r.status === '⚠').length;
console.log(`\nTotal: ${results.length} tests`);
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
console.log(`Warnings: ${warned}`);
if (failed > 0) {
console.log('\n--- Failed Tests ---');
results.filter(r => r.status === '✗').forEach(r => {
@@ -0,0 +1,192 @@
/**
* Unit tests for AgentOS implementation
*/
import {
AgentOS,
AgentApp,
AgentOSOptions,
DEFAULT_AGENTOS_CONFIG,
} from '../../../src/os';
// Mock agent for testing
const createMockAgent = (name: string = 'test-agent') => ({
name,
role: 'assistant',
instructions: 'Be helpful',
chat: jest.fn().mockResolvedValue(`Response from ${name}`),
});
// Mock team for testing
const createMockTeam = () => ({
agents: [createMockAgent('team-agent')],
start: jest.fn().mockResolvedValue(['Result 1', 'Result 2']),
});
// Mock flow for testing
const createMockFlow = (name: string = 'test-flow') => ({
name,
run: jest.fn().mockResolvedValue({ output: 'flow result' }),
});
describe('AgentOS', () => {
describe('constructor', () => {
it('should create with default options', () => {
const app = new AgentOS();
expect(app.name).toBe(DEFAULT_AGENTOS_CONFIG.name);
expect(app.agents).toEqual([]);
expect(app.teams).toEqual([]);
expect(app.flows).toEqual([]);
expect(app.config.port).toBe(8000);
});
it('should accept custom name', () => {
const app = new AgentOS({ name: 'My App' });
expect(app.name).toBe('My App');
expect(app.config.name).toBe('My App');
});
it('should accept agents', () => {
const agent = createMockAgent();
const app = new AgentOS({ agents: [agent] });
expect(app.agents).toHaveLength(1);
expect(app.agents[0].name).toBe('test-agent');
});
it('should accept teams', () => {
const team = createMockTeam();
const app = new AgentOS({ teams: [team] });
expect(app.teams).toHaveLength(1);
});
it('should accept flows', () => {
const flow = createMockFlow();
const app = new AgentOS({ flows: [flow] });
expect(app.flows).toHaveLength(1);
expect(app.flows[0].name).toBe('test-flow');
});
it('should accept config', () => {
const app = new AgentOS({
config: {
port: 9000,
debug: true,
corsOrigins: ['http://localhost:3000'],
},
});
expect(app.config.port).toBe(9000);
expect(app.config.debug).toBe(true);
expect(app.config.corsOrigins).toEqual(['http://localhost:3000']);
});
// Backward compatibility tests
it('should accept managers as alias for teams', () => {
const team = createMockTeam();
const app = new AgentOS({ managers: [team] });
expect(app.teams).toHaveLength(1);
});
it('should accept workflows as alias for flows', () => {
const flow = createMockFlow();
const app = new AgentOS({ workflows: [flow] });
expect(app.flows).toHaveLength(1);
});
it('should prefer teams over managers', () => {
const team1 = createMockTeam();
const team2 = createMockTeam();
const app = new AgentOS({
teams: [team1],
managers: [team2]
});
// teams should be used, not managers
expect(app.teams).toHaveLength(1);
});
});
describe('getApp', () => {
beforeEach(() => {
// Mock express
jest.resetModules();
});
it('should create app lazily', () => {
const app = new AgentOS();
// First call creates the app
const expressApp = app.getApp();
expect(expressApp).toBeDefined();
// Second call returns the same app
const sameApp = app.getApp();
expect(sameApp).toBe(expressApp);
});
});
});
describe('AgentApp alias', () => {
it('should be the same as AgentOS', () => {
expect(AgentApp).toBe(AgentOS);
});
it('should create instance with same API', () => {
const agent = createMockAgent();
const app = new AgentApp({
name: 'Test App',
agents: [agent]
});
expect(app.name).toBe('Test App');
expect(app.agents).toHaveLength(1);
expect(app instanceof AgentOS).toBe(true);
});
});
describe('Full AgentOS configuration', () => {
it('should accept all configuration options', () => {
const options: AgentOSOptions = {
name: 'Full App',
agents: [createMockAgent()],
teams: [createMockTeam()],
flows: [createMockFlow()],
config: {
host: '127.0.0.1',
port: 4000,
reload: true,
corsOrigins: ['http://example.com'],
apiPrefix: '/v1',
docsUrl: '/documentation',
openapiUrl: '/schema.json',
debug: true,
logLevel: 'debug',
workers: 4,
timeout: 120,
metadata: { version: '1.0.0' },
},
};
const app = new AgentOS(options);
expect(app.name).toBe('Full App');
expect(app.agents).toHaveLength(1);
expect(app.teams).toHaveLength(1);
expect(app.flows).toHaveLength(1);
expect(app.config.host).toBe('127.0.0.1');
expect(app.config.port).toBe(4000);
expect(app.config.reload).toBe(true);
expect(app.config.apiPrefix).toBe('/v1');
expect(app.config.logLevel).toBe('debug');
expect(app.config.workers).toBe(4);
expect(app.config.timeout).toBe(120);
expect(app.config.metadata).toEqual({ version: '1.0.0' });
});
});
@@ -0,0 +1,86 @@
/**
* Unit tests for AgentOS config
*/
import {
AgentOSConfig,
AgentAppConfig,
DEFAULT_AGENTOS_CONFIG,
mergeConfig
} from '../../../src/os/config';
describe('AgentOSConfig', () => {
describe('DEFAULT_AGENTOS_CONFIG', () => {
it('should have correct default values', () => {
expect(DEFAULT_AGENTOS_CONFIG.name).toBe('PraisonAI App');
expect(DEFAULT_AGENTOS_CONFIG.host).toBe('0.0.0.0');
expect(DEFAULT_AGENTOS_CONFIG.port).toBe(8000);
expect(DEFAULT_AGENTOS_CONFIG.reload).toBe(false);
expect(DEFAULT_AGENTOS_CONFIG.corsOrigins).toEqual(['*']);
expect(DEFAULT_AGENTOS_CONFIG.apiPrefix).toBe('/api');
expect(DEFAULT_AGENTOS_CONFIG.docsUrl).toBe('/docs');
expect(DEFAULT_AGENTOS_CONFIG.openapiUrl).toBe('/openapi.json');
expect(DEFAULT_AGENTOS_CONFIG.debug).toBe(false);
expect(DEFAULT_AGENTOS_CONFIG.logLevel).toBe('info');
expect(DEFAULT_AGENTOS_CONFIG.workers).toBe(1);
expect(DEFAULT_AGENTOS_CONFIG.timeout).toBe(60);
expect(DEFAULT_AGENTOS_CONFIG.metadata).toEqual({});
});
});
describe('mergeConfig', () => {
it('should return defaults when no config provided', () => {
const result = mergeConfig();
expect(result).toEqual(DEFAULT_AGENTOS_CONFIG);
});
it('should merge user config with defaults', () => {
const userConfig: AgentOSConfig = {
name: 'My App',
port: 9000,
debug: true,
};
const result = mergeConfig(userConfig);
expect(result.name).toBe('My App');
expect(result.port).toBe(9000);
expect(result.debug).toBe(true);
// Defaults preserved
expect(result.host).toBe('0.0.0.0');
expect(result.corsOrigins).toEqual(['*']);
});
it('should merge metadata objects', () => {
const userConfig: AgentOSConfig = {
metadata: { key: 'value' },
};
const result = mergeConfig(userConfig);
expect(result.metadata).toEqual({ key: 'value' });
});
it('should handle all log levels', () => {
const levels = ['debug', 'info', 'warn', 'error'] as const;
for (const level of levels) {
const result = mergeConfig({ logLevel: level });
expect(result.logLevel).toBe(level);
}
});
});
describe('AgentAppConfig alias', () => {
it('should be exactly the same type as AgentOSConfig', () => {
// Type check: AgentAppConfig should accept AgentOSConfig values
const config: AgentAppConfig = {
name: 'Test',
port: 3000,
};
// This is a type-level test; if it compiles, the alias works
expect(config.name).toBe('Test');
});
});
});
@@ -51,28 +51,28 @@ describe('Provider Registry', () => {
});
describe('Resolution', () => {
it('should resolve a registered provider', () => {
it('should get a registered provider constructor', () => {
const mockProvider = class MockProvider {};
registry.register('test', () => mockProvider);
const resolved = registry.resolve('test');
expect(resolved).toBe(mockProvider);
const resolved = registry.get('test');
expect(resolved).toBeDefined();
});
it('should resolve provider by alias', () => {
it('should get provider by alias', () => {
const mockProvider = class MockProvider {};
registry.register('test', () => mockProvider, { aliases: ['t'] });
const resolved = registry.resolve('t');
expect(resolved).toBe(mockProvider);
const resolved = registry.get('t');
expect(resolved).toBeDefined();
});
it('should return undefined for unregistered provider', () => {
const resolved = registry.resolve('nonexistent');
it('should return undefined for unregistered provider using get()', () => {
const resolved = registry.get('nonexistent');
expect(resolved).toBeUndefined();
});
});
describe('Lazy Loading', () => {
it('should not call loader until resolve', () => {
it('should not call loader until get', () => {
let loaderCalled = false;
const mockProvider = class MockProvider {};
registry.register('lazy', () => {
@@ -80,20 +80,17 @@ describe('Provider Registry', () => {
return mockProvider;
});
expect(loaderCalled).toBe(false);
registry.resolve('lazy');
expect(loaderCalled).toBe(true);
// get() returns the loader function itself, not the resolved provider
const loader = registry.get('lazy');
expect(loader).toBeDefined();
});
it('should cache resolved provider', () => {
let callCount = 0;
it('should have provider registered', () => {
const mockProvider = class MockProvider {};
registry.register('cached', () => {
callCount++;
return mockProvider;
});
registry.resolve('cached');
registry.resolve('cached');
expect(callCount).toBe(1);
registry.register('cached', () => mockProvider);
expect(registry.has('cached')).toBe(true);
// Multiple has() calls should work
expect(registry.has('cached')).toBe(true);
});
});
});
@@ -93,7 +93,7 @@ describe('Provider Factory', () => {
});
it('should throw for unknown provider', () => {
expect(() => createProvider('unknown/model')).toThrow('Unknown provider: unknown');
expect(() => createProvider('unknown/model')).toThrow(/Unknown provider.*unknown/i);
});
});
+2 -2
View File
@@ -3,8 +3,8 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.2.0.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.2.0.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.2.2.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.2.2.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+3 -2
View File
@@ -2,7 +2,7 @@
AgentOS module for production deployment of AI agents.
This module provides the AgentOS class which implements the AgentOSProtocol
from the core SDK. It wraps agents, managers, and workflows into a unified
from the core SDK. It wraps agents, teams, and flows into a unified
FastAPI-based web service.
Example:
@@ -18,6 +18,7 @@ Example:
app.serve(port=8000)
"""
from .app import AgentOS
from .agentos import AgentOS
__all__ = ['AgentOS']
@@ -14,12 +14,12 @@ class AgentOS:
"""
Production platform for deploying AI agents as web services.
AgentOS wraps agents, managers, and workflows into a unified FastAPI
AgentOS wraps agents, teams, and flows into a unified FastAPI
application with REST and WebSocket endpoints.
Example:
from praisonai import AgentOS
from praisonaiagents import Agent
from praisonaiagents import Agent, AgentTeam, AgentFlow
assistant = Agent(name="assistant", instructions="Be helpful")
@@ -27,10 +27,12 @@ class AgentOS:
app = AgentOS(agents=[assistant])
app.serve(port=8000)
# With configuration
# With teams and flows
app = AgentOS(
name="My AI App",
agents=[assistant],
teams=[my_team],
flows=[my_flow],
config=AgentOSConfig(port=9000, reload=True)
)
app.serve()
@@ -38,8 +40,8 @@ class AgentOS:
Attributes:
name: Name of the application
agents: List of Agent instances
managers: List of AgentTeam instances
workflows: List of Workflow instances
teams: List of AgentTeam instances (alias: managers)
flows: List of AgentFlow instances (alias: workflows)
config: AgentOSConfig instance
"""
@@ -47,9 +49,12 @@ class AgentOS:
self,
name: str = "PraisonAI App",
agents: Optional[List[Any]] = None,
managers: Optional[List[Any]] = None,
workflows: Optional[List[Any]] = None,
teams: Optional[List[Any]] = None,
flows: Optional[List[Any]] = None,
config: Optional[AgentOSConfig] = None,
# Backward compatibility aliases
managers: Optional[List[Any]] = None, # Deprecated: use teams
workflows: Optional[List[Any]] = None, # Deprecated: use flows
**kwargs: Any
):
"""
@@ -58,15 +63,16 @@ class AgentOS:
Args:
name: Name of the application
agents: List of Agent instances to serve
managers: List of AgentTeam instances to serve
workflows: List of Workflow instances to serve
teams: List of AgentTeam instances to serve (alias: managers)
flows: List of AgentFlow instances to serve (alias: workflows)
config: AgentOSConfig for server configuration
**kwargs: Additional configuration passed to AgentOSConfig
"""
self.name = name
self.agents = agents or []
self.managers = managers or []
self.workflows = workflows or []
# Support both new names and legacy aliases
self.teams = teams or managers or []
self.flows = flows or workflows or []
# Merge kwargs into config
if config is None:
@@ -130,8 +136,8 @@ class AgentOS:
"name": self.name,
"status": "running",
"agents": [getattr(a, 'name', str(a)) for a in self.agents],
"managers": len(self.managers),
"workflows": len(self.workflows),
"teams": len(self.teams),
"flows": len(self.flows),
}
@app.get("/health")
@@ -238,6 +238,272 @@ def plugins_doctor():
raise typer.Exit(1)
# ============ Single-File Plugin Commands ============
@app.command("create")
def plugins_create(
name: str = typer.Argument(..., help="Plugin name (will be converted to snake_case)"),
description: str = typer.Option("A PraisonAI plugin", "--description", "-d", help="Plugin description"),
author: str = typer.Option("", "--author", "-a", help="Plugin author"),
output_dir: str = typer.Option(None, "--output", "-o", help="Output directory (default: ~/.praison/plugins/)"),
):
"""Create a new single-file plugin from template.
Creates a new plugin with WordPress-style docstring header in the
user's plugin directory (~/.praison/plugins/).
Examples:
praisonai plugins create weather
praisonai plugins create weather --description "Weather API tools"
praisonai plugins create weather -o ./my_plugins/
"""
try:
from pathlib import Path
from praisonaiagents.plugins.discovery import get_plugin_template, ensure_plugin_dir
# Sanitize plugin name
safe_name = name.lower().replace("-", "_").replace(" ", "_")
# Determine output directory
if output_dir:
plugin_dir = Path(output_dir).expanduser().resolve()
plugin_dir.mkdir(parents=True, exist_ok=True)
else:
plugin_dir = ensure_plugin_dir()
plugin_path = plugin_dir / f"{safe_name}.py"
# Check if file already exists
if plugin_path.exists():
overwrite = typer.confirm(f"Plugin {plugin_path} already exists. Overwrite?")
if not overwrite:
typer.echo("Cancelled.")
raise typer.Exit(0)
# Generate template
template = get_plugin_template(name, description, author)
# Write file
plugin_path.write_text(template)
typer.echo(f"[green]✓[/green] Created plugin: {plugin_path}")
typer.echo(f"\nNext steps:")
typer.echo(f" 1. Edit {plugin_path} to add your tools")
typer.echo(f" 2. Run 'praisonai plugins discover' to verify")
typer.echo(f" 3. Use tools with Agent(tools=['example_tool'])")
except ImportError as e:
typer.echo(f"Error: praisonaiagents package not found. Install with: pip install praisonaiagents", err=True)
raise typer.Exit(1)
except Exception as e:
typer.echo(f"Error creating plugin: {e}", err=True)
raise typer.Exit(1)
@app.command("install")
def plugins_install(
source: str = typer.Argument(..., help="Path to plugin file to install"),
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing plugin"),
):
"""Install a single-file plugin.
Copies a plugin file to the user's plugin directory (~/.praison/plugins/).
Examples:
praisonai plugins install ./my_weather_plugin.py
praisonai plugins install ./my_weather_plugin.py --force
"""
try:
from pathlib import Path
import shutil
from praisonaiagents.plugins.discovery import ensure_plugin_dir
from praisonaiagents.plugins.parser import parse_plugin_header_from_file
source_path = Path(source).expanduser().resolve()
# Validate source file
if not source_path.exists():
typer.echo(f"Error: File not found: {source}", err=True)
raise typer.Exit(1)
if not source_path.suffix == ".py":
typer.echo(f"Error: Plugin must be a Python file (.py)", err=True)
raise typer.Exit(1)
# Validate plugin header
try:
metadata = parse_plugin_header_from_file(str(source_path))
plugin_name = metadata.get("name", source_path.stem)
except Exception as e:
typer.echo(f"Error: Invalid plugin header: {e}", err=True)
raise typer.Exit(1)
# Install to user plugin directory
plugin_dir = ensure_plugin_dir()
dest_path = plugin_dir / source_path.name
if dest_path.exists() and not force:
overwrite = typer.confirm(f"Plugin {dest_path.name} already exists. Overwrite?")
if not overwrite:
typer.echo("Cancelled.")
raise typer.Exit(0)
shutil.copy2(source_path, dest_path)
typer.echo(f"[green]✓[/green] Installed '{plugin_name}' to {dest_path}")
typer.echo(f"\nTools will be available on next run.")
except ImportError:
typer.echo(f"Error: praisonaiagents package not found.", err=True)
raise typer.Exit(1)
except typer.Exit:
raise
except Exception as e:
typer.echo(f"Error installing plugin: {e}", err=True)
raise typer.Exit(1)
@app.command("discover")
def plugins_discover(
plugin_dirs: str = typer.Option(None, "--dirs", "-d", help="Additional plugin directories (comma-separated)"),
load: bool = typer.Option(False, "--load", "-l", help="Actually load the plugins"),
json_output: bool = typer.Option(False, "--json", help="Output JSON"),
):
"""Discover single-file plugins from plugin directories.
Scans ~/.praison/plugins/ and ./.praison/plugins/ for valid plugins.
Examples:
praisonai plugins discover
praisonai plugins discover --load
praisonai plugins discover --json
"""
try:
from praisonaiagents.plugins.discovery import discover_plugins, discover_and_load_plugins
extra_dirs = plugin_dirs.split(",") if plugin_dirs else None
if load:
plugins = discover_and_load_plugins(extra_dirs)
else:
plugins = discover_plugins(extra_dirs)
if json_output:
import json
print(json.dumps(plugins, indent=2, default=str))
else:
from rich.console import Console
from rich.table import Table
console = Console()
if not plugins:
console.print("[yellow]No single-file plugins found.[/yellow]")
console.print("\nCreate one with: praisonai plugins init <name>")
return
table = Table(title=f"Single-File Plugins ({len(plugins)} found)")
table.add_column("Name", style="cyan")
table.add_column("Version")
table.add_column("Tools", style="green")
table.add_column("Path")
for plugin in plugins:
tools = plugin.get("tools", [])
tools_str = ", ".join(tools[:3])
if len(tools) > 3:
tools_str += f" (+{len(tools)-3})"
table.add_row(
plugin.get("name", "-"),
plugin.get("version", "-"),
tools_str or "-",
str(plugin.get("path", "-"))[:40],
)
console.print(table)
if load:
console.print("\n[green]Plugins loaded.[/green] Tools are now available.")
except ImportError:
typer.echo(f"Error: praisonaiagents package not found.", err=True)
raise typer.Exit(1)
except Exception as e:
typer.echo(f"Error discovering plugins: {e}", err=True)
raise typer.Exit(1)
@app.command("remove")
def plugins_remove(
name: str = typer.Argument(..., help="Plugin name or filename to remove"),
force: bool = typer.Option(False, "--force", "-f", help="Remove without confirmation"),
):
"""Remove a single-file plugin.
Removes a plugin from the user's plugin directory.
Examples:
praisonai plugins remove weather
praisonai plugins remove weather_plugin.py --force
"""
try:
from pathlib import Path
from praisonaiagents.plugins.discovery import get_default_plugin_dirs
# Normalize name
if not name.endswith(".py"):
name = f"{name}.py"
# Search in plugin directories
plugin_path = None
for plugin_dir in get_default_plugin_dirs():
candidate = plugin_dir / name
if candidate.exists():
plugin_path = candidate
break
# Also check user directory explicitly
if not plugin_path:
user_dir = Path.home() / ".praison" / "plugins"
candidate = user_dir / name
if candidate.exists():
plugin_path = candidate
if not plugin_path:
typer.echo(f"Plugin not found: {name}", err=True)
raise typer.Exit(1)
if not force:
confirm = typer.confirm(f"Remove plugin {plugin_path}?")
if not confirm:
typer.echo("Cancelled.")
raise typer.Exit(0)
plugin_path.unlink()
typer.echo(f"[green]✓[/green] Removed: {plugin_path}")
except ImportError:
# Fallback without praisonaiagents
from pathlib import Path
user_dir = Path.home() / ".praison" / "plugins"
if not name.endswith(".py"):
name = f"{name}.py"
plugin_path = user_dir / name
if plugin_path.exists():
if force or typer.confirm(f"Remove plugin {plugin_path}?"):
plugin_path.unlink()
typer.echo(f"[green]✓[/green] Removed: {plugin_path}")
else:
typer.echo(f"Plugin not found: {name}", err=True)
raise typer.Exit(1)
except typer.Exit:
raise
except Exception as e:
typer.echo(f"Error removing plugin: {e}", err=True)
raise typer.Exit(1)
def _get_available_plugins():
"""Get list of available plugins."""
# Built-in plugins
+1 -1
View File
@@ -57,7 +57,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==4.2.0 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==4.2.2 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+1 -1
View File
@@ -1 +1 @@
__version__ = "4.2.0"
__version__ = "4.2.2"
+27 -2
View File
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=1.2.0",
"praisonaiagents>=1.2.2",
"python-dotenv>=0.19.0",
"litellm>=1.81.0",
"PyYAML>=6.0",
@@ -69,7 +69,7 @@ realtime = [
"websockets>=12.0",
"plotly>=5.24.0",
"yfinance>=0.2.44",
"duckduckgo_search>=6.3.0",
"ddgs>=8.0.0",
"sqlalchemy>=2.0.36"
]
call = [
@@ -98,6 +98,31 @@ lite = [
# Minimal installation without TUI, litellm - just core agent functionality
# Use: pip install praisonai[lite] for lightweight deployments
]
all = [
# Full installation with all main features
"chainlit>=2.8.5,<=2.9.4",
"sqlalchemy>=2.0.36",
"aiosqlite>=0.20.0",
"greenlet>=3.0.3",
"gradio>=4.26.0",
"flask>=3.0.0",
"fastapi>=0.115.0",
"uvicorn>=0.34.0",
"sse-starlette>=1.6.0",
"tavily-python==0.5.0",
"crawl4ai>=0.7.0",
"playwright>=1.47.0",
"websockets>=12.0",
"plotly>=5.24.0",
"yfinance>=0.2.44",
"ddgs>=8.0.0",
]
bot = [
# Dependencies for Telegram, Discord, and Slack bots
"python-telegram-bot>=20.0",
"discord.py>=2.0.0",
"slack_sdk>=3.0.0",
]
dev = [
"textual-dev>=1.0.0",
"pytest>=7.0.0",
+1 -1
View File
@@ -5,6 +5,6 @@ python-dotenv>=1.0.1
instructor>=0.4.8
PyYAML>=6.0
praisonaiagents>=0.0.4
duckduckgo_search
ddgs
praisonai_tools
flask>=2.0.0
+12 -12
View File
@@ -31,17 +31,17 @@ PYTHON_CMD="${PRAISONAI_PYTHON:-}"
SKIP_VENV="${PRAISONAI_SKIP_VENV:-0}"
MIN_PYTHON_VERSION="3.10"
# Logging functions
# Logging functions - all output to stderr to avoid capturing in $()
log_info() {
echo -e "${BLUE}==>${NC} $1"
echo -e "${BLUE}==>${NC} $1" >&2
}
log_success() {
echo -e "${GREEN}✓${NC} $1"
echo -e "${GREEN}✓${NC} $1" >&2
}
log_warn() {
echo -e "${YELLOW}⚠${NC} $1"
echo -e "${YELLOW}⚠${NC} $1" >&2
}
log_error() {
@@ -49,7 +49,7 @@ log_error() {
}
log_step() {
echo -e "${CYAN}→${NC} $1"
echo -e "${CYAN}→${NC} $1" >&2
}
# Print banner
@@ -350,17 +350,17 @@ install_praisonai() {
pip_cmd="$venv_dir/Scripts/pip.exe"
fi
# Build install command
local install_pkg="praisonaiagents"
# Build install command - default is praisonai[all] for full experience
local install_pkg="praisonai[all]"
if [[ "$VERSION" != "latest" ]]; then
install_pkg="praisonaiagents==$VERSION"
install_pkg="praisonai[all]==$VERSION"
fi
# Add extras if specified
# Override extras if specified
if [[ -n "$EXTRAS" ]]; then
install_pkg="praisonaiagents[$EXTRAS]"
install_pkg="praisonai[$EXTRAS]"
if [[ "$VERSION" != "latest" ]]; then
install_pkg="praisonaiagents[$EXTRAS]==$VERSION"
install_pkg="praisonai[$EXTRAS]==$VERSION"
fi
fi
@@ -465,7 +465,7 @@ verify_installation() {
fi
# Check import
if $python_cmd -c "import praisonaiagents; print(f'Version: {praisonaiagents.__version__}')" 2>/dev/null; then
if $python_cmd -c "from praisonaiagents import Agent; print('Import successful')" 2>/dev/null; then
log_success "PraisonAI agents package verified"
else
log_error "Failed to import praisonaiagents"
+196 -36
View File
@@ -533,6 +533,26 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517, upload-time = "2024-10-18T12:32:54.066Z" },
]
[[package]]
name = "brotlicffi"
version = "1.2.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
]
sdist = { url = "https://files.pythonhosted.org/packages/84/85/57c314a6b35336efbbdc13e5fc9ae13f6b60a0647cfa7c1221178ac6d8ae/brotlicffi-1.2.0.0.tar.gz", hash = "sha256:34345d8d1f9d534fcac2249e57a4c3c8801a33c9942ff9f8574f67a175e17adb", size = 476682, upload-time = "2025-11-21T18:17:57.334Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/df/a72b284d8c7bef0ed5756b41c2eb7d0219a1dd6ac6762f1c7bdbc31ef3af/brotlicffi-1.2.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:9458d08a7ccde8e3c0afedbf2c70a8263227a68dea5ab13590593f4c0a4fd5f4", size = 432340, upload-time = "2025-11-21T18:17:42.277Z" },
{ url = "https://files.pythonhosted.org/packages/74/2b/cc55a2d1d6fb4f5d458fba44a3d3f91fb4320aa14145799fd3a996af0686/brotlicffi-1.2.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84e3d0020cf1bd8b8131f4a07819edee9f283721566fe044a20ec792ca8fd8b7", size = 1534002, upload-time = "2025-11-21T18:17:43.746Z" },
{ url = "https://files.pythonhosted.org/packages/e4/9c/d51486bf366fc7d6735f0e46b5b96ca58dc005b250263525a1eea3cd5d21/brotlicffi-1.2.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33cfb408d0cff64cd50bef268c0fed397c46fbb53944aa37264148614a62e990", size = 1536547, upload-time = "2025-11-21T18:17:45.729Z" },
{ url = "https://files.pythonhosted.org/packages/1b/37/293a9a0a7caf17e6e657668bebb92dfe730305999fe8c0e2703b8888789c/brotlicffi-1.2.0.0-cp38-abi3-win32.whl", hash = "sha256:23e5c912fdc6fd37143203820230374d24babd078fc054e18070a647118158f6", size = 343085, upload-time = "2025-11-21T18:17:48.887Z" },
{ url = "https://files.pythonhosted.org/packages/07/6b/6e92009df3b8b7272f85a0992b306b61c34b7ea1c4776643746e61c380ac/brotlicffi-1.2.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:f139a7cdfe4ae7859513067b736eb44d19fae1186f9e99370092f6915216451b", size = 378586, upload-time = "2025-11-21T18:17:50.531Z" },
{ url = "https://files.pythonhosted.org/packages/a4/ec/52488a0563f1663e2ccc75834b470650f4b8bcdea3132aef3bf67219c661/brotlicffi-1.2.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fa102a60e50ddbd08de86a63431a722ea216d9bc903b000bf544149cc9b823dc", size = 402002, upload-time = "2025-11-21T18:17:51.76Z" },
{ url = "https://files.pythonhosted.org/packages/e4/63/d4aea4835fd97da1401d798d9b8ba77227974de565faea402f520b37b10f/brotlicffi-1.2.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d3c4332fc808a94e8c1035950a10d04b681b03ab585ce897ae2a360d479037c", size = 406447, upload-time = "2025-11-21T18:17:53.614Z" },
{ url = "https://files.pythonhosted.org/packages/62/4e/5554ecb2615ff035ef8678d4e419549a0f7a28b3f096b272174d656749fb/brotlicffi-1.2.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb4eb5830026b79a93bf503ad32b2c5257315e9ffc49e76b2715cffd07c8e3db", size = 402521, upload-time = "2025-11-21T18:17:54.875Z" },
{ url = "https://files.pythonhosted.org/packages/b5/d3/b07f8f125ac52bbee5dc00ef0d526f820f67321bf4184f915f17f50a4657/brotlicffi-1.2.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3832c66e00d6d82087f20a972b2fc03e21cd99ef22705225a6f8f418a9158ecc", size = 374730, upload-time = "2025-11-21T18:17:56.334Z" },
]
[[package]]
name = "build"
version = "1.2.2.post1"
@@ -988,6 +1008,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" },
]
[[package]]
name = "ddgs"
version = "9.10.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "fake-useragent" },
{ name = "httpx", extra = ["brotli", "http2", "socks"] },
{ name = "lxml" },
{ name = "primp" },
]
sdist = { url = "https://files.pythonhosted.org/packages/07/76/8dc0323d1577037abad7a679f8af150ebb73a94995d3012de71a8898e6e6/ddgs-9.10.0.tar.gz", hash = "sha256:d9381ff75bdf1ad6691d3d1dc2be12be190d1d32ecd24f1002c492143c52c34f", size = 31491, upload-time = "2025-12-17T23:30:15.021Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/0e/d4b7d6a8df5074cf67bc14adead39955b0bf847c947ff6cad0bb527887f4/ddgs-9.10.0-py3-none-any.whl", hash = "sha256:81233d79309836eb03e7df2a0d2697adc83c47c342713132c0ba618f1f2c6eee", size = 40311, upload-time = "2025-12-17T23:30:13.606Z" },
]
[[package]]
name = "deprecated"
version = "1.2.15"
@@ -1000,6 +1036,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/8f/c7f227eb42cfeaddce3eb0c96c60cbca37797fa7b34f8e1aeadf6c5c0983/Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320", size = 9941, upload-time = "2024-11-15T14:42:03.315Z" },
]
[[package]]
name = "discord-py"
version = "2.6.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
{ name = "audioop-lts", marker = "python_full_version >= '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ce/e7/9b1dbb9b2fc07616132a526c05af23cfd420381793968a189ee08e12e35f/discord_py-2.6.4.tar.gz", hash = "sha256:44384920bae9b7a073df64ae9b14c8cf85f9274b5ad5d1d07bd5a67539de2da9", size = 1092623, upload-time = "2025-10-08T21:45:43.593Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/ae/3d3a89b06f005dc5fa8618528dde519b3ba7775c365750f7932b9831ef05/discord_py-2.6.4-py3-none-any.whl", hash = "sha256:2783b7fb7f8affa26847bfc025144652c294e8fe6e0f8877c67ed895749eb227", size = 1209284, upload-time = "2025-10-08T21:45:41.679Z" },
]
[[package]]
name = "diskcache"
version = "5.6.3"
@@ -1050,20 +1099,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/7c/e9fcff7623954d86bdc17782036cbf715ecab1bec4847c008557affe1ca8/docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637", size = 36533, upload-time = "2024-03-15T10:39:41.527Z" },
]
[[package]]
name = "duckduckgo-search"
version = "7.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "lxml" },
{ name = "primp" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/9a/1f0a49fafd5d203f3a9e73a46991b9034851b6a644fe14b50e5c8f06f765/duckduckgo_search-7.1.0.tar.gz", hash = "sha256:2276d60e3a57171058eaa7f0a57f09876f77e14a32dbdc97acc707aad134bbc3", size = 24810, upload-time = "2024-12-25T14:42:35.358Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/25/c3f678f065dffebf1c11a388b229164ccfcdaf4113f3de7695be3eafda2f/duckduckgo_search-7.1.0-py3-none-any.whl", hash = "sha256:2e51901992048108ea5c24d8e2c0373078d5088b802fba23eb124ee4d812c38f", size = 20471, upload-time = "2024-12-25T14:42:32.984Z" },
]
[[package]]
name = "durationpy"
version = "0.9"
@@ -1566,11 +1601,30 @@ wheels = [
name = "h11"
version = "0.14.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.13.*'",
"python_full_version >= '3.12.4' and python_full_version < '3.13'",
"python_full_version >= '3.12' and python_full_version < '3.12.4'",
"python_full_version == '3.11.*'",
"python_full_version < '3.11'",
]
sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload-time = "2022-09-25T15:40:01.519Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload-time = "2022-09-25T15:39:59.68Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
]
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "h2"
version = "4.2.0"
@@ -1610,15 +1664,38 @@ wheels = [
name = "httpcore"
version = "1.0.7"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.13.*'",
"python_full_version >= '3.12.4' and python_full_version < '3.13'",
"python_full_version >= '3.12' and python_full_version < '3.12.4'",
"python_full_version == '3.11.*'",
"python_full_version < '3.11'",
]
dependencies = [
{ name = "certifi" },
{ name = "h11" },
{ name = "certifi", marker = "python_full_version < '3.14'" },
{ name = "h11", version = "0.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196, upload-time = "2024-11-15T12:30:47.531Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551, upload-time = "2024-11-15T12:30:45.782Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
]
dependencies = [
{ name = "certifi", marker = "python_full_version >= '3.14'" },
{ name = "h11", version = "0.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httptools"
version = "0.6.4"
@@ -1662,7 +1739,8 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "httpcore", version = "1.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
{ name = "httpcore", version = "1.0.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
@@ -1671,9 +1749,16 @@ wheels = [
]
[package.optional-dependencies]
brotli = [
{ name = "brotli", marker = "platform_python_implementation == 'CPython'" },
{ name = "brotlicffi", marker = "platform_python_implementation != 'CPython'" },
]
http2 = [
{ name = "h2" },
]
socks = [
{ name = "socksio" },
]
[[package]]
name = "httpx-sse"
@@ -3811,6 +3896,24 @@ acp = [
agentops = [
{ name = "agentops" },
]
all = [
{ name = "aiosqlite" },
{ name = "chainlit" },
{ name = "crawl4ai" },
{ name = "ddgs" },
{ name = "fastapi" },
{ name = "flask" },
{ name = "gradio" },
{ name = "greenlet" },
{ name = "playwright" },
{ name = "plotly" },
{ name = "sqlalchemy" },
{ name = "sse-starlette" },
{ name = "tavily-python" },
{ name = "uvicorn" },
{ name = "websockets" },
{ name = "yfinance" },
]
api = [
{ name = "fastapi" },
{ name = "flask" },
@@ -3829,6 +3932,11 @@ autogen-v4 = [
{ name = "crewai" },
{ name = "praisonai-tools" },
]
bot = [
{ name = "discord-py" },
{ name = "python-telegram-bot" },
{ name = "slack-sdk" },
]
call = [
{ name = "fastapi" },
{ name = "flaml", extra = ["automl"] },
@@ -3873,7 +3981,7 @@ realtime = [
{ name = "aiosqlite" },
{ name = "chainlit" },
{ name = "crawl4ai" },
{ name = "duckduckgo-search" },
{ name = "ddgs" },
{ name = "greenlet" },
{ name = "plotly" },
{ name = "sqlalchemy" },
@@ -3892,6 +4000,7 @@ ui = [
requires-dist = [
{ name = "agent-client-protocol", marker = "extra == 'acp'", specifier = ">=0.7.0" },
{ name = "agentops", marker = "extra == 'agentops'", specifier = ">=0.3.12" },
{ name = "aiosqlite", marker = "extra == 'all'", specifier = ">=0.20.0" },
{ name = "aiosqlite", marker = "extra == 'chat'", specifier = ">=0.20.0" },
{ name = "aiosqlite", marker = "extra == 'code'", specifier = ">=0.20.0" },
{ name = "aiosqlite", marker = "extra == 'realtime'", specifier = ">=0.20.0" },
@@ -3899,22 +4008,30 @@ requires-dist = [
{ name = "autogen-agentchat", marker = "extra == 'autogen-v4'", specifier = ">=0.4.0" },
{ name = "autogen-core", marker = "extra == 'autogen-v4'", specifier = ">=0.4.0" },
{ name = "autogen-ext", extras = ["openai"], marker = "extra == 'autogen-v4'", specifier = ">=0.4.0" },
{ name = "chainlit", marker = "extra == 'all'", specifier = ">=2.8.5,<=2.9.4" },
{ name = "chainlit", marker = "extra == 'chat'", specifier = ">=2.8.5,<=2.9.4" },
{ name = "chainlit", marker = "extra == 'code'", specifier = ">=2.8.5,<=2.9.4" },
{ name = "chainlit", marker = "extra == 'realtime'", specifier = ">=2.8.5,<=2.9.4" },
{ name = "chainlit", marker = "extra == 'ui'", specifier = ">=2.8.5,<=2.9.4" },
{ name = "crawl4ai", marker = "extra == 'all'", specifier = ">=0.7.0" },
{ name = "crawl4ai", marker = "extra == 'chat'", specifier = ">=0.7.0" },
{ name = "crawl4ai", marker = "extra == 'code'", specifier = ">=0.7.0" },
{ name = "crawl4ai", marker = "extra == 'realtime'", specifier = ">=0.7.0" },
{ name = "crewai", marker = "extra == 'autogen'" },
{ name = "crewai", marker = "extra == 'autogen-v4'" },
{ name = "crewai", marker = "extra == 'crewai'", specifier = ">=0.157.0" },
{ name = "duckduckgo-search", marker = "extra == 'realtime'", specifier = ">=6.3.0" },
{ name = "ddgs", marker = "extra == 'all'", specifier = ">=8.0.0" },
{ name = "ddgs", marker = "extra == 'realtime'", specifier = ">=8.0.0" },
{ name = "discord-py", marker = "extra == 'bot'", specifier = ">=2.0.0" },
{ name = "fastapi", marker = "extra == 'all'", specifier = ">=0.115.0" },
{ name = "fastapi", marker = "extra == 'api'", specifier = ">=0.115.0" },
{ name = "fastapi", marker = "extra == 'call'", specifier = ">=0.95.0" },
{ name = "flaml", extras = ["automl"], marker = "extra == 'call'", specifier = ">=2.3.1" },
{ name = "flask", marker = "extra == 'all'", specifier = ">=3.0.0" },
{ name = "flask", marker = "extra == 'api'", specifier = ">=3.0.0" },
{ name = "gradio", marker = "extra == 'all'", specifier = ">=4.26.0" },
{ name = "gradio", marker = "extra == 'gradio'", specifier = ">=4.26.0" },
{ name = "greenlet", marker = "extra == 'all'", specifier = ">=3.0.3" },
{ name = "greenlet", marker = "extra == 'chat'", specifier = ">=3.0.3" },
{ name = "greenlet", marker = "extra == 'code'", specifier = ">=3.0.3" },
{ name = "greenlet", marker = "extra == 'realtime'", specifier = ">=3.0.3" },
@@ -3922,28 +4039,35 @@ requires-dist = [
{ name = "litellm", specifier = ">=1.81.0" },
{ name = "markdown", specifier = ">=3.5" },
{ name = "mcp", specifier = ">=1.20.0" },
{ name = "playwright", marker = "extra == 'all'", specifier = ">=1.47.0" },
{ name = "playwright", marker = "extra == 'chat'", specifier = ">=1.47.0" },
{ name = "playwright", marker = "extra == 'code'", specifier = ">=1.47.0" },
{ name = "plotly", marker = "extra == 'all'", specifier = ">=5.24.0" },
{ name = "plotly", marker = "extra == 'realtime'", specifier = ">=5.24.0" },
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.1.0" },
{ name = "praisonai-tools", marker = "extra == 'autogen-v4'", specifier = ">=0.1.0" },
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.1.0" },
{ name = "praisonaiagents", specifier = ">=1.2.0" },
{ name = "praisonaiagents", specifier = ">=1.2.2" },
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = "==0.2.29" },
{ name = "pyngrok", marker = "extra == 'call'", specifier = ">=1.4.0" },
{ name = "pyparsing", specifier = ">=3.0.0" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" },
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.0" },
{ name = "python-dotenv", specifier = ">=0.19.0" },
{ name = "python-telegram-bot", marker = "extra == 'bot'", specifier = ">=20.0" },
{ name = "pyyaml", specifier = ">=6.0" },
{ name = "rich", specifier = ">=13.7" },
{ name = "rich", marker = "extra == 'call'" },
{ name = "rich", marker = "extra == 'chat'" },
{ name = "slack-sdk", marker = "extra == 'bot'", specifier = ">=3.0.0" },
{ name = "sqlalchemy", marker = "extra == 'all'", specifier = ">=2.0.36" },
{ name = "sqlalchemy", marker = "extra == 'chat'", specifier = ">=2.0.36" },
{ name = "sqlalchemy", marker = "extra == 'code'", specifier = ">=2.0.36" },
{ name = "sqlalchemy", marker = "extra == 'realtime'", specifier = ">=2.0.36" },
{ name = "sqlalchemy", marker = "extra == 'ui'", specifier = ">=2.0.36" },
{ name = "sse-starlette", marker = "extra == 'all'", specifier = ">=1.6.0" },
{ name = "sse-starlette", marker = "extra == 'api'", specifier = ">=1.6.0" },
{ name = "tavily-python", marker = "extra == 'all'", specifier = "==0.5.0" },
{ name = "tavily-python", marker = "extra == 'chat'", specifier = "==0.5.0" },
{ name = "tavily-python", marker = "extra == 'code'", specifier = "==0.5.0" },
{ name = "tavily-python", marker = "extra == 'realtime'", specifier = "==0.5.0" },
@@ -3951,13 +4075,16 @@ requires-dist = [
{ name = "textual-dev", marker = "extra == 'dev'", specifier = ">=1.0.0" },
{ name = "twilio", marker = "extra == 'call'", specifier = ">=7.0.0" },
{ name = "typer", specifier = ">=0.9.0" },
{ name = "uvicorn", marker = "extra == 'all'", specifier = ">=0.34.0" },
{ name = "uvicorn", marker = "extra == 'api'", specifier = ">=0.34.0" },
{ name = "uvicorn", marker = "extra == 'call'", specifier = ">=0.20.0" },
{ name = "websockets", marker = "extra == 'all'", specifier = ">=12.0" },
{ name = "websockets", marker = "extra == 'call'", specifier = ">=12.0" },
{ name = "websockets", marker = "extra == 'realtime'", specifier = ">=12.0" },
{ name = "yfinance", marker = "extra == 'all'", specifier = ">=0.2.44" },
{ name = "yfinance", marker = "extra == 'realtime'", specifier = ">=0.2.44" },
]
provides-extras = ["ui", "gradio", "api", "agentops", "chat", "code", "realtime", "call", "train", "crewai", "autogen", "autogen-v4", "acp", "lite", "dev"]
provides-extras = ["ui", "gradio", "api", "agentops", "chat", "code", "realtime", "call", "train", "crewai", "autogen", "autogen-v4", "acp", "lite", "all", "bot", "dev"]
[[package]]
name = "praisonai-tools"
@@ -3973,7 +4100,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "1.2.0"
version = "1.2.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -3982,9 +4109,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/86/25/72f3b51aa817cbf21cca6c49daab8ee9d33ee78c9b8eb9b83c3b2b40c53a/praisonaiagents-1.2.0.tar.gz", hash = "sha256:99b148a409fbb7781561af9cce5e1d7c15b142ecf9958d82a7e0a639a6164eb1", size = 1162698, upload-time = "2026-01-31T15:06:43.911Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a5/fd/dc1591930e10108c04b21de9e620ef0910ad0d7c34ecb1a7616ce0ccc8c7/praisonaiagents-1.2.2.tar.gz", hash = "sha256:c790107dfd278a46c52a0191b77c13900f091fb5a7051195cb1926d9ca19e991", size = 1163165, upload-time = "2026-01-31T20:23:29.288Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/4e/372fa96a980fa79fdd4a8a77be83d3aed5edeea0fbee7db9f234522f57aa/praisonaiagents-1.2.0-py3-none-any.whl", hash = "sha256:550cdf76ddd788416c9cfa5a66a1be3985678abffd008b0034d87231ddebeeae", size = 1155602, upload-time = "2026-01-31T15:06:41.816Z" },
{ url = "https://files.pythonhosted.org/packages/0b/21/406275ba1c6f1d7dcef710c7aa62a6fccb5cafbbb4883a39fe48715837ea/praisonaiagents-1.2.2-py3-none-any.whl", hash = "sha256:8732cdd90526b1fba0feb192c97b8c0b2387eda2827cf89d7d9867cd02aeaad8", size = 1156030, upload-time = "2026-01-31T20:23:27.414Z" },
]
[[package]]
@@ -4006,18 +4133,18 @@ wheels = [
[[package]]
name = "primp"
version = "0.9.2"
version = "0.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9c/cd/2a861a12b73bd50f4d3b9c98a8747447573c9964eee6b29c053dc5e0ac2f/primp-0.9.2.tar.gz", hash = "sha256:5b95666c25b9107eab3c05a89cb7b1748d5122e57c57b25bfc3249d525c45300", size = 82950, upload-time = "2024-12-23T16:26:02.2Z" }
sdist = { url = "https://files.pythonhosted.org/packages/56/0b/a87556189da4de1fc6360ca1aa05e8335509633f836cdd06dd17f0743300/primp-0.15.0.tar.gz", hash = "sha256:1af8ea4b15f57571ff7fc5e282a82c5eb69bc695e19b8ddeeda324397965b30a", size = 113022, upload-time = "2025-04-17T11:41:05.315Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b2/02/1fb6259faf65f0e72180a3934653d4015f74eb8fcd6750098be316b8111c/primp-0.9.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a3179640e633be843ed5daba5c4e3086ad91f77c7bb40a9db06326f28d56b12b", size = 3143519, upload-time = "2024-12-23T16:25:54.205Z" },
{ url = "https://files.pythonhosted.org/packages/4e/73/a622a2020cccdabcd88614a6b8eb7b7b43c237b1ba10cfd6bc83195fdb1b/primp-0.9.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94a5da8ba25f74152b43bc16a7591dfb5d7d30a5827dc0a0f96a956f7d3616be", size = 2905167, upload-time = "2024-12-23T16:25:51.279Z" },
{ url = "https://files.pythonhosted.org/packages/97/d0/75d475bd73258911a58a59b23055acba95dde903df39b625c337a9d30dc7/primp-0.9.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0668c0abb6d56fc8b0a918179b1d0f68e7267c1dc632e2b683c618317e13143f", size = 3228652, upload-time = "2024-12-23T16:25:49.31Z" },
{ url = "https://files.pythonhosted.org/packages/9b/99/b528083a2c190258c5c810d88f508d63e454516fb8e6d02becdbf9a35b9f/primp-0.9.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a9c29a4b8eabfc28a1746d2fe93d33b9fcf2e81e642dd0e3eaecede60cc36b7d", size = 3194404, upload-time = "2024-12-23T16:25:45.691Z" },
{ url = "https://files.pythonhosted.org/packages/8d/d5/27baa9849c0f31e8771df67009597038d6a72f66b4154cc65e8c2114fbd6/primp-0.9.2-cp38-abi3-manylinux_2_34_armv7l.whl", hash = "sha256:04d499308a101b06b40f5fda1bdc795db5731cd0dfbb1a8873f4acd07c085b1d", size = 2959637, upload-time = "2024-12-23T16:25:47.55Z" },
{ url = "https://files.pythonhosted.org/packages/c6/b7/c43edea5e1da0ce231480fac30af2a9d06f1b3a319cf42a7cb1b85960702/primp-0.9.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4cd5daf39034a0a8c96cdc0c4c306184c6f2b1b2a0b39dc3294d79ed28a6f7fe", size = 3366274, upload-time = "2024-12-23T16:25:56.949Z" },
{ url = "https://files.pythonhosted.org/packages/97/e2/f94ea8baf7e61cf73b925ef0aa4767461fe7356f03a890dc874e2b12b2fd/primp-0.9.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d14653434837eb431b3cf7ca006647d7a196906e48bba96bb600ba2ba70bcdc", size = 3556571, upload-time = "2024-12-23T16:25:59.7Z" },
{ url = "https://files.pythonhosted.org/packages/72/9e/83e4edfd920a395596ddfb1dd1c8f7ec577e6bc42149adb357fd35dd03e4/primp-0.9.2-cp38-abi3-win_amd64.whl", hash = "sha256:80d9f07564dc9b25b1a9676df770561418557c124fedecae84f6491a1974b61d", size = 3095511, upload-time = "2024-12-23T16:26:03.246Z" },
{ url = "https://files.pythonhosted.org/packages/f5/5a/146ac964b99ea7657ad67eb66f770be6577dfe9200cb28f9a95baffd6c3f/primp-0.15.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1b281f4ca41a0c6612d4c6e68b96e28acfe786d226a427cd944baa8d7acd644f", size = 3178914, upload-time = "2025-04-17T11:40:59.558Z" },
{ url = "https://files.pythonhosted.org/packages/bc/8a/cc2321e32db3ce64d6e32950d5bcbea01861db97bfb20b5394affc45b387/primp-0.15.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:489cbab55cd793ceb8f90bb7423c6ea64ebb53208ffcf7a044138e3c66d77299", size = 2955079, upload-time = "2025-04-17T11:40:57.398Z" },
{ url = "https://files.pythonhosted.org/packages/c3/7b/cbd5d999a07ff2a21465975d4eb477ae6f69765e8fe8c9087dab250180d8/primp-0.15.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c18b45c23f94016215f62d2334552224236217aaeb716871ce0e4dcfa08eb161", size = 3281018, upload-time = "2025-04-17T11:40:55.308Z" },
{ url = "https://files.pythonhosted.org/packages/1b/6e/a6221c612e61303aec2bcac3f0a02e8b67aee8c0db7bdc174aeb8010f975/primp-0.15.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e985a9cba2e3f96a323722e5440aa9eccaac3178e74b884778e926b5249df080", size = 3255229, upload-time = "2025-04-17T11:40:47.811Z" },
{ url = "https://files.pythonhosted.org/packages/3b/54/bfeef5aca613dc660a69d0760a26c6b8747d8fdb5a7f20cb2cee53c9862f/primp-0.15.0-cp38-abi3-manylinux_2_34_armv7l.whl", hash = "sha256:6b84a6ffa083e34668ff0037221d399c24d939b5629cd38223af860de9e17a83", size = 3014522, upload-time = "2025-04-17T11:40:50.191Z" },
{ url = "https://files.pythonhosted.org/packages/ac/96/84078e09f16a1dad208f2fe0f8a81be2cf36e024675b0f9eec0c2f6e2182/primp-0.15.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:592f6079646bdf5abbbfc3b0a28dac8de943f8907a250ce09398cda5eaebd260", size = 3418567, upload-time = "2025-04-17T11:41:01.595Z" },
{ url = "https://files.pythonhosted.org/packages/6c/80/8a7a9587d3eb85be3d0b64319f2f690c90eb7953e3f73a9ddd9e46c8dc42/primp-0.15.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5a728e5a05f37db6189eb413d22c78bd143fa59dd6a8a26dacd43332b3971fe8", size = 3606279, upload-time = "2025-04-17T11:41:03.61Z" },
{ url = "https://files.pythonhosted.org/packages/0c/dd/f0183ed0145e58cf9d286c1b2c14f63ccee987a4ff79ac85acc31b5d86bd/primp-0.15.0-cp38-abi3-win_amd64.whl", hash = "sha256:aeb6bd20b06dfc92cfe4436939c18de88a58c640752cf7f30d9e4ae893cdec32", size = 3149967, upload-time = "2025-04-17T11:41:07.067Z" },
]
[[package]]
@@ -4677,6 +4804,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/5f/bccb8d210f9a2bb5d5723cfe3034d940501045f8c1e074ec3c2574d2519c/python_socketio-5.12.0-py3-none-any.whl", hash = "sha256:50fe22fd2b0aa634df3e74489e42217b09af2fb22eee45f2c006df36d1d08cb9", size = 77036, upload-time = "2024-12-18T17:54:45.088Z" },
]
[[package]]
name = "python-telegram-bot"
version = "22.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpcore", version = "1.0.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
{ name = "httpx" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cd/9b/8df90c85404166a6631e857027866263adb27440d8af1dbeffbdc4f0166c/python_telegram_bot-22.6.tar.gz", hash = "sha256:50ae8cc10f8dff01445628687951020721f37956966b92a91df4c1bf2d113742", size = 1503761, upload-time = "2026-01-24T13:57:00.269Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/97/7298f0e1afe3a1ae52ff4c5af5087ed4de319ea73eb3b5c8c4dd4e76e708/python_telegram_bot-22.6-py3-none-any.whl", hash = "sha256:e598fe171c3dde2dfd0f001619ee9110eece66761a677b34719fb18934935ce0", size = 737267, upload-time = "2026-01-24T13:56:58.06Z" },
]
[[package]]
name = "pytz"
version = "2024.2"
@@ -5319,6 +5459,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "slack-sdk"
version = "3.39.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b6/dd/645f3eb93fce38eadbb649e85684730b1fc3906c2674ca59bddc2ca2bd2e/slack_sdk-3.39.0.tar.gz", hash = "sha256:6a56be10dc155c436ff658c6b776e1c082e29eae6a771fccf8b0a235822bbcb1", size = 247207, upload-time = "2025-11-20T15:27:57.556Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/1f/32bcf088e535c1870b1a1f2e3b916129c66fdfe565a793316317241d41e5/slack_sdk-3.39.0-py2.py3-none-any.whl", hash = "sha256:b1556b2f5b8b12b94e5ea3f56c4f2c7f04462e4e1013d325c5764ff118044fa8", size = 309850, upload-time = "2025-11-20T15:27:55.729Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
@@ -5337,6 +5486,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002, upload-time = "2021-11-16T18:38:34.792Z" },
]
[[package]]
name = "socksio"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055, upload-time = "2020-04-17T15:50:34.664Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763, upload-time = "2020-04-17T15:50:31.878Z" },
]
[[package]]
name = "soupsieve"
version = "2.6"
@@ -5994,7 +6152,8 @@ version = "0.40.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
{ name = "h11", version = "0.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
{ name = "h11", version = "0.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" }
@@ -6252,7 +6411,8 @@ name = "wsproto"
version = "1.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "h11" },
{ name = "h11", version = "0.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
{ name = "h11", version = "0.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" }
wheels = [