aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a3__py3-none-any.whl
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.
- aethergraph/__init__.py +4 -10
- aethergraph/__main__.py +296 -0
- aethergraph/api/v1/__init__.py +0 -0
- aethergraph/api/v1/agents.py +46 -0
- aethergraph/api/v1/apps.py +70 -0
- aethergraph/api/v1/artifacts.py +415 -0
- aethergraph/api/v1/channels.py +89 -0
- aethergraph/api/v1/deps.py +168 -0
- aethergraph/api/v1/graphs.py +259 -0
- aethergraph/api/v1/identity.py +25 -0
- aethergraph/api/v1/memory.py +353 -0
- aethergraph/api/v1/misc.py +47 -0
- aethergraph/api/v1/pagination.py +29 -0
- aethergraph/api/v1/runs.py +568 -0
- aethergraph/api/v1/schemas.py +535 -0
- aethergraph/api/v1/session.py +323 -0
- aethergraph/api/v1/stats.py +201 -0
- aethergraph/api/v1/viz.py +152 -0
- aethergraph/config/config.py +22 -0
- aethergraph/config/loader.py +3 -2
- aethergraph/config/storage.py +209 -0
- aethergraph/contracts/__init__.py +0 -0
- aethergraph/contracts/services/__init__.py +0 -0
- aethergraph/contracts/services/artifacts.py +27 -14
- aethergraph/contracts/services/memory.py +45 -17
- aethergraph/contracts/services/metering.py +129 -0
- aethergraph/contracts/services/runs.py +50 -0
- aethergraph/contracts/services/sessions.py +87 -0
- aethergraph/contracts/services/state_stores.py +3 -0
- aethergraph/contracts/services/viz.py +44 -0
- aethergraph/contracts/storage/artifact_index.py +88 -0
- aethergraph/contracts/storage/artifact_store.py +99 -0
- aethergraph/contracts/storage/async_kv.py +34 -0
- aethergraph/contracts/storage/blob_store.py +50 -0
- aethergraph/contracts/storage/doc_store.py +35 -0
- aethergraph/contracts/storage/event_log.py +31 -0
- aethergraph/contracts/storage/vector_index.py +48 -0
- aethergraph/core/__init__.py +0 -0
- aethergraph/core/execution/forward_scheduler.py +13 -2
- aethergraph/core/execution/global_scheduler.py +21 -15
- aethergraph/core/execution/step_forward.py +10 -1
- aethergraph/core/graph/__init__.py +0 -0
- aethergraph/core/graph/graph_builder.py +8 -4
- aethergraph/core/graph/graph_fn.py +156 -15
- aethergraph/core/graph/graph_spec.py +8 -0
- aethergraph/core/graph/graphify.py +146 -27
- aethergraph/core/graph/node_spec.py +0 -2
- aethergraph/core/graph/node_state.py +3 -0
- aethergraph/core/graph/task_graph.py +39 -1
- aethergraph/core/runtime/__init__.py +0 -0
- aethergraph/core/runtime/ad_hoc_context.py +64 -4
- aethergraph/core/runtime/base_service.py +28 -4
- aethergraph/core/runtime/execution_context.py +13 -15
- aethergraph/core/runtime/graph_runner.py +222 -37
- aethergraph/core/runtime/node_context.py +510 -6
- aethergraph/core/runtime/node_services.py +12 -5
- aethergraph/core/runtime/recovery.py +15 -1
- aethergraph/core/runtime/run_manager.py +783 -0
- aethergraph/core/runtime/run_manager_local.py +204 -0
- aethergraph/core/runtime/run_registration.py +2 -2
- aethergraph/core/runtime/run_types.py +89 -0
- aethergraph/core/runtime/runtime_env.py +136 -7
- aethergraph/core/runtime/runtime_metering.py +71 -0
- aethergraph/core/runtime/runtime_registry.py +36 -13
- aethergraph/core/runtime/runtime_services.py +194 -6
- aethergraph/core/tools/builtins/toolset.py +1 -1
- aethergraph/core/tools/toolkit.py +5 -0
- aethergraph/plugins/agents/default_chat_agent copy.py +90 -0
- aethergraph/plugins/agents/default_chat_agent.py +171 -0
- aethergraph/plugins/agents/shared.py +81 -0
- aethergraph/plugins/channel/adapters/webui.py +112 -112
- aethergraph/plugins/channel/routes/webui_routes.py +367 -102
- aethergraph/plugins/channel/utils/slack_utils.py +115 -59
- aethergraph/plugins/channel/utils/telegram_utils.py +88 -47
- aethergraph/plugins/channel/websockets/weibui_ws.py +172 -0
- aethergraph/runtime/__init__.py +15 -0
- aethergraph/server/app_factory.py +196 -34
- aethergraph/server/clients/channel_client.py +202 -0
- aethergraph/server/http/channel_http_routes.py +116 -0
- aethergraph/server/http/channel_ws_routers.py +45 -0
- aethergraph/server/loading.py +117 -0
- aethergraph/server/server.py +131 -0
- aethergraph/server/server_state.py +240 -0
- aethergraph/server/start.py +227 -66
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +1 -0
- aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +400 -0
- aethergraph/server/ui_static/index.html +15 -0
- aethergraph/server/ui_static/logo.png +0 -0
- aethergraph/services/artifacts/__init__.py +0 -0
- aethergraph/services/artifacts/facade.py +1239 -132
- aethergraph/services/auth/{dev.py → authn.py} +0 -8
- aethergraph/services/auth/authz.py +100 -0
- aethergraph/services/channel/__init__.py +0 -0
- aethergraph/services/channel/channel_bus.py +19 -1
- aethergraph/services/channel/factory.py +13 -1
- aethergraph/services/channel/ingress.py +311 -0
- aethergraph/services/channel/queue_adapter.py +75 -0
- aethergraph/services/channel/session.py +502 -19
- aethergraph/services/container/default_container.py +122 -43
- aethergraph/services/continuations/continuation.py +6 -0
- aethergraph/services/continuations/stores/fs_store.py +19 -0
- aethergraph/services/eventhub/event_hub.py +76 -0
- aethergraph/services/kv/__init__.py +0 -0
- aethergraph/services/kv/ephemeral.py +244 -0
- aethergraph/services/llm/__init__.py +0 -0
- aethergraph/services/llm/generic_client copy.py +691 -0
- aethergraph/services/llm/generic_client.py +1288 -187
- aethergraph/services/llm/providers.py +3 -1
- aethergraph/services/llm/types.py +47 -0
- aethergraph/services/llm/utils.py +284 -0
- aethergraph/services/logger/std.py +3 -0
- aethergraph/services/mcp/__init__.py +9 -0
- aethergraph/services/mcp/http_client.py +38 -0
- aethergraph/services/mcp/service.py +225 -1
- aethergraph/services/mcp/stdio_client.py +41 -6
- aethergraph/services/mcp/ws_client.py +44 -2
- aethergraph/services/memory/__init__.py +0 -0
- aethergraph/services/memory/distillers/llm_long_term.py +234 -0
- aethergraph/services/memory/distillers/llm_meta_summary.py +398 -0
- aethergraph/services/memory/distillers/long_term.py +225 -0
- aethergraph/services/memory/facade/__init__.py +3 -0
- aethergraph/services/memory/facade/chat.py +440 -0
- aethergraph/services/memory/facade/core.py +447 -0
- aethergraph/services/memory/facade/distillation.py +424 -0
- aethergraph/services/memory/facade/rag.py +410 -0
- aethergraph/services/memory/facade/results.py +315 -0
- aethergraph/services/memory/facade/retrieval.py +139 -0
- aethergraph/services/memory/facade/types.py +77 -0
- aethergraph/services/memory/facade/utils.py +43 -0
- aethergraph/services/memory/facade_dep.py +1539 -0
- aethergraph/services/memory/factory.py +9 -3
- aethergraph/services/memory/utils.py +10 -0
- aethergraph/services/metering/eventlog_metering.py +470 -0
- aethergraph/services/metering/noop.py +25 -4
- aethergraph/services/rag/__init__.py +0 -0
- aethergraph/services/rag/facade.py +279 -23
- aethergraph/services/rag/index_factory.py +2 -2
- aethergraph/services/rag/node_rag.py +317 -0
- aethergraph/services/rate_limit/inmem_rate_limit.py +24 -0
- aethergraph/services/registry/__init__.py +0 -0
- aethergraph/services/registry/agent_app_meta.py +419 -0
- aethergraph/services/registry/registry_key.py +1 -1
- aethergraph/services/registry/unified_registry.py +74 -6
- aethergraph/services/scope/scope.py +159 -0
- aethergraph/services/scope/scope_factory.py +164 -0
- aethergraph/services/state_stores/serialize.py +5 -0
- aethergraph/services/state_stores/utils.py +2 -1
- aethergraph/services/viz/__init__.py +0 -0
- aethergraph/services/viz/facade.py +413 -0
- aethergraph/services/viz/viz_service.py +69 -0
- aethergraph/storage/artifacts/artifact_index_jsonl.py +180 -0
- aethergraph/storage/artifacts/artifact_index_sqlite.py +426 -0
- aethergraph/storage/artifacts/cas_store.py +422 -0
- aethergraph/storage/artifacts/fs_cas.py +18 -0
- aethergraph/storage/artifacts/s3_cas.py +14 -0
- aethergraph/storage/artifacts/utils.py +124 -0
- aethergraph/storage/blob/fs_blob.py +86 -0
- aethergraph/storage/blob/s3_blob.py +115 -0
- aethergraph/storage/continuation_store/fs_cont.py +283 -0
- aethergraph/storage/continuation_store/inmem_cont.py +146 -0
- aethergraph/storage/continuation_store/kvdoc_cont.py +261 -0
- aethergraph/storage/docstore/fs_doc.py +63 -0
- aethergraph/storage/docstore/sqlite_doc.py +31 -0
- aethergraph/storage/docstore/sqlite_doc_sync.py +90 -0
- aethergraph/storage/eventlog/fs_event.py +136 -0
- aethergraph/storage/eventlog/sqlite_event.py +47 -0
- aethergraph/storage/eventlog/sqlite_event_sync.py +178 -0
- aethergraph/storage/factory.py +432 -0
- aethergraph/storage/fs_utils.py +28 -0
- aethergraph/storage/graph_state_store/state_store.py +64 -0
- aethergraph/storage/kv/inmem_kv.py +103 -0
- aethergraph/storage/kv/layered_kv.py +52 -0
- aethergraph/storage/kv/sqlite_kv.py +39 -0
- aethergraph/storage/kv/sqlite_kv_sync.py +98 -0
- aethergraph/storage/memory/event_persist.py +68 -0
- aethergraph/storage/memory/fs_persist.py +118 -0
- aethergraph/{services/memory/hotlog_kv.py → storage/memory/hotlog.py} +8 -2
- aethergraph/{services → storage}/memory/indices.py +31 -7
- aethergraph/storage/metering/meter_event.py +55 -0
- aethergraph/storage/runs/doc_store.py +280 -0
- aethergraph/storage/runs/inmen_store.py +82 -0
- aethergraph/storage/runs/sqlite_run_store.py +403 -0
- aethergraph/storage/sessions/doc_store.py +183 -0
- aethergraph/storage/sessions/inmem_store.py +110 -0
- aethergraph/storage/sessions/sqlite_session_store.py +399 -0
- aethergraph/storage/vector_index/chroma_index.py +138 -0
- aethergraph/storage/vector_index/faiss_index.py +179 -0
- aethergraph/storage/vector_index/sqlite_index.py +187 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/METADATA +138 -31
- aethergraph-0.1.0a3.dist-info/RECORD +356 -0
- aethergraph-0.1.0a3.dist-info/entry_points.txt +3 -0
- aethergraph/services/artifacts/factory.py +0 -35
- aethergraph/services/artifacts/fs_store.py +0 -656
- aethergraph/services/artifacts/jsonl_index.py +0 -123
- aethergraph/services/artifacts/sqlite_index.py +0 -209
- aethergraph/services/memory/distillers/episode.py +0 -116
- aethergraph/services/memory/distillers/rolling.py +0 -74
- aethergraph/services/memory/facade.py +0 -633
- aethergraph/services/memory/persist_fs.py +0 -40
- aethergraph/services/rag/index/base.py +0 -27
- aethergraph/services/rag/index/faiss_index.py +0 -121
- aethergraph/services/rag/index/sqlite_index.py +0 -134
- aethergraph-0.1.0a1.dist-info/RECORD +0 -182
- aethergraph-0.1.0a1.dist-info/entry_points.txt +0 -2
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/WHEEL +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/top_level.txt +0 -0
|
@@ -35,6 +35,41 @@ class ChannelSession:
|
|
|
35
35
|
def _node_id(self):
|
|
36
36
|
return self.ctx.node_id
|
|
37
37
|
|
|
38
|
+
@property
|
|
39
|
+
def _session_id(self):
|
|
40
|
+
return self.ctx.session_id
|
|
41
|
+
|
|
42
|
+
def _inject_context_meta(self, meta: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
43
|
+
"""
|
|
44
|
+
Merge caller-provided meta with context-derived metadata
|
|
45
|
+
(run_id, session_id, agent_id, app_id, graph_id, node_id).
|
|
46
|
+
|
|
47
|
+
Caller-supplied keys win; we only fill in defaults.
|
|
48
|
+
"""
|
|
49
|
+
base: dict[str, Any] = dict(meta or {})
|
|
50
|
+
ctx = self.ctx
|
|
51
|
+
|
|
52
|
+
# Use setdefault so explicit meta wins.
|
|
53
|
+
if getattr(ctx, "run_id", None) is not None:
|
|
54
|
+
base.setdefault("run_id", ctx.run_id)
|
|
55
|
+
|
|
56
|
+
if getattr(ctx, "graph_id", None) is not None:
|
|
57
|
+
base.setdefault("graph_id", ctx.graph_id)
|
|
58
|
+
|
|
59
|
+
if getattr(ctx, "node_id", None) is not None:
|
|
60
|
+
base.setdefault("node_id", ctx.node_id)
|
|
61
|
+
|
|
62
|
+
if getattr(ctx, "session_id", None) is not None:
|
|
63
|
+
base.setdefault("session_id", ctx.session_id)
|
|
64
|
+
|
|
65
|
+
if getattr(ctx, "agent_id", None) is not None:
|
|
66
|
+
base.setdefault("agent_id", ctx.agent_id)
|
|
67
|
+
|
|
68
|
+
if getattr(ctx, "app_id", None) is not None:
|
|
69
|
+
base.setdefault("app_id", ctx.app_id)
|
|
70
|
+
|
|
71
|
+
return base
|
|
72
|
+
|
|
38
73
|
def _resolve_default_key(self) -> str:
|
|
39
74
|
"""Unified default resolver (bus default → console)."""
|
|
40
75
|
return self._bus.get_default_channel_key() or "console:stdin"
|
|
@@ -71,14 +106,98 @@ class ChannelSession:
|
|
|
71
106
|
|
|
72
107
|
# -------- send --------
|
|
73
108
|
async def send(self, event: OutEvent, *, channel: str | None = None):
|
|
109
|
+
"""
|
|
110
|
+
Send a single outbound event to the configured channel.
|
|
111
|
+
|
|
112
|
+
This method ensures the event is associated with the correct channel,
|
|
113
|
+
merges context-derived metadata, and publishes the event via the channel bus.
|
|
114
|
+
This is the core low-level send method; higher-level convenience methods
|
|
115
|
+
(e.g., `send_text`, `send_rich`, etc.) build on top of this and are recommended
|
|
116
|
+
for common use cases.
|
|
117
|
+
|
|
118
|
+
Examples:
|
|
119
|
+
Basic usage to send a pre-constructed event:
|
|
120
|
+
```python
|
|
121
|
+
|
|
122
|
+
event = OutEvent(type="agent.message", text="Hello!", channel=None)
|
|
123
|
+
await context.channel().send(event)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Sending to a specific channel:
|
|
127
|
+
```python
|
|
128
|
+
await context.channel().send(event, channel="web:chat")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
event: The `OutEvent` instance to send. If `event.channel` is not set,
|
|
133
|
+
it will be resolved automatically.
|
|
134
|
+
channel: Optional explicit channel key to override the default or event's channel.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
None
|
|
138
|
+
|
|
139
|
+
Notes:
|
|
140
|
+
for AG WebUI, you can set meta with
|
|
141
|
+
```python
|
|
142
|
+
{
|
|
143
|
+
"agent_id": "agent-123",
|
|
144
|
+
"name": "Analyst",
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
to override the sender's display name and avatar in the chat.
|
|
148
|
+
"""
|
|
74
149
|
event = self._ensure_channel(event, channel=channel)
|
|
150
|
+
|
|
151
|
+
# merge context meta
|
|
152
|
+
event.meta = self._inject_context_meta(event.meta)
|
|
75
153
|
await self._bus.publish(event)
|
|
76
154
|
|
|
77
155
|
async def send_text(
|
|
78
156
|
self, text: str, *, meta: dict[str, Any] | None = None, channel: str | None = None
|
|
79
157
|
):
|
|
158
|
+
"""
|
|
159
|
+
Send a plain text message to the configured channel.
|
|
160
|
+
|
|
161
|
+
This method constructs a normalized outbound event, merges context-derived metadata,
|
|
162
|
+
and dispatches the message via the channel bus.
|
|
163
|
+
|
|
164
|
+
Examples:
|
|
165
|
+
Basic usage to send a text message:
|
|
166
|
+
```python
|
|
167
|
+
await context.channel().send_text("Hello, world!")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Sending with additional metadata and to a specific channel:
|
|
171
|
+
```python
|
|
172
|
+
await context.channel().send_text(
|
|
173
|
+
"Status update.",
|
|
174
|
+
meta={"priority": "high"},
|
|
175
|
+
channel="web:chat"
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
text: The primary text content to send.
|
|
181
|
+
meta: Optional dictionary of metadata to include with the event.
|
|
182
|
+
channel: Optional explicit channel key to override the default or session-bound channel.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
None
|
|
186
|
+
|
|
187
|
+
Notes:
|
|
188
|
+
for AG WebUI, you can set meta with
|
|
189
|
+
```python
|
|
190
|
+
{
|
|
191
|
+
"agent_id": "agent-123",
|
|
192
|
+
"name": "Analyst",
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
"""
|
|
80
196
|
event = OutEvent(
|
|
81
|
-
type="agent.message",
|
|
197
|
+
type="agent.message",
|
|
198
|
+
channel=self._resolve_key(channel),
|
|
199
|
+
text=text,
|
|
200
|
+
meta=self._inject_context_meta(meta),
|
|
82
201
|
)
|
|
83
202
|
await self._bus.publish(event)
|
|
84
203
|
|
|
@@ -90,13 +209,57 @@ class ChannelSession:
|
|
|
90
209
|
meta: dict[str, Any] | None = None,
|
|
91
210
|
channel: str | None = None,
|
|
92
211
|
):
|
|
212
|
+
"""
|
|
213
|
+
Send a rich message to the configured channel.
|
|
214
|
+
|
|
215
|
+
This method constructs and dispatches an outbound event that can include both plain text and
|
|
216
|
+
structured rich content (such as cards, tables, or custom payloads). Context-derived metadata
|
|
217
|
+
is automatically merged, and the event is published via the channel bus.
|
|
218
|
+
|
|
219
|
+
Examples:
|
|
220
|
+
Basic usage to send a rich message:
|
|
221
|
+
```python
|
|
222
|
+
await context.channel().send_rich(
|
|
223
|
+
text="Here are your results:",
|
|
224
|
+
rich={"table": {"rows": [["A", 1], ["B", 2]]}}
|
|
225
|
+
)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Sending with additional metadata and to a specific channel:
|
|
229
|
+
```python
|
|
230
|
+
await context.channel().send_rich(
|
|
231
|
+
text="Task completed.",
|
|
232
|
+
rich={"status": "success"},
|
|
233
|
+
meta={"priority": "high"},
|
|
234
|
+
channel="web:chat"
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
text: The primary text content to send (optional).
|
|
240
|
+
rich: A dictionary containing structured rich content to include with the message.
|
|
241
|
+
meta: Optional dictionary of metadata to include with the event.
|
|
242
|
+
channel: Optional explicit channel key to override the default or session-bound channel.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
None
|
|
246
|
+
|
|
247
|
+
Notes:
|
|
248
|
+
for AG WebUI, you can set meta with
|
|
249
|
+
```python
|
|
250
|
+
{
|
|
251
|
+
"agent_id": "agent-123",
|
|
252
|
+
"name": "Analyst",
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
"""
|
|
93
256
|
await self._bus.publish(
|
|
94
257
|
OutEvent(
|
|
95
258
|
type="agent.message",
|
|
96
259
|
channel=self._resolve_key(channel),
|
|
97
260
|
text=text,
|
|
98
261
|
rich=rich,
|
|
99
|
-
meta=meta
|
|
262
|
+
meta=self._inject_context_meta(meta),
|
|
100
263
|
)
|
|
101
264
|
)
|
|
102
265
|
|
|
@@ -108,12 +271,51 @@ class ChannelSession:
|
|
|
108
271
|
title: str | None = None,
|
|
109
272
|
channel: str | None = None,
|
|
110
273
|
):
|
|
274
|
+
"""
|
|
275
|
+
Send an image message to the configured channel.
|
|
276
|
+
|
|
277
|
+
This method constructs and dispatches an outbound event containing image metadata,
|
|
278
|
+
including the image URL, alternative text, and an optional title. Context-derived
|
|
279
|
+
metadata is automatically merged, and the event is published via the channel bus.
|
|
280
|
+
|
|
281
|
+
Examples:
|
|
282
|
+
Basic usage to send an image:
|
|
283
|
+
```python
|
|
284
|
+
await context.channel().send_image(
|
|
285
|
+
url="https://example.com/image.png",
|
|
286
|
+
alt="Sample image"
|
|
287
|
+
)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Sending with a custom title and to a specific channel:
|
|
291
|
+
```python
|
|
292
|
+
await context.channel().send_image(
|
|
293
|
+
url="https://example.com/photo.jpg",
|
|
294
|
+
alt="User profile photo",
|
|
295
|
+
title="Profile",
|
|
296
|
+
channel="web:chat"
|
|
297
|
+
)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
url: The URL of the image to send. If None, an empty string is used.
|
|
302
|
+
alt: Alternative text describing the image (for accessibility).
|
|
303
|
+
title: Optional title to display with the image.
|
|
304
|
+
channel: Optional explicit channel key to override the default or session-bound channel.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
None
|
|
308
|
+
|
|
309
|
+
Notes:
|
|
310
|
+
The capability to render images depends on the client adapter.
|
|
311
|
+
"""
|
|
111
312
|
await self._bus.publish(
|
|
112
313
|
OutEvent(
|
|
113
314
|
type="agent.message",
|
|
114
315
|
channel=self._resolve_key(channel),
|
|
115
316
|
text=title or alt,
|
|
116
317
|
image={"url": url or "", "alt": alt, "title": title or ""},
|
|
318
|
+
meta=self._inject_context_meta(None),
|
|
117
319
|
)
|
|
118
320
|
)
|
|
119
321
|
|
|
@@ -126,13 +328,59 @@ class ChannelSession:
|
|
|
126
328
|
title: str | None = None,
|
|
127
329
|
channel: str | None = None,
|
|
128
330
|
):
|
|
331
|
+
"""
|
|
332
|
+
Send a file to the configured channel in a normalized format.
|
|
333
|
+
|
|
334
|
+
This method constructs and dispatches an outbound event containing file metadata,
|
|
335
|
+
including the file URL, raw bytes, filename, and an optional title. Context-derived
|
|
336
|
+
metadata is automatically merged, and the event is published via the channel bus.
|
|
337
|
+
|
|
338
|
+
Examples:
|
|
339
|
+
Basic usage to send a file by URL:
|
|
340
|
+
```python
|
|
341
|
+
await context.channel().send_file(
|
|
342
|
+
url="https://example.com/report.pdf",
|
|
343
|
+
filename="report.pdf",
|
|
344
|
+
title="Monthly Report"
|
|
345
|
+
)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Sending a file from bytes:
|
|
349
|
+
```python
|
|
350
|
+
await context.channel().send_file(
|
|
351
|
+
file_bytes=b"binarydata...",
|
|
352
|
+
filename="data.bin",
|
|
353
|
+
title="Raw Data"
|
|
354
|
+
)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
url: The URL of the file to send. If None, only file_bytes will be used.
|
|
359
|
+
file_bytes: Optional raw bytes of the file to send.
|
|
360
|
+
filename: The display name of the file (defaults to "file.bin").
|
|
361
|
+
title: Optional title to display with the file.
|
|
362
|
+
channel: Optional explicit channel key to override the default or session-bound channel.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
None
|
|
366
|
+
|
|
367
|
+
Notes:
|
|
368
|
+
The capability to handle file uploads depends on the client adapter.
|
|
369
|
+
If both `url` and `file_bytes` are provided, both will be included in the event.
|
|
370
|
+
"""
|
|
129
371
|
file = {"filename": filename}
|
|
130
372
|
if url:
|
|
131
373
|
file["url"] = url
|
|
132
374
|
if file_bytes is not None:
|
|
133
375
|
file["bytes"] = file_bytes
|
|
134
376
|
await self._bus.publish(
|
|
135
|
-
OutEvent(
|
|
377
|
+
OutEvent(
|
|
378
|
+
type="file.upload",
|
|
379
|
+
channel=self._resolve_key(channel),
|
|
380
|
+
text=title,
|
|
381
|
+
file=file,
|
|
382
|
+
meta=self._inject_context_meta(None),
|
|
383
|
+
)
|
|
136
384
|
)
|
|
137
385
|
|
|
138
386
|
async def send_buttons(
|
|
@@ -143,13 +391,47 @@ class ChannelSession:
|
|
|
143
391
|
meta: dict[str, Any] | None = None,
|
|
144
392
|
channel: str | None = None,
|
|
145
393
|
):
|
|
394
|
+
"""
|
|
395
|
+
Send a message with interactive buttons to the configured channel.
|
|
396
|
+
|
|
397
|
+
This method constructs and dispatches an outbound event containing a text prompt and a list of interactive buttons. Context-derived metadata is automatically merged, and the event is published via the channel bus.
|
|
398
|
+
|
|
399
|
+
Examples:
|
|
400
|
+
Basic usage to send a button prompt:
|
|
401
|
+
```python
|
|
402
|
+
from aethergraph import Button
|
|
403
|
+
await context.channel().send_buttons(
|
|
404
|
+
"Choose an option:",
|
|
405
|
+
[Button(label="Yes", value="yes"), Button(label="No", value="no")]
|
|
406
|
+
)
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Sending with additional metadata and to a specific channel:
|
|
410
|
+
```python
|
|
411
|
+
await context.channel().send_buttons(
|
|
412
|
+
"Select your role:",
|
|
413
|
+
[Button(label="Admin", value="admin"), Button(label="User", value="user")],
|
|
414
|
+
meta={"priority": "high"},
|
|
415
|
+
channel="web:chat"
|
|
416
|
+
)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
text: The primary text content to display above the buttons.
|
|
421
|
+
buttons: A list of `Button` objects representing the interactive options.
|
|
422
|
+
meta: Optional dictionary of metadata to include with the event.
|
|
423
|
+
channel: Optional explicit channel key to override the default or session-bound channel.
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
None
|
|
427
|
+
"""
|
|
146
428
|
await self._bus.publish(
|
|
147
429
|
OutEvent(
|
|
148
430
|
type="link.buttons",
|
|
149
431
|
channel=self._resolve_key(channel),
|
|
150
432
|
text=text,
|
|
151
433
|
buttons=buttons,
|
|
152
|
-
meta=meta
|
|
434
|
+
meta=self._inject_context_meta(meta),
|
|
153
435
|
)
|
|
154
436
|
)
|
|
155
437
|
|
|
@@ -163,12 +445,10 @@ class ChannelSession:
|
|
|
163
445
|
timeout_s: int,
|
|
164
446
|
) -> dict:
|
|
165
447
|
ch_key = self._resolve_key(channel)
|
|
166
|
-
|
|
167
448
|
# 1) Create continuation (with audit/security)
|
|
168
449
|
cont = await self.ctx.create_continuation(
|
|
169
450
|
channel=ch_key, kind=kind, payload=payload, deadline_s=timeout_s
|
|
170
451
|
)
|
|
171
|
-
|
|
172
452
|
# 2) PREPARE the wait future BEFORE notifying (prevents race)
|
|
173
453
|
fut = self.ctx.prepare_wait_for_resume(cont.token)
|
|
174
454
|
|
|
@@ -225,6 +505,36 @@ class ChannelSession:
|
|
|
225
505
|
silent: bool = False, # kept for back-compat; same behavior as before
|
|
226
506
|
channel: str | None = None,
|
|
227
507
|
) -> str:
|
|
508
|
+
"""
|
|
509
|
+
Prompt the user for a text response in a normalized format.
|
|
510
|
+
|
|
511
|
+
This method sends a prompt to the configured channel, waits for a user reply, and returns the text input.
|
|
512
|
+
It automatically handles context metadata, timeout, and channel resolution.
|
|
513
|
+
|
|
514
|
+
Examples:
|
|
515
|
+
Basic usage to prompt for user input:
|
|
516
|
+
```python
|
|
517
|
+
reply = await context.channel().ask_text("What is your name?")
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Prompting with a custom timeout and silent mode:
|
|
521
|
+
```python
|
|
522
|
+
reply = await context.channel().ask_text(
|
|
523
|
+
"Enter your feedback.",
|
|
524
|
+
timeout_s=120,
|
|
525
|
+
silent=True
|
|
526
|
+
)
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
prompt: The text prompt to display to the user. If None, a generic prompt may be shown.
|
|
531
|
+
timeout_s: Maximum time in seconds to wait for a response (default: 3600).
|
|
532
|
+
silent: If True, suppresses prompt display in some adapters (back-compat; default: False).
|
|
533
|
+
channel: Optional explicit channel key to override the default or session-bound channel.
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
str: The user's text response, or an empty string if no input was received.
|
|
537
|
+
"""
|
|
228
538
|
payload = await self._ask_core(
|
|
229
539
|
kind="user_input",
|
|
230
540
|
payload={"prompt": prompt, "_silent": silent},
|
|
@@ -234,6 +544,33 @@ class ChannelSession:
|
|
|
234
544
|
return str(payload.get("text", ""))
|
|
235
545
|
|
|
236
546
|
async def wait_text(self, *, timeout_s: int = 3600, channel: str | None = None) -> str:
|
|
547
|
+
"""
|
|
548
|
+
Wait for a single text response from the user in a normalized format.
|
|
549
|
+
|
|
550
|
+
This method prompts the user for input (with no explicit prompt), waits for a reply,
|
|
551
|
+
and returns the text. It automatically handles context metadata, timeout, and channel resolution.
|
|
552
|
+
|
|
553
|
+
Examples:
|
|
554
|
+
Basic usage to wait for user input:
|
|
555
|
+
```python
|
|
556
|
+
reply = await context.channel().wait_text()
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Waiting with a custom timeout and specific channel:
|
|
560
|
+
```python
|
|
561
|
+
reply = await context.channel().wait_text(
|
|
562
|
+
timeout_s=120,
|
|
563
|
+
channel="web:chat"
|
|
564
|
+
)
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
timeout_s: Maximum time in seconds to wait for a response (default: 3600).
|
|
569
|
+
channel: Optional explicit channel key to override the default or session-bound channel.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
str: The user's text response, or an empty string if no input was received.
|
|
573
|
+
"""
|
|
237
574
|
# Alias for ask_text(prompt=None) but keeps existing signature
|
|
238
575
|
return await self.ask_text(prompt=None, timeout_s=timeout_s, silent=True, channel=channel)
|
|
239
576
|
|
|
@@ -245,6 +582,44 @@ class ChannelSession:
|
|
|
245
582
|
timeout_s: int = 3600,
|
|
246
583
|
channel: str | None = None,
|
|
247
584
|
) -> dict[str, Any]:
|
|
585
|
+
"""
|
|
586
|
+
Prompt the user for approval or rejection in a normalized format.
|
|
587
|
+
|
|
588
|
+
This method sends an approval prompt with customizable options (buttons) to the configured channel,
|
|
589
|
+
waits for the user's selection, and returns a normalized result indicating approval status and choice.
|
|
590
|
+
Context metadata, timeout, and channel resolution are handled automatically.
|
|
591
|
+
|
|
592
|
+
Examples:
|
|
593
|
+
Basic usage to prompt for approval:
|
|
594
|
+
```python
|
|
595
|
+
result = await context.channel().ask_approval("Do you approve this action?")
|
|
596
|
+
# result: { "approved": True/False, "choice": "Approve"/"Reject" }
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
Prompting with custom options and timeout:
|
|
600
|
+
```python
|
|
601
|
+
result = await context.channel().ask_approval(
|
|
602
|
+
"Proceed with deployment?",
|
|
603
|
+
options=["Yes", "No", "Defer"],
|
|
604
|
+
timeout_s=120
|
|
605
|
+
)
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
Args:
|
|
609
|
+
prompt: The text prompt to display to the user.
|
|
610
|
+
options: Iterable of button labels for user choices (defaults to "Approve" and "Reject").
|
|
611
|
+
timeout_s: Maximum time in seconds to wait for a response (default: 3600).
|
|
612
|
+
channel: Optional explicit channel key to override the default or session-bound channel.
|
|
613
|
+
|
|
614
|
+
Returns:
|
|
615
|
+
dict: A dictionary containing:
|
|
616
|
+
- "approved": bool indicating if the __first__ option was selected (True if approved, False otherwise).
|
|
617
|
+
- "choice": The label of the button selected by the user (str or None).
|
|
618
|
+
|
|
619
|
+
Warning:
|
|
620
|
+
The returned "choices" are determined by the external adapter and may vary. To be robust, make sure
|
|
621
|
+
to use `choices.lower()` and strip whitespace when comparing.
|
|
622
|
+
"""
|
|
248
623
|
payload = await self._ask_core(
|
|
249
624
|
kind="approval",
|
|
250
625
|
payload={"prompt": {"title": prompt, "buttons": list(options)}},
|
|
@@ -252,10 +627,9 @@ class ChannelSession:
|
|
|
252
627
|
timeout_s=timeout_s,
|
|
253
628
|
)
|
|
254
629
|
choice = payload.get("choice")
|
|
255
|
-
|
|
256
630
|
# Normalize return
|
|
257
631
|
# 1) If adapter explicitly sets approved, trust it
|
|
258
|
-
buttons = list(options) # just
|
|
632
|
+
buttons = list(options) # just plain list, not Button objects
|
|
259
633
|
# 2) Fallback: derive from choice + options
|
|
260
634
|
if choice is None or not buttons:
|
|
261
635
|
approved = False
|
|
@@ -271,20 +645,54 @@ class ChannelSession:
|
|
|
271
645
|
|
|
272
646
|
async def ask_files(
|
|
273
647
|
self,
|
|
274
|
-
*,
|
|
275
648
|
prompt: str,
|
|
649
|
+
*,
|
|
276
650
|
accept: list[str] | None = None,
|
|
277
651
|
multiple: bool = True,
|
|
278
652
|
timeout_s: int = 3600,
|
|
279
653
|
channel: str | None = None,
|
|
280
654
|
) -> dict:
|
|
281
655
|
"""
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
656
|
+
Prompt the user to upload one or more files, optionally with a text comment.
|
|
657
|
+
|
|
658
|
+
This method sends a file upload request to the configured channel, allowing the user to select files
|
|
659
|
+
and optionally enter accompanying text. The `accept` parameter provides hints to the client UI about
|
|
660
|
+
which file types are preferred, but is not enforced server-side. The method waits for the user's response
|
|
661
|
+
and returns a normalized result containing both text and file references.
|
|
662
|
+
|
|
663
|
+
Examples:
|
|
664
|
+
Basic usage to prompt for file upload:
|
|
665
|
+
```python
|
|
666
|
+
result = await context.channel().ask_files(
|
|
667
|
+
prompt="Please upload your report."
|
|
668
|
+
)
|
|
669
|
+
# result: { "text": "...", "files": [FileRef(...), ...] }
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
Restricting to images and allowing multiple files:
|
|
673
|
+
```python
|
|
674
|
+
result = await context.channel().ask_files(
|
|
675
|
+
prompt="Upload images for review.",
|
|
676
|
+
accept=["image/png", ".jpg"],
|
|
677
|
+
multiple=True
|
|
678
|
+
)
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
prompt: The text prompt to display to the user above the file picker.
|
|
683
|
+
accept: Optional list of MIME types or file extensions to suggest allowed file types (e.g., "image/png", ".pdf", ".jpg").
|
|
684
|
+
multiple: If True, allows the user to select multiple files (default: True).
|
|
685
|
+
timeout_s: Maximum time in seconds to wait for a response (default: 3600).
|
|
686
|
+
channel: Optional explicit channel key to override the default or session-bound channel.
|
|
687
|
+
|
|
688
|
+
Returns:
|
|
689
|
+
dict: A dictionary containing:
|
|
690
|
+
- "text": str, the user's comment or description (may be empty).
|
|
691
|
+
- "files": List[FileRef], references to the uploaded files (empty if none).
|
|
692
|
+
|
|
693
|
+
Notes:
|
|
694
|
+
On console adapters, file upload is not supported; only text will be returned.
|
|
695
|
+
The `accept` parameter is a UI hint and does not guarantee file type enforcement.
|
|
288
696
|
"""
|
|
289
697
|
payload = await self._ask_core(
|
|
290
698
|
kind="user_files",
|
|
@@ -317,7 +725,36 @@ class ChannelSession:
|
|
|
317
725
|
|
|
318
726
|
# ---------- inbox helpers (platform-agnostic) ----------
|
|
319
727
|
async def get_latest_uploads(self, *, clear: bool = True) -> list[FileRef]:
|
|
320
|
-
"""
|
|
728
|
+
"""
|
|
729
|
+
Retrieve the latest uploaded files from this channel's inbox in a normalized format.
|
|
730
|
+
|
|
731
|
+
This method accesses the ephemeral KV store to fetch file uploads associated with the current channel.
|
|
732
|
+
By default, it clears the inbox after retrieval to prevent duplicate processing. If the KV service
|
|
733
|
+
is unavailable in the context, a RuntimeError is raised. This method allows the fetch the files user
|
|
734
|
+
uploaded __not__ from an ask_files prompt, but from any prior upload event.
|
|
735
|
+
|
|
736
|
+
Examples:
|
|
737
|
+
Basic usage to fetch and clear uploaded files:
|
|
738
|
+
```python
|
|
739
|
+
files = await context.channel().get_latest_uploads()
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
Fetching files without clearing the inbox:
|
|
743
|
+
```python
|
|
744
|
+
files = await context.channel().get_latest_uploads(clear=False)
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
clear: If True (default), removes files from the inbox after retrieval.
|
|
749
|
+
If False, files are returned but remain in the inbox.
|
|
750
|
+
|
|
751
|
+
Returns:
|
|
752
|
+
List[FileRef]: A list of `FileRef` objects representing the uploaded files.
|
|
753
|
+
Returns an empty list if no files are present.
|
|
754
|
+
|
|
755
|
+
Raises:
|
|
756
|
+
RuntimeError: If the ephemeral KV service is not available in the current context.
|
|
757
|
+
"""
|
|
321
758
|
kv = getattr(self.ctx.services, "kv", None)
|
|
322
759
|
if kv:
|
|
323
760
|
if clear:
|
|
@@ -339,6 +776,9 @@ class ChannelSession:
|
|
|
339
776
|
self._channel_key = outer._resolve_key(channel_key)
|
|
340
777
|
self._upsert_key = f"{outer._run_id}:{outer._node_id}:stream"
|
|
341
778
|
|
|
779
|
+
def _inject_context_meta(self, meta: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
780
|
+
return self._outer._inject_context_meta(meta)
|
|
781
|
+
|
|
342
782
|
def _buf(self):
|
|
343
783
|
return getattr(self, "__buf", None)
|
|
344
784
|
|
|
@@ -355,6 +795,7 @@ class ChannelSession:
|
|
|
355
795
|
type="agent.stream.start",
|
|
356
796
|
channel=self._channel_key,
|
|
357
797
|
upsert_key=self._upsert_key,
|
|
798
|
+
meta=self._inject_context_meta(None),
|
|
358
799
|
)
|
|
359
800
|
)
|
|
360
801
|
|
|
@@ -369,6 +810,7 @@ class ChannelSession:
|
|
|
369
810
|
channel=self._channel_key,
|
|
370
811
|
text="".join(buf),
|
|
371
812
|
upsert_key=self._upsert_key,
|
|
813
|
+
meta=self._inject_context_meta(None),
|
|
372
814
|
)
|
|
373
815
|
)
|
|
374
816
|
|
|
@@ -380,19 +822,54 @@ class ChannelSession:
|
|
|
380
822
|
channel=self._channel_key,
|
|
381
823
|
text=full_text,
|
|
382
824
|
upsert_key=self._upsert_key,
|
|
825
|
+
meta=self._inject_context_meta(None),
|
|
383
826
|
)
|
|
384
827
|
)
|
|
385
828
|
await self._outer._bus.publish(
|
|
386
829
|
OutEvent(
|
|
387
|
-
type="agent.stream.end",
|
|
830
|
+
type="agent.stream.end",
|
|
831
|
+
channel=self._channel_key,
|
|
832
|
+
upsert_key=self._upsert_key,
|
|
833
|
+
meta=self._inject_context_meta(None),
|
|
388
834
|
)
|
|
389
835
|
)
|
|
390
836
|
|
|
391
837
|
@asynccontextmanager
|
|
392
838
|
async def stream(self, channel: str | None = None) -> AsyncIterator["_StreamSender"]:
|
|
393
839
|
"""
|
|
394
|
-
|
|
395
|
-
|
|
840
|
+
Stream a sequence of text deltas to the configured channel in a normalized format.
|
|
841
|
+
|
|
842
|
+
This method provides a context manager for streaming incremental message updates (such as LLM generation)
|
|
843
|
+
to the target channel. It automatically handles context metadata, upsert keys, and dispatches start, update,
|
|
844
|
+
and end events to the channel bus. The caller is responsible for sending deltas and ending the stream.
|
|
845
|
+
|
|
846
|
+
Examples:
|
|
847
|
+
Basic usage to stream LLM output:
|
|
848
|
+
```python
|
|
849
|
+
async with context.channel().stream() as s:
|
|
850
|
+
await s.delta("Hello, ")
|
|
851
|
+
await s.delta("world!")
|
|
852
|
+
await s.end()
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
Streaming to a specific channel:
|
|
856
|
+
```python
|
|
857
|
+
async with context.channel().stream(channel="web:chat") as s:
|
|
858
|
+
await s.delta("Generating results...")
|
|
859
|
+
await s.end(full_text="Results complete.")
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
Args:
|
|
863
|
+
channel: Optional explicit channel key to target a specific channel for this stream.
|
|
864
|
+
If None, uses the session-bound or default channel.
|
|
865
|
+
|
|
866
|
+
Returns:
|
|
867
|
+
AsyncIterator[_StreamSender]: An async context manager yielding a `_StreamSender` object
|
|
868
|
+
for sending deltas and ending the stream.
|
|
869
|
+
|
|
870
|
+
Notes:
|
|
871
|
+
The caller must explicitly call `end()` to finalize the stream. No auto-end is performed.
|
|
872
|
+
The adapter may have specific behaviors for rendering streamed content (update vs. append).
|
|
396
873
|
"""
|
|
397
874
|
s = ChannelSession._StreamSender(self, channel_key=channel)
|
|
398
875
|
try:
|
|
@@ -421,6 +898,9 @@ class ChannelSession:
|
|
|
421
898
|
self._channel_key = outer._resolve_key(channel_key)
|
|
422
899
|
self._upsert_key = f"{outer._run_id}:{outer._node_id}:{key_suffix}"
|
|
423
900
|
|
|
901
|
+
def _inject_context_meta(self, meta: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
902
|
+
return self._outer._inject_context_meta(meta)
|
|
903
|
+
|
|
424
904
|
async def start(self, *, subtitle: str | None = None):
|
|
425
905
|
if not self._started:
|
|
426
906
|
self._started = True
|
|
@@ -435,6 +915,7 @@ class ChannelSession:
|
|
|
435
915
|
"total": self._total,
|
|
436
916
|
"current": self._current,
|
|
437
917
|
},
|
|
918
|
+
meta=self._inject_context_meta(None),
|
|
438
919
|
)
|
|
439
920
|
)
|
|
440
921
|
|
|
@@ -468,6 +949,7 @@ class ChannelSession:
|
|
|
468
949
|
channel=self._channel_key,
|
|
469
950
|
upsert_key=self._upsert_key,
|
|
470
951
|
rich=payload,
|
|
952
|
+
meta=self._inject_context_meta(None),
|
|
471
953
|
)
|
|
472
954
|
)
|
|
473
955
|
|
|
@@ -484,6 +966,7 @@ class ChannelSession:
|
|
|
484
966
|
"total": self._total,
|
|
485
967
|
"current": self._total if self._total is not None else None,
|
|
486
968
|
},
|
|
969
|
+
meta=self._inject_context_meta(None),
|
|
487
970
|
)
|
|
488
971
|
)
|
|
489
972
|
|