echo-agent 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {echo_agent-0.2.1 → echo_agent-0.2.3}/PKG-INFO +4 -1
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/__init__.py +1 -1
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/loop.py +8 -3
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/pipeline/context_stage.py +39 -1
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/pipeline/inference_stage.py +38 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/__init__.py +42 -25
- echo_agent-0.2.3/echo_agent/agent/tools/image_gen_fal.py +231 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/app.py +6 -3
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/base.py +15 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/dingtalk.py +2 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/email.py +2 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/feishu.py +2 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/matrix.py +2 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/qqbot.py +2 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/webhook.py +2 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/wecom.py +2 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/weixin.py +2 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/whatsapp.py +2 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/i18n/en.py +8 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/i18n/zh.py +8 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/setup.py +32 -10
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/config/schema.py +11 -2
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/dependencies/lazy_deps.py +1 -0
- echo_agent-0.2.3/echo_agent/gateway/api/__init__.py +54 -0
- echo_agent-0.2.3/echo_agent/gateway/api/channels.py +53 -0
- echo_agent-0.2.3/echo_agent/gateway/api/config.py +85 -0
- echo_agent-0.2.3/echo_agent/gateway/api/knowledge.py +136 -0
- echo_agent-0.2.3/echo_agent/gateway/api/lifecycle.py +41 -0
- echo_agent-0.2.3/echo_agent/gateway/api/memory.py +137 -0
- echo_agent-0.2.3/echo_agent/gateway/api/skills.py +66 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/health.py +8 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/server.py +34 -1
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/security/capabilities.py +2 -2
- {echo_agent-0.2.1 → echo_agent-0.2.3}/pyproject.toml +3 -2
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/creative/ppt-author/scripts/create_pptx.py +15 -10
- {echo_agent-0.2.1 → echo_agent-0.2.3}/.gitignore +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/LICENSE +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/README.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/__main__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/a2a/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/a2a/client.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/a2a/models.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/a2a/protocol.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/a2a/server.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/approval_gate.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/compression/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/compression/assembler.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/compression/boundary.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/compression/compressor.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/compression/engine.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/compression/pruner.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/compression/summarizer.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/compression/types.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/compression/validator.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/consolidation.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/context.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/context_cache.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/executors/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/executors/base.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/executors/factory.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/executors/remote.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/multi_agent/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/multi_agent/audit.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/multi_agent/error_messages.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/multi_agent/error_types.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/multi_agent/models.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/multi_agent/registry.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/multi_agent/runtime.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/pipeline/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/pipeline/response_stage.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/pipeline/types.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/planning/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/planning/models.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/planning/planner.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/planning/reflection.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/planning/strategies.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/streaming.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/base.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/circuit_breaker.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/clarify.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/code_exec.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/cronjob.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/delegate.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/filesystem.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/image_gen.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/knowledge.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/memory.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/message.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/notify.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/patch.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/process.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/registry.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/search.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/session_search.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/shell.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/skill_install.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/skills.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/task.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/todo.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/tts.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/vision.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/web.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/agent/tools/workflow.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/bus/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/bus/events.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/bus/queue.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/bus/rate_limiter.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/cli.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/cron.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/discord.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/manager.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/qqbot_media.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/slack.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/channels/telegram.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/colors.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/evolution_cmd.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/i18n/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/plugins_cmd.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/prompt.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/service.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/cli/status.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/config/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/config/default.yaml +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/config/loader.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/dependencies/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/dependencies/cli.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/dependencies/skill_require.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evaluation/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evaluation/dataset.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evaluation/metrics.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evaluation/reporter.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evaluation/runner.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evolution/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evolution/engine.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evolution/evolver.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evolution/gate.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evolution/recorder.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evolution/scheduler.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evolution/store.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evolution/tools.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evolution/types.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/evolution/validation.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/auth.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/editor.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/hooks.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/media.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/rate_limiter.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/router.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/session_context.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/session_policy.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/gateway/static/index.html +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/knowledge/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/knowledge/index.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/mcp/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/mcp/client.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/mcp/manager.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/mcp/oauth.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/mcp/security.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/mcp/tool_adapter.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/mcp/transport.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/memory/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/memory/consolidator.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/memory/contradiction.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/memory/forgetting.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/memory/retrieval.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/memory/reviewer.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/memory/store.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/memory/tiers.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/memory/types.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/memory/vectors.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/credential_pool.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/inference.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/provider.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/providers/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/providers/anthropic_provider.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/providers/bedrock_provider.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/providers/format_utils.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/providers/gemini_provider.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/providers/openai_provider.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/providers/openrouter_provider.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/rate_limiter.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/router.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/stub.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/models/tokenizer.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/observability/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/observability/monitor.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/observability/spans.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/observability/telemetry.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/permissions/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/permissions/allowlist.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/permissions/manager.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/plugins/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/plugins/context.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/plugins/errors.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/plugins/hooks.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/plugins/loader.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/plugins/manager.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/plugins/manifest.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/plugins/sandbox.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/runtime_paths.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/scheduler/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/scheduler/delivery.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/scheduler/service.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/security/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/security/guards.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/security/normalizer.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/security/path_policy.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/security/risk_classifier.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/security/smart_approval.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/security/tokenizer.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/security/tool_policy.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/session/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/session/manager.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/session/media_ref.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/skills/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/skills/manager.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/skills/reviewer.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/skills/store.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/storage/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/storage/backend.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/storage/sqlite.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/tasks/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/tasks/manager.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/tasks/models.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/tasks/workflow.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/tools/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/tools/base.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/utils/__init__.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/utils/async_io.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/echo_agent/utils/text.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/scripts/install.sh +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/creative/excel-author/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/creative/excel-author/scripts/create_xlsx.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/creative/image-gen/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/creative/image-gen/scripts/generate_image.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/creative/meme-gen/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/creative/meme-gen/scripts/make_meme.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/creative/ppt-author/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/development/code-runner/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/development/code-runner/scripts/safe_exec.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/development/github-ops/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/development/plan/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/development/skill-creator/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/development/skill-creator/scripts/init_skill.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/development/skill-creator/scripts/package_skill.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/development/skill-creator/scripts/quick_validate.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/development/workflow-chain/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/development/workflow-chain/scripts/workflow_engine.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/devops/docker-manage/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/devops/system-monitor/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/devops/system-monitor/scripts/system_check.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/finance/finance-tracker/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/finance/finance-tracker/scripts/finance_manager.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/finance/stocks/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/finance/stocks/scripts/market_query.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/health/fitness-nutrition/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/health/fitness-nutrition/scripts/health_query.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/learning/flashcards/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/learning/flashcards/scripts/flashcard_engine.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/media/tts-voice/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/media/tts-voice/scripts/text_to_speech.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/media/voice-note/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/media/voice-note/scripts/voice_process.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/calendar/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/calendar/scripts/calendar_client.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/daily-briefing/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/daily-briefing/scripts/generate_briefing.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/email-assistant/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/email-assistant/scripts/email_client.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/note-taking/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/note-taking/scripts/notes_manager.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/notion-sync/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/notion-sync/scripts/notion_client.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/ocr-document/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/ocr-document/scripts/extract_document.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/reminder/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/reminder/scripts/reminder_store.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/summarize/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/productivity/weather/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/research/arxiv/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/research/arxiv/scripts/search_arxiv.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/research/deep-research/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/research/deep-research/scripts/research_report.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/research/rss-watcher/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/research/rss-watcher/scripts/feed_monitor.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/research/web-extract/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/research/web-extract/scripts/extract_url.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/research/web-search/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/research/web-search/scripts/web_search.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/utility/calculator/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/utility/calculator/scripts/calc.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/utility/file-convert/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/utility/file-convert/scripts/convert.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/utility/maps-poi/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/utility/maps-poi/scripts/geo_query.py +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/utility/text-tools/SKILL.md +0 -0
- {echo_agent-0.2.1 → echo_agent-0.2.3}/skills/utility/text-tools/scripts/text_process.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: echo-agent
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: A modular AI agent framework with multi-channel support
|
|
5
5
|
Author: Echo Agent contributors
|
|
6
6
|
License: MIT
|
|
@@ -25,6 +25,7 @@ Requires-Dist: anthropic>=0.40; extra == 'all'
|
|
|
25
25
|
Requires-Dist: boto3>=1.34; extra == 'all'
|
|
26
26
|
Requires-Dist: cryptography>=41.0; extra == 'all'
|
|
27
27
|
Requires-Dist: faiss-cpu>=1.7; extra == 'all'
|
|
28
|
+
Requires-Dist: fal-client>=0.5; extra == 'all'
|
|
28
29
|
Requires-Dist: google-generativeai>=0.8; extra == 'all'
|
|
29
30
|
Requires-Dist: openai>=1.30; extra == 'all'
|
|
30
31
|
Requires-Dist: psutil>=5.9; extra == 'all'
|
|
@@ -45,6 +46,8 @@ Provides-Extra: dev
|
|
|
45
46
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
46
47
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
47
48
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
49
|
+
Provides-Extra: fal
|
|
50
|
+
Requires-Dist: fal-client>=0.5; extra == 'fal'
|
|
48
51
|
Provides-Extra: gemini
|
|
49
52
|
Requires-Dist: google-generativeai>=0.8; extra == 'gemini'
|
|
50
53
|
Provides-Extra: openai
|
|
@@ -259,6 +259,7 @@ class AgentLoop:
|
|
|
259
259
|
memory_snapshots=self._memory_snapshots,
|
|
260
260
|
snapshot_enabled=self._snapshot_enabled,
|
|
261
261
|
tool_definitions_fn=self.tools.get_definitions,
|
|
262
|
+
bus=bus,
|
|
262
263
|
)
|
|
263
264
|
self._inference_stage = InferenceStage(
|
|
264
265
|
config=config,
|
|
@@ -730,9 +731,13 @@ class AgentLoop:
|
|
|
730
731
|
return template
|
|
731
732
|
|
|
732
733
|
def _should_stream_channel(self, channel: str) -> bool:
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
734
|
+
channels = set(self.config.channels.stream_channels)
|
|
735
|
+
if channel in channels:
|
|
736
|
+
return True
|
|
737
|
+
for pattern in channels:
|
|
738
|
+
if pattern.endswith(":*") and channel.startswith(pattern[:-1]):
|
|
739
|
+
return True
|
|
740
|
+
return False
|
|
736
741
|
|
|
737
742
|
async def process_direct(self, content: str, session_key: str = "cli:direct") -> str:
|
|
738
743
|
"""Process a message directly (for CLI or testing)."""
|
|
@@ -15,7 +15,7 @@ from echo_agent.agent.context import (
|
|
|
15
15
|
build_skills_context,
|
|
16
16
|
)
|
|
17
17
|
from echo_agent.agent.pipeline.types import PipelineContext
|
|
18
|
-
from echo_agent.bus.events import InboundEvent
|
|
18
|
+
from echo_agent.bus.events import InboundEvent, OutboundEvent
|
|
19
19
|
from echo_agent.session.manager import Session
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -56,6 +56,7 @@ class ContextStage:
|
|
|
56
56
|
memory_snapshots: OrderedDict,
|
|
57
57
|
snapshot_enabled: bool,
|
|
58
58
|
tool_definitions_fn: Any,
|
|
59
|
+
bus: Any = None,
|
|
59
60
|
):
|
|
60
61
|
self._config = config
|
|
61
62
|
self._sessions = sessions
|
|
@@ -71,6 +72,19 @@ class ContextStage:
|
|
|
71
72
|
self._memory_snapshots = memory_snapshots
|
|
72
73
|
self._snapshot_enabled = snapshot_enabled
|
|
73
74
|
self._tool_definitions_fn = tool_definitions_fn
|
|
75
|
+
self._bus = bus
|
|
76
|
+
|
|
77
|
+
async def _emit_progress(self, event: InboundEvent, metadata: dict[str, Any]) -> None:
|
|
78
|
+
if not getattr(self._config.gateway, 'emit_progress_events', True):
|
|
79
|
+
return
|
|
80
|
+
out = OutboundEvent.text_reply(
|
|
81
|
+
channel=event.channel, chat_id=event.chat_id, text="", reply_to_id=event.reply_to_id,
|
|
82
|
+
)
|
|
83
|
+
out.is_final = False
|
|
84
|
+
out.message_kind = "progress"
|
|
85
|
+
out.metadata = {"_progress": True, "_inbound_event_id": event.event_id}
|
|
86
|
+
out.metadata.update(metadata)
|
|
87
|
+
await self._bus.publish_outbound(out)
|
|
74
88
|
|
|
75
89
|
async def build(
|
|
76
90
|
self,
|
|
@@ -155,6 +169,18 @@ class ContextStage:
|
|
|
155
169
|
"Relevant memory:\n"
|
|
156
170
|
+ "\n".join(f"- {r.key}: {r.content}" for r, _ in scored)
|
|
157
171
|
)
|
|
172
|
+
if publish_response and self._bus:
|
|
173
|
+
_debug = getattr(self._config.gateway, 'progress_debug', False)
|
|
174
|
+
_mem_meta: dict[str, Any] = {
|
|
175
|
+
"progress_type": "memory_retrieved",
|
|
176
|
+
"count": len(scored),
|
|
177
|
+
}
|
|
178
|
+
if _debug:
|
|
179
|
+
_mem_meta["entries"] = [
|
|
180
|
+
{"key": r.key, "content_preview": r.content[:100]}
|
|
181
|
+
for r, _ in scored[:5]
|
|
182
|
+
]
|
|
183
|
+
await self._emit_progress(event, _mem_meta)
|
|
158
184
|
|
|
159
185
|
if self._knowledge:
|
|
160
186
|
knowledge_results = self._knowledge.search(
|
|
@@ -165,6 +191,18 @@ class ContextStage:
|
|
|
165
191
|
knowledge_context = self._knowledge.format_results(knowledge_results)
|
|
166
192
|
if knowledge_context:
|
|
167
193
|
retrieval_parts.append(knowledge_context)
|
|
194
|
+
if publish_response and self._bus:
|
|
195
|
+
_debug = getattr(self._config.gateway, 'progress_debug', False)
|
|
196
|
+
_know_meta: dict[str, Any] = {
|
|
197
|
+
"progress_type": "knowledge_cited",
|
|
198
|
+
"count": len(knowledge_results) if knowledge_results else 0,
|
|
199
|
+
}
|
|
200
|
+
if _debug:
|
|
201
|
+
_know_meta["citations"] = [
|
|
202
|
+
{"path": getattr(r, 'path', ''), "chunk_preview": getattr(r, 'text', '')[:200], "score": getattr(r, 'score', 0.0)}
|
|
203
|
+
for r in (knowledge_results[:5] if knowledge_results else [])
|
|
204
|
+
]
|
|
205
|
+
await self._emit_progress(event, _know_meta)
|
|
168
206
|
|
|
169
207
|
task_type = self._infer_task_type(event.text)
|
|
170
208
|
|
|
@@ -92,6 +92,20 @@ class InferenceStage:
|
|
|
92
92
|
out.metadata.update({"_progress": True, "_tool_hint": tool_hint, "_inbound_event_id": event.event_id})
|
|
93
93
|
await self._bus.publish_outbound(out)
|
|
94
94
|
|
|
95
|
+
async def _emit_tool_event(metadata: dict[str, Any]) -> None:
|
|
96
|
+
if not ctx.publish_response:
|
|
97
|
+
return
|
|
98
|
+
if not getattr(self._config.gateway, 'emit_progress_events', True):
|
|
99
|
+
return
|
|
100
|
+
out = OutboundEvent.text_reply(
|
|
101
|
+
channel=event.channel, chat_id=event.chat_id, text="", reply_to_id=event.reply_to_id,
|
|
102
|
+
)
|
|
103
|
+
out.is_final = False
|
|
104
|
+
out.message_kind = "progress"
|
|
105
|
+
out.metadata = {"_progress": True, "_inbound_event_id": event.event_id}
|
|
106
|
+
out.metadata.update(metadata)
|
|
107
|
+
await self._bus.publish_outbound(out)
|
|
108
|
+
|
|
95
109
|
# Standard inference loop
|
|
96
110
|
response_text = ""
|
|
97
111
|
should_review_skills = False
|
|
@@ -272,6 +286,19 @@ class InferenceStage:
|
|
|
272
286
|
if _hook_cancelled:
|
|
273
287
|
continue
|
|
274
288
|
|
|
289
|
+
import time as _time
|
|
290
|
+
_tool_start_ts = _time.monotonic()
|
|
291
|
+
|
|
292
|
+
_debug_progress = getattr(self._config.gateway, 'progress_debug', False)
|
|
293
|
+
_tool_start_meta: dict[str, Any] = {
|
|
294
|
+
"progress_type": "tool_call",
|
|
295
|
+
"tool": tool_call.name,
|
|
296
|
+
"status": "started",
|
|
297
|
+
}
|
|
298
|
+
if _debug_progress:
|
|
299
|
+
_tool_start_meta["args"] = str(tool_call.arguments)[:500]
|
|
300
|
+
await _emit_tool_event(_tool_start_meta)
|
|
301
|
+
|
|
275
302
|
result = await self._tools.execute(tool_call.name, tool_call.arguments, tool_exec_ctx)
|
|
276
303
|
|
|
277
304
|
# post_tool_call hook
|
|
@@ -280,6 +307,17 @@ class InferenceStage:
|
|
|
280
307
|
"post_tool_call", result, tool_call.name, tool_call.arguments, tool_exec_ctx,
|
|
281
308
|
)
|
|
282
309
|
|
|
310
|
+
_tool_duration_ms = int((_time.monotonic() - _tool_start_ts) * 1000)
|
|
311
|
+
_tool_result_meta: dict[str, Any] = {
|
|
312
|
+
"progress_type": "tool_result",
|
|
313
|
+
"tool": tool_call.name,
|
|
314
|
+
"duration_ms": _tool_duration_ms,
|
|
315
|
+
"status": "done" if result.success else "error",
|
|
316
|
+
}
|
|
317
|
+
if _debug_progress:
|
|
318
|
+
_tool_result_meta["result_preview"] = result.text[:500]
|
|
319
|
+
await _emit_tool_event(_tool_result_meta)
|
|
320
|
+
|
|
283
321
|
result_text = result.text
|
|
284
322
|
if len(result_text) > self._MAX_TOOL_RESULT_CHARS:
|
|
285
323
|
result_text = result_text[:self._MAX_TOOL_RESULT_CHARS] + "\n...(truncated)"
|
|
@@ -189,30 +189,42 @@ def _infer_tts_model(api_base: str) -> str:
|
|
|
189
189
|
|
|
190
190
|
def _try_register_image_gen(tools: list[Tool], config: Config, provider: LLMProvider | None = None) -> None:
|
|
191
191
|
ig = getattr(config.tools, "image_gen", None)
|
|
192
|
-
|
|
192
|
+
backend = getattr(ig, "backend", "openai") if ig else "openai"
|
|
193
|
+
|
|
194
|
+
if backend == "fal":
|
|
195
|
+
fal_key = getattr(ig, "fal_key", "") if ig else ""
|
|
196
|
+
fal_model = getattr(ig, "fal_model", "") if ig else ""
|
|
197
|
+
if not fal_key:
|
|
198
|
+
logger.info(
|
|
199
|
+
"image_generate tool not registered: no fal_key configured. "
|
|
200
|
+
"Set tools.image_gen.fal_key and tools.image_gen.fal_model in config to enable."
|
|
201
|
+
)
|
|
202
|
+
return
|
|
203
|
+
from echo_agent.agent.tools.image_gen_fal import FalImageGenTool, FAL_MODELS
|
|
204
|
+
if fal_model and fal_model not in FAL_MODELS:
|
|
205
|
+
logger.warning(
|
|
206
|
+
"image_generate: configured fal_model '{}' is not in the built-in catalog. "
|
|
207
|
+
"Supported: {}. The tool will error at execution time.",
|
|
208
|
+
fal_model, ", ".join(sorted(FAL_MODELS.keys())),
|
|
209
|
+
)
|
|
210
|
+
tools.append(FalImageGenTool(fal_key=fal_key, model=fal_model))
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
api_key = getattr(ig, "api_key", "") if ig else ""
|
|
193
214
|
api_base = getattr(ig, "api_base", "") if ig else ""
|
|
194
215
|
model = getattr(ig, "model", "") if ig else ""
|
|
195
216
|
|
|
196
|
-
if explicit_key:
|
|
197
|
-
# User explicitly configured image_gen — use their settings as-is
|
|
198
|
-
api_key = explicit_key
|
|
199
|
-
elif _is_openai_compatible_provider(provider):
|
|
200
|
-
# Fallback only for OpenAI-compatible providers — use unwrapped to get real key/base
|
|
201
|
-
real = _unwrap_provider(provider)
|
|
202
|
-
api_key = getattr(real, "api_key", "")
|
|
203
|
-
if not api_base:
|
|
204
|
-
api_base = getattr(real, "api_base", "")
|
|
205
|
-
# Reset default model so we infer from api_base
|
|
206
|
-
model = ""
|
|
207
|
-
else:
|
|
208
|
-
return
|
|
209
|
-
|
|
210
217
|
if not api_key:
|
|
218
|
+
if _is_openai_compatible_provider(provider):
|
|
219
|
+
logger.info(
|
|
220
|
+
"image_generate tool not registered: no explicit image_gen.api_key configured. "
|
|
221
|
+
"Set tools.image_gen.api_key, tools.image_gen.api_base, and tools.image_gen.model in config to enable."
|
|
222
|
+
)
|
|
211
223
|
return
|
|
212
224
|
|
|
213
|
-
|
|
214
|
-
if not model or (not explicit_key and model == "dall-e-3"):
|
|
225
|
+
if not model:
|
|
215
226
|
model = _infer_image_model(api_base)
|
|
227
|
+
logger.debug("image_generate: model not configured, inferred '{}' from api_base", model)
|
|
216
228
|
|
|
217
229
|
from echo_agent.agent.tools.image_gen import ImageGenTool
|
|
218
230
|
tools.append(ImageGenTool(api_key=api_key, api_base=api_base, model=model))
|
|
@@ -222,18 +234,23 @@ def _try_register_tts(tools: list[Tool], config: Config, ws: str, provider: LLMP
|
|
|
222
234
|
from echo_agent.agent.tools.tts import TTSTool
|
|
223
235
|
tts_cfg = getattr(config.tools, "tts", None)
|
|
224
236
|
openai_key = getattr(tts_cfg, "openai_api_key", "") if tts_cfg else ""
|
|
225
|
-
openai_base = ""
|
|
237
|
+
openai_base = getattr(tts_cfg, "openai_api_base", "") if tts_cfg else ""
|
|
238
|
+
tts_model = getattr(tts_cfg, "model", "") if tts_cfg else ""
|
|
226
239
|
default_backend = getattr(tts_cfg, "default_backend", "edge") if tts_cfg else "edge"
|
|
227
240
|
default_voice = getattr(tts_cfg, "default_voice", "") if tts_cfg else ""
|
|
228
241
|
|
|
229
|
-
# Fallback only for OpenAI-compatible providers
|
|
230
242
|
if not openai_key and _is_openai_compatible_provider(provider):
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
243
|
+
logger.info(
|
|
244
|
+
"TTS openai backend not available: no explicit tts.openai_api_key configured. "
|
|
245
|
+
"Set tools.tts.openai_api_key, tools.tts.openai_api_base, and tools.tts.model in config to enable. "
|
|
246
|
+
"edge backend remains available without configuration."
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
if not tts_model and openai_key:
|
|
250
|
+
tts_model = _infer_tts_model(openai_base)
|
|
251
|
+
logger.debug("TTS: model not configured, inferred '{}' from openai_api_base", tts_model)
|
|
252
|
+
if not tts_model:
|
|
253
|
+
tts_model = "tts-1"
|
|
237
254
|
|
|
238
255
|
tools.append(TTSTool(
|
|
239
256
|
workspace=ws,
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Image generation tool — FAL.ai backend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import threading
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from echo_agent.agent.tools.base import Tool, ToolExecutionContext, ToolResult
|
|
11
|
+
|
|
12
|
+
DEFAULT_MODEL = "fal-ai/flux/schnell"
|
|
13
|
+
|
|
14
|
+
FAL_MODELS: dict[str, dict[str, Any]] = {
|
|
15
|
+
"fal-ai/flux/schnell": {
|
|
16
|
+
"display": "FLUX Schnell",
|
|
17
|
+
"size_style": "image_size_preset",
|
|
18
|
+
"sizes": {
|
|
19
|
+
"landscape": "landscape_16_9",
|
|
20
|
+
"square": "square_hd",
|
|
21
|
+
"portrait": "portrait_16_9",
|
|
22
|
+
},
|
|
23
|
+
"defaults": {
|
|
24
|
+
"num_images": 1,
|
|
25
|
+
"output_format": "png",
|
|
26
|
+
"enable_safety_checker": False,
|
|
27
|
+
"sync_mode": True,
|
|
28
|
+
},
|
|
29
|
+
"supports": {
|
|
30
|
+
"prompt", "image_size", "num_images", "output_format",
|
|
31
|
+
"enable_safety_checker", "sync_mode", "seed",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
"fal-ai/flux-2-pro": {
|
|
35
|
+
"display": "FLUX 2 Pro",
|
|
36
|
+
"size_style": "image_size_preset",
|
|
37
|
+
"sizes": {
|
|
38
|
+
"landscape": "landscape_16_9",
|
|
39
|
+
"square": "square_hd",
|
|
40
|
+
"portrait": "portrait_16_9",
|
|
41
|
+
},
|
|
42
|
+
"defaults": {
|
|
43
|
+
"num_inference_steps": 50,
|
|
44
|
+
"guidance_scale": 4.5,
|
|
45
|
+
"num_images": 1,
|
|
46
|
+
"output_format": "png",
|
|
47
|
+
"enable_safety_checker": False,
|
|
48
|
+
"sync_mode": True,
|
|
49
|
+
},
|
|
50
|
+
"supports": {
|
|
51
|
+
"prompt", "image_size", "num_inference_steps", "guidance_scale",
|
|
52
|
+
"num_images", "output_format", "enable_safety_checker",
|
|
53
|
+
"sync_mode", "seed",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
"fal-ai/ideogram/v3": {
|
|
57
|
+
"display": "Ideogram V3",
|
|
58
|
+
"size_style": "image_size_preset",
|
|
59
|
+
"sizes": {
|
|
60
|
+
"landscape": "landscape_16_9",
|
|
61
|
+
"square": "square_hd",
|
|
62
|
+
"portrait": "portrait_16_9",
|
|
63
|
+
},
|
|
64
|
+
"defaults": {
|
|
65
|
+
"rendering_speed": "BALANCED",
|
|
66
|
+
"expand_prompt": True,
|
|
67
|
+
"style": "AUTO",
|
|
68
|
+
},
|
|
69
|
+
"supports": {
|
|
70
|
+
"prompt", "image_size", "rendering_speed", "expand_prompt",
|
|
71
|
+
"style", "seed",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
"fal-ai/recraft/v4/pro/text-to-image": {
|
|
75
|
+
"display": "Recraft V4 Pro",
|
|
76
|
+
"size_style": "image_size_preset",
|
|
77
|
+
"sizes": {
|
|
78
|
+
"landscape": "landscape_16_9",
|
|
79
|
+
"square": "square_hd",
|
|
80
|
+
"portrait": "portrait_16_9",
|
|
81
|
+
},
|
|
82
|
+
"defaults": {
|
|
83
|
+
"enable_safety_checker": False,
|
|
84
|
+
},
|
|
85
|
+
"supports": {
|
|
86
|
+
"prompt", "image_size", "enable_safety_checker",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
"fal-ai/qwen-image": {
|
|
90
|
+
"display": "Qwen Image",
|
|
91
|
+
"size_style": "image_size_preset",
|
|
92
|
+
"sizes": {
|
|
93
|
+
"landscape": "landscape_16_9",
|
|
94
|
+
"square": "square_hd",
|
|
95
|
+
"portrait": "portrait_16_9",
|
|
96
|
+
},
|
|
97
|
+
"defaults": {
|
|
98
|
+
"num_inference_steps": 30,
|
|
99
|
+
"guidance_scale": 2.5,
|
|
100
|
+
"num_images": 1,
|
|
101
|
+
"output_format": "png",
|
|
102
|
+
},
|
|
103
|
+
"supports": {
|
|
104
|
+
"prompt", "image_size", "num_inference_steps", "guidance_scale",
|
|
105
|
+
"num_images", "output_format", "seed", "sync_mode",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
VALID_ASPECT_RATIOS = ("landscape", "square", "portrait")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _load_fal_client():
|
|
114
|
+
try:
|
|
115
|
+
from echo_agent.dependencies import ensure
|
|
116
|
+
ensure("tool.image-gen-fal")
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
import fal_client # type: ignore
|
|
120
|
+
return fal_client
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
_fal_env_lock = threading.Lock()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _build_payload(model_id: str, prompt: str, aspect_ratio: str) -> dict[str, Any]:
|
|
127
|
+
meta = FAL_MODELS[model_id]
|
|
128
|
+
size_style = meta["size_style"]
|
|
129
|
+
sizes = meta["sizes"]
|
|
130
|
+
|
|
131
|
+
aspect = aspect_ratio if aspect_ratio in sizes else "landscape"
|
|
132
|
+
payload: dict[str, Any] = dict(meta.get("defaults", {}))
|
|
133
|
+
payload["prompt"] = prompt.strip()
|
|
134
|
+
|
|
135
|
+
if size_style == "image_size_preset":
|
|
136
|
+
payload["image_size"] = sizes[aspect]
|
|
137
|
+
elif size_style == "aspect_ratio":
|
|
138
|
+
payload["aspect_ratio"] = sizes[aspect]
|
|
139
|
+
|
|
140
|
+
supports = meta["supports"]
|
|
141
|
+
return {k: v for k, v in payload.items() if k in supports}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class FalImageGenTool(Tool):
|
|
145
|
+
name = "image_generate"
|
|
146
|
+
description = "Generate an image from a text prompt using FAL.ai (supports FLUX, Ideogram, Recraft, Qwen and more)."
|
|
147
|
+
parameters = {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"properties": {
|
|
150
|
+
"prompt": {"type": "string", "description": "Text description of the image to generate."},
|
|
151
|
+
"aspect_ratio": {
|
|
152
|
+
"type": "string",
|
|
153
|
+
"enum": ["landscape", "square", "portrait"],
|
|
154
|
+
"description": "Image aspect ratio.",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
"required": ["prompt"],
|
|
158
|
+
}
|
|
159
|
+
timeout_seconds = 120
|
|
160
|
+
|
|
161
|
+
def __init__(self, fal_key: str = "", model: str = ""):
|
|
162
|
+
self._fal_key = fal_key
|
|
163
|
+
self._model = model or DEFAULT_MODEL
|
|
164
|
+
|
|
165
|
+
def is_ready(self) -> bool:
|
|
166
|
+
return bool(self._fal_key)
|
|
167
|
+
|
|
168
|
+
def readiness_detail(self) -> tuple[bool, str]:
|
|
169
|
+
if self._fal_key:
|
|
170
|
+
return True, "ok"
|
|
171
|
+
return False, "FAL.ai API key not configured"
|
|
172
|
+
|
|
173
|
+
async def execute(self, params: dict[str, Any], ctx: ToolExecutionContext | None = None) -> ToolResult:
|
|
174
|
+
if not self._fal_key:
|
|
175
|
+
return ToolResult(success=False, error="FAL.ai API key not configured")
|
|
176
|
+
|
|
177
|
+
prompt = params.get("prompt", "").strip()
|
|
178
|
+
if not prompt:
|
|
179
|
+
return ToolResult(success=False, error="prompt is required")
|
|
180
|
+
|
|
181
|
+
aspect_ratio = params.get("aspect_ratio", "landscape").lower().strip()
|
|
182
|
+
if aspect_ratio not in VALID_ASPECT_RATIOS:
|
|
183
|
+
aspect_ratio = "landscape"
|
|
184
|
+
|
|
185
|
+
model_id = self._model
|
|
186
|
+
if model_id not in FAL_MODELS:
|
|
187
|
+
supported = ", ".join(sorted(FAL_MODELS.keys()))
|
|
188
|
+
return ToolResult(
|
|
189
|
+
success=False,
|
|
190
|
+
error=f"Unknown FAL model '{model_id}'. Supported models: {supported}",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
arguments = _build_payload(model_id, prompt, aspect_ratio)
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
fal = _load_fal_client()
|
|
197
|
+
except ImportError:
|
|
198
|
+
return ToolResult(
|
|
199
|
+
success=False,
|
|
200
|
+
error="fal-client package not installed. Run: pip install fal-client",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def _submit_with_key():
|
|
204
|
+
with _fal_env_lock:
|
|
205
|
+
prev_key = os.environ.get("FAL_KEY")
|
|
206
|
+
os.environ["FAL_KEY"] = self._fal_key
|
|
207
|
+
try:
|
|
208
|
+
handler = fal.submit(model_id, arguments=arguments)
|
|
209
|
+
return handler.get()
|
|
210
|
+
finally:
|
|
211
|
+
if prev_key is not None:
|
|
212
|
+
os.environ["FAL_KEY"] = prev_key
|
|
213
|
+
else:
|
|
214
|
+
os.environ.pop("FAL_KEY", None)
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
result = await asyncio.to_thread(_submit_with_key)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
return ToolResult(success=False, error=f"FAL.ai generation failed: {e}")
|
|
220
|
+
|
|
221
|
+
images = result.get("images") if isinstance(result, dict) else None
|
|
222
|
+
if not images:
|
|
223
|
+
return ToolResult(success=False, error="No images returned from FAL.ai")
|
|
224
|
+
|
|
225
|
+
image_url = images[0].get("url", "")
|
|
226
|
+
width = images[0].get("width", "")
|
|
227
|
+
height = images[0].get("height", "")
|
|
228
|
+
output = f"Image URL: {image_url}"
|
|
229
|
+
if width and height:
|
|
230
|
+
output += f"\nSize: {width}x{height}"
|
|
231
|
+
return ToolResult(output=output, metadata={"url": image_url})
|
|
@@ -230,10 +230,11 @@ class AppRuntime:
|
|
|
230
230
|
the storage close — from running.
|
|
231
231
|
"""
|
|
232
232
|
|
|
233
|
-
def __init__(self, ctx: BootstrapResult):
|
|
233
|
+
def __init__(self, ctx: BootstrapResult, shutdown_event: asyncio.Event | None = None):
|
|
234
234
|
self._ctx = ctx
|
|
235
235
|
self._gateway: Any = None
|
|
236
236
|
self._started = False
|
|
237
|
+
self._shutdown_event = shutdown_event
|
|
237
238
|
|
|
238
239
|
@property
|
|
239
240
|
def gateway(self) -> Any:
|
|
@@ -271,6 +272,8 @@ class AppRuntime:
|
|
|
271
272
|
agent_loop=ctx.agent,
|
|
272
273
|
a2a_config=ctx.config.a2a,
|
|
273
274
|
)
|
|
275
|
+
if self._shutdown_event:
|
|
276
|
+
self._gateway.set_shutdown_event(self._shutdown_event)
|
|
274
277
|
await self._gateway.start()
|
|
275
278
|
logger.info("Gateway started on {}:{}", ctx.config.gateway.host, ctx.config.gateway.port)
|
|
276
279
|
return True
|
|
@@ -313,7 +316,7 @@ async def run(config_path: str | None = None, workspace: str | None = None) -> N
|
|
|
313
316
|
logger.info("Echo Agent starting — workspace: {}", ctx.workspace)
|
|
314
317
|
|
|
315
318
|
install_signal_handler(shutdown)
|
|
316
|
-
runtime = AppRuntime(ctx)
|
|
319
|
+
runtime = AppRuntime(ctx, shutdown_event=shutdown)
|
|
317
320
|
try:
|
|
318
321
|
if not await runtime.start():
|
|
319
322
|
return
|
|
@@ -347,7 +350,7 @@ async def run_gateway(
|
|
|
347
350
|
ctx.config.gateway.port = port
|
|
348
351
|
|
|
349
352
|
install_signal_handler(shutdown)
|
|
350
|
-
runtime = AppRuntime(ctx)
|
|
353
|
+
runtime = AppRuntime(ctx, shutdown_event=shutdown)
|
|
351
354
|
try:
|
|
352
355
|
if not await runtime.start():
|
|
353
356
|
return
|
|
@@ -21,6 +21,7 @@ class SendResult:
|
|
|
21
21
|
success: bool
|
|
22
22
|
message_id: str = ""
|
|
23
23
|
error: str = ""
|
|
24
|
+
skipped: bool = False
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class BaseChannel(ABC):
|
|
@@ -52,6 +53,20 @@ class BaseChannel(ABC):
|
|
|
52
53
|
async def send(self, event: OutboundEvent) -> SendResult | None:
|
|
53
54
|
"""Send a message through this channel."""
|
|
54
55
|
|
|
56
|
+
def should_deliver(self, event: OutboundEvent) -> bool:
|
|
57
|
+
"""Return False to silently skip non-final messages on channels that cannot edit.
|
|
58
|
+
|
|
59
|
+
Channels that support message editing (Telegram, Discord) can show progress
|
|
60
|
+
then overwrite it with the final response. Channels without edit support would
|
|
61
|
+
deliver every intermediate chunk as a separate, irrevocable message — confusing
|
|
62
|
+
the user with duplicates. This guard prevents that at the base layer.
|
|
63
|
+
"""
|
|
64
|
+
if self.supports_edit:
|
|
65
|
+
return True
|
|
66
|
+
if event.is_final:
|
|
67
|
+
return True
|
|
68
|
+
return False
|
|
69
|
+
|
|
55
70
|
async def edit_message(
|
|
56
71
|
self,
|
|
57
72
|
chat_id: str,
|
|
@@ -56,6 +56,8 @@ class DingTalkChannel(BaseChannel):
|
|
|
56
56
|
await self._session.close()
|
|
57
57
|
|
|
58
58
|
async def send(self, event: OutboundEvent) -> SendResult | None:
|
|
59
|
+
if not self.should_deliver(event):
|
|
60
|
+
return SendResult(success=True, skipped=True)
|
|
59
61
|
text = event.text or ""
|
|
60
62
|
if not text or not self._session:
|
|
61
63
|
return SendResult(success=False, error="no text or no session")
|
|
@@ -44,6 +44,8 @@ class EmailChannel(BaseChannel):
|
|
|
44
44
|
pass
|
|
45
45
|
|
|
46
46
|
async def send(self, event: OutboundEvent) -> SendResult | None:
|
|
47
|
+
if not self.should_deliver(event):
|
|
48
|
+
return SendResult(success=True, skipped=True)
|
|
47
49
|
text = event.text or ""
|
|
48
50
|
if not text:
|
|
49
51
|
return SendResult(success=False, error="no text")
|
|
@@ -56,6 +56,8 @@ class FeishuChannel(BaseChannel):
|
|
|
56
56
|
await self._session.close()
|
|
57
57
|
|
|
58
58
|
async def send(self, event: OutboundEvent) -> SendResult | None:
|
|
59
|
+
if not self.should_deliver(event):
|
|
60
|
+
return SendResult(success=True, skipped=True)
|
|
59
61
|
text = event.text or ""
|
|
60
62
|
if not text or not self._session:
|
|
61
63
|
return SendResult(success=False, error="no text or no session")
|
|
@@ -54,6 +54,8 @@ class MatrixChannel(BaseChannel):
|
|
|
54
54
|
await self._session.close()
|
|
55
55
|
|
|
56
56
|
async def send(self, event: OutboundEvent) -> SendResult | None:
|
|
57
|
+
if not self.should_deliver(event):
|
|
58
|
+
return SendResult(success=True, skipped=True)
|
|
57
59
|
text = event.text or ""
|
|
58
60
|
if not text or not self._session:
|
|
59
61
|
return SendResult(success=False, error="no text or no session")
|
|
@@ -191,6 +191,8 @@ class QQBotChannel(BaseChannel):
|
|
|
191
191
|
# ── Send ─────────────────────────────────────────────────────────────────
|
|
192
192
|
|
|
193
193
|
async def send(self, event: OutboundEvent) -> SendResult | None:
|
|
194
|
+
if not self.should_deliver(event):
|
|
195
|
+
return SendResult(success=True, skipped=True)
|
|
194
196
|
if not self._session:
|
|
195
197
|
return SendResult(success=False, error="session not initialized")
|
|
196
198
|
await self._ensure_token()
|
|
@@ -43,6 +43,8 @@ class WebhookChannel(BaseChannel):
|
|
|
43
43
|
await self._runner.cleanup()
|
|
44
44
|
|
|
45
45
|
async def send(self, event: OutboundEvent) -> SendResult | None:
|
|
46
|
+
if not self.should_deliver(event):
|
|
47
|
+
return SendResult(success=True, skipped=True)
|
|
46
48
|
future = self._pending_responses.pop(event.reply_to_id or "", None)
|
|
47
49
|
if future and not future.done():
|
|
48
50
|
future.set_result(event.text)
|
|
@@ -55,6 +55,8 @@ class WeComChannel(BaseChannel):
|
|
|
55
55
|
await self._session.close()
|
|
56
56
|
|
|
57
57
|
async def send(self, event: OutboundEvent) -> SendResult | None:
|
|
58
|
+
if not self.should_deliver(event):
|
|
59
|
+
return SendResult(success=True, skipped=True)
|
|
58
60
|
text = event.text or ""
|
|
59
61
|
if not text or not self._session:
|
|
60
62
|
return SendResult(success=False, error="no text or no session")
|
|
@@ -367,6 +367,8 @@ class WeixinChannel(BaseChannel):
|
|
|
367
367
|
# ── Send ─────────────────────────────────────────────────────────────────
|
|
368
368
|
|
|
369
369
|
async def send(self, event: OutboundEvent) -> SendResult | None:
|
|
370
|
+
if not self.should_deliver(event):
|
|
371
|
+
return SendResult(success=True, skipped=True)
|
|
370
372
|
text = event.text or ""
|
|
371
373
|
if not text or not self._send_session or not self._token:
|
|
372
374
|
return SendResult(success=False, error="no text, no session, or no token")
|