feat(sdk): add host integration extension points

This commit is contained in:
chengyongru
2026-07-28 15:30:28 +08:00
committed by Xubin Ren
parent 12f828ea3d
commit c050955ae3
13 changed files with 359 additions and 7 deletions
+1
View File
@@ -58,6 +58,7 @@ class AgentTurnHookContext:
message_id: str | None = None
session_key: str | None = None
metadata: dict[str, Any] = field(default_factory=dict)
attributes: dict[str, Any] = field(default_factory=dict)
ephemeral: bool = False
+35 -5
View File
@@ -122,6 +122,7 @@ class TurnContext:
initial_messages: list[dict[str, Any]] = field(default_factory=list)
request_context: RequestContext | None = None
runtime_context_blocks: list[RuntimeContextBlock] = field(default_factory=list)
attributes: dict[str, Any] = field(default_factory=dict)
final_content: str | None = None
all_messages: list[dict[str, Any]] = field(default_factory=list)
@@ -612,10 +613,17 @@ class AgentLoop:
def register_runtime_context_provider(
self,
provider: RuntimeContextProvider,
) -> None:
"""Register a provider resolved once before each inbound model turn."""
if provider not in self._runtime_context_providers:
self._runtime_context_providers.append(provider)
) -> Callable[[], None]:
"""Register a per-turn context provider and return an unsubscribe callback."""
if provider in self._runtime_context_providers:
return lambda: None
self._runtime_context_providers.append(provider)
def _unsubscribe() -> None:
with suppress(ValueError):
self._runtime_context_providers.remove(provider)
return _unsubscribe
async def submit_cron_turn(self, msg: InboundMessage) -> OutboundMessage | None:
return await self._cron_turns.submit(msg)
@@ -703,6 +711,7 @@ class AgentLoop:
original_user_text=ctx.original_user_text,
runtime=ctx.runtime,
metadata=dict(ctx.msg.metadata or {}),
attributes=dict(ctx.attributes),
sender_id=ctx.msg.sender_id,
turn_id=ctx.turn_id,
workspace=scope.project_path,
@@ -881,6 +890,7 @@ class AgentLoop:
original_user_text=pending_msg.content,
runtime=runtime,
metadata=dict(metadata),
attributes=dict(request_ctx.attributes),
sender_id=pending_msg.sender_id,
turn_id=request_ctx.turn_id,
workspace=scope.project_path,
@@ -980,6 +990,7 @@ class AgentLoop:
chat_id=chat_id,
message_id=message_id,
metadata=metadata,
attributes=dict(request_ctx.attributes),
session_key=active_session_key,
workspace=effective_scope.project_path,
tool_hint_max_length=self.tool_hint_max_length,
@@ -1320,6 +1331,7 @@ class AgentLoop:
runtime: LLMRuntime | None = None,
delivery: TurnDelivery | None = None,
on_runtime_admitted: Callable[[LLMRuntime], Awaitable[None]] | None = None,
attributes: Mapping[str, Any] | None = None,
) -> OutboundMessage | None:
"""Process a single inbound message and return the response."""
kind = TurnKind.SYSTEM if msg.channel == "system" else TurnKind.USER
@@ -1367,6 +1379,7 @@ class AgentLoop:
hooks=list(hooks or []),
hook_factories=list(hook_factories or []),
tools=tools,
attributes=dict(attributes or {}),
)
# A streaming callback may be present even when the final text comes from a
# non-streaming recovery. Only the last completed segment can suppress the
@@ -1559,8 +1572,15 @@ class AgentLoop:
ctx.session.add_message(
"assistant", result.content, _command=True
)
self.sessions.save(ctx.session)
self._clear_pending_user_turn(ctx.session)
self.sessions.save(ctx.session)
if not ctx.ephemeral:
await self._runtime_events().session_turn_persisted(
ctx.msg,
ctx.session_key,
turn_id=ctx.turn_id,
attributes=ctx.attributes,
)
return True
return False
@@ -1702,6 +1722,13 @@ class AgentLoop:
self._clear_pending_user_turn(ctx.session)
self._clear_runtime_checkpoint(ctx.session)
self.sessions.save(ctx.session)
if not ctx.ephemeral:
await self._runtime_events().session_turn_persisted(
ctx.msg,
ctx.session_key,
turn_id=ctx.turn_id,
attributes=ctx.attributes,
)
async def _prepare_outbound(self, ctx: TurnContext) -> None:
if ctx.suppress_response:
@@ -1983,6 +2010,7 @@ class AgentLoop:
persist_user_message: bool = True,
runtime: LLMRuntime | None = None,
on_runtime_admitted: Callable[[LLMRuntime], Awaitable[None]] | None = None,
attributes: Mapping[str, Any] | None = None,
) -> OutboundMessage | None:
"""Process an external message directly and return the outbound payload."""
if channel == "system":
@@ -2018,6 +2046,8 @@ class AgentLoop:
kwargs["runtime"] = runtime
if on_runtime_admitted is not None:
kwargs["on_runtime_admitted"] = on_runtime_admitted
if attributes is not None:
kwargs["attributes"] = dict(attributes)
return await self._process_message(
msg,
**kwargs,
+1
View File
@@ -26,6 +26,7 @@ class RequestContext:
original_user_text: str | None = None
runtime: LLMRuntime | None = None
metadata: dict[str, Any] = field(default_factory=dict)
attributes: dict[str, Any] = field(default_factory=dict)
sender_id: str | None = None
turn_id: str | None = None
workspace: Path | None = None
+2
View File
@@ -29,6 +29,7 @@ class AgentTurnHookSpec:
chat_id: str = "direct"
message_id: str | None = None
metadata: dict[str, Any] | None = None
attributes: dict[str, Any] | None = None
session_key: str | None = None
workspace: Path | None = None
tool_hint_max_length: int = 40
@@ -62,6 +63,7 @@ def build_agent_turn_hook(spec: AgentTurnHookSpec) -> AgentHook:
message_id=spec.message_id,
session_key=spec.session_key,
metadata=dict(spec.metadata or {}),
attributes=dict(spec.attributes or {}),
ephemeral=spec.ephemeral,
)
hook_chain: list[AgentHook] = [progress_hook]