mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-22 16:35:20 +08:00
feat(runner): unify plugin execution across agents and event processors
This commit is contained in:
@@ -2,35 +2,35 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .runner.descriptor import AgentRunnerDescriptor
|
||||
from .runner.descriptor import RunnerDescriptor
|
||||
from .runner.id import parse_runner_id, format_runner_id, RunnerIdParts, is_plugin_runner_id
|
||||
from .runner.errors import (
|
||||
AgentRunnerError,
|
||||
RunnerError,
|
||||
RunnerNotFoundError,
|
||||
RunnerNotAuthorizedError,
|
||||
RunnerProtocolError,
|
||||
RunnerExecutionError,
|
||||
)
|
||||
from .runner.registry import AgentRunnerRegistry
|
||||
from .runner.context_builder import AgentRunContextBuilder
|
||||
from .runner.registry import RunnerRegistry
|
||||
from .runner.context_builder import RunnerContextBuilder
|
||||
from .runner.resource_builder import AgentResourceBuilder
|
||||
from .runner.result_normalizer import AgentResultNormalizer
|
||||
from .runner.orchestrator import AgentRunOrchestrator
|
||||
from .runner.config_resolver import RunnerConfigResolver
|
||||
|
||||
__all__ = [
|
||||
'AgentRunnerDescriptor',
|
||||
'RunnerDescriptor',
|
||||
'parse_runner_id',
|
||||
'format_runner_id',
|
||||
'is_plugin_runner_id',
|
||||
'RunnerIdParts',
|
||||
'AgentRunnerError',
|
||||
'RunnerError',
|
||||
'RunnerNotFoundError',
|
||||
'RunnerNotAuthorizedError',
|
||||
'RunnerProtocolError',
|
||||
'RunnerExecutionError',
|
||||
'AgentRunnerRegistry',
|
||||
'AgentRunContextBuilder',
|
||||
'RunnerRegistry',
|
||||
'RunnerContextBuilder',
|
||||
'AgentResourceBuilder',
|
||||
'AgentResultNormalizer',
|
||||
'AgentRunOrchestrator',
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .id import parse_runner_id, format_runner_id, RunnerIdParts
|
||||
from .errors import (
|
||||
AgentRunnerError,
|
||||
RunnerError,
|
||||
RunnerNotFoundError,
|
||||
RunnerNotAuthorizedError,
|
||||
RunnerProtocolError,
|
||||
RunnerExecutionError,
|
||||
)
|
||||
from .registry import AgentRunnerRegistry
|
||||
from .context_builder import AgentRunContextBuilder
|
||||
from .registry import RunnerRegistry
|
||||
from .context_builder import RunnerContextBuilder
|
||||
from .resource_builder import AgentResourceBuilder
|
||||
from .result_normalizer import AgentResultNormalizer
|
||||
from .orchestrator import AgentRunOrchestrator
|
||||
from .config_resolver import RunnerConfigResolver
|
||||
from .default_config import AgentRunnerDefaultConfigService
|
||||
from .default_config import RunnerDefaultConfigService
|
||||
from .binding_resolver import AgentBindingResolver, AgentBindingResolutionError
|
||||
from .session_registry import (
|
||||
AgentRunSessionRegistry,
|
||||
@@ -35,22 +35,22 @@ from .events import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'AgentRunnerDescriptor',
|
||||
'RunnerDescriptor',
|
||||
'parse_runner_id',
|
||||
'format_runner_id',
|
||||
'RunnerIdParts',
|
||||
'AgentRunnerError',
|
||||
'RunnerError',
|
||||
'RunnerNotFoundError',
|
||||
'RunnerNotAuthorizedError',
|
||||
'RunnerProtocolError',
|
||||
'RunnerExecutionError',
|
||||
'AgentRunnerRegistry',
|
||||
'AgentRunContextBuilder',
|
||||
'RunnerRegistry',
|
||||
'RunnerContextBuilder',
|
||||
'AgentResourceBuilder',
|
||||
'AgentResultNormalizer',
|
||||
'AgentRunOrchestrator',
|
||||
'RunnerConfigResolver',
|
||||
'AgentRunnerDefaultConfigService',
|
||||
'RunnerDefaultConfigService',
|
||||
'AgentBindingResolver',
|
||||
'AgentBindingResolutionError',
|
||||
'AgentRunSessionRegistry',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Resolve the current AgentRunner configuration shape."""
|
||||
"""Resolve the current Runner configuration shape."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -12,7 +12,7 @@ HOST_SECURITY_BOOLEAN_FIELDS = (
|
||||
|
||||
|
||||
class RunnerConfigResolver:
|
||||
"""Configuration helpers for the current AgentRunner shape.
|
||||
"""Configuration helpers for the current Runner shape.
|
||||
|
||||
Responsibilities:
|
||||
- Resolve runner ID from ai.runner.id
|
||||
@@ -131,20 +131,20 @@ class RunnerConfigResolver:
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
def resolve_agent_runner_id(config: dict[str, typing.Any]) -> str | None:
|
||||
def resolve_agent_id(config: dict[str, typing.Any]) -> str | None:
|
||||
"""Resolve a runner ID from a validated persisted Agent config."""
|
||||
runner = config.get('runner', {})
|
||||
runner_id = runner.get('id') if isinstance(runner, dict) else None
|
||||
return runner_id if isinstance(runner_id, str) and runner_id else None
|
||||
|
||||
@classmethod
|
||||
def resolve_agent_runner_config(
|
||||
def resolve_agent_config(
|
||||
cls,
|
||||
config: typing.Any,
|
||||
) -> tuple[dict[str, typing.Any], str | None, dict[str, typing.Any]]:
|
||||
"""Validate an Agent config and return its selected runner configuration."""
|
||||
validated = cls.validate_agent_config(config)
|
||||
runner_id = cls.resolve_agent_runner_id(validated)
|
||||
runner_id = cls.resolve_agent_id(validated)
|
||||
runner_configs = typing.cast(dict[str, typing.Any], validated['runner_config'])
|
||||
runner_config = runner_configs.get(runner_id, {}) if runner_id else {}
|
||||
return validated, runner_id, typing.cast(dict[str, typing.Any], runner_config)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"""Helpers for interpreting AgentRunner DynamicForm configuration."""
|
||||
"""Helpers for interpreting Runner DynamicForm configuration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .descriptor import RunnerDescriptor
|
||||
|
||||
|
||||
FORM_ITEM_TYPE_ALIASES = {
|
||||
@@ -24,7 +25,7 @@ def normalize_schema_item_type(item_type: typing.Any) -> typing.Any:
|
||||
|
||||
|
||||
def iter_schema_items(
|
||||
descriptor: AgentRunnerDescriptor | None,
|
||||
descriptor: RunnerDescriptor | None,
|
||||
field_types: set[str],
|
||||
) -> typing.Iterator[dict[str, typing.Any]]:
|
||||
"""Yield descriptor config schema items whose type is in field_types."""
|
||||
@@ -37,22 +38,22 @@ def iter_schema_items(
|
||||
yield item
|
||||
|
||||
|
||||
def uses_host_models(descriptor: AgentRunnerDescriptor | None) -> bool:
|
||||
def uses_host_models(descriptor: RunnerDescriptor | None) -> bool:
|
||||
"""Return whether LangBot should resolve model resources for this runner."""
|
||||
return any(True for _ in iter_schema_items(descriptor, LLM_MODEL_SELECTOR_TYPES))
|
||||
|
||||
|
||||
def uses_host_tools(descriptor: AgentRunnerDescriptor | None) -> bool:
|
||||
def uses_host_tools(descriptor: RunnerDescriptor | None) -> bool:
|
||||
"""Return whether LangBot should expose tool resources to this runner."""
|
||||
return descriptor is not None and descriptor.supports_tool_calling()
|
||||
|
||||
|
||||
def uses_host_knowledge_bases(descriptor: AgentRunnerDescriptor | None) -> bool:
|
||||
def uses_host_knowledge_bases(descriptor: RunnerDescriptor | None) -> bool:
|
||||
"""Return whether LangBot should expose knowledge-base resources to this runner."""
|
||||
return descriptor is not None and descriptor.supports_knowledge_retrieval()
|
||||
|
||||
|
||||
def supports_skill_authoring(descriptor: AgentRunnerDescriptor | None) -> bool:
|
||||
def supports_skill_authoring(descriptor: RunnerDescriptor | None) -> bool:
|
||||
"""Return whether the runner wants Host skill-authoring tools."""
|
||||
if descriptor is None:
|
||||
return False
|
||||
@@ -60,7 +61,7 @@ def supports_skill_authoring(descriptor: AgentRunnerDescriptor | None) -> bool:
|
||||
|
||||
|
||||
def extract_prompt_config(
|
||||
descriptor: AgentRunnerDescriptor | None,
|
||||
descriptor: RunnerDescriptor | None,
|
||||
runner_config: dict[str, typing.Any],
|
||||
default_prompt: list[dict[str, typing.Any]],
|
||||
) -> list[dict[str, typing.Any]]:
|
||||
@@ -78,7 +79,7 @@ def extract_prompt_config(
|
||||
|
||||
|
||||
def extract_model_selection(
|
||||
descriptor: AgentRunnerDescriptor | None,
|
||||
descriptor: RunnerDescriptor | None,
|
||||
runner_config: dict[str, typing.Any],
|
||||
) -> tuple[str, list[str]]:
|
||||
"""Extract primary/fallback LLM selections from schema-defined fields."""
|
||||
@@ -110,7 +111,7 @@ def extract_model_selection(
|
||||
|
||||
|
||||
def extract_knowledge_base_uuids(
|
||||
descriptor: AgentRunnerDescriptor | None,
|
||||
descriptor: RunnerDescriptor | None,
|
||||
runner_config: dict[str, typing.Any],
|
||||
) -> list[str]:
|
||||
"""Extract configured knowledge-base UUIDs from schema-defined fields."""
|
||||
@@ -124,15 +125,13 @@ def extract_knowledge_base_uuids(
|
||||
continue
|
||||
value = runner_config.get(field_name, item.get('default', []))
|
||||
if isinstance(value, list):
|
||||
kb_uuids.extend(
|
||||
kb_uuid for kb_uuid in value if isinstance(kb_uuid, str) and kb_uuid not in NONE_SENTINELS
|
||||
)
|
||||
kb_uuids.extend(kb_uuid for kb_uuid in value if isinstance(kb_uuid, str) and kb_uuid not in NONE_SENTINELS)
|
||||
|
||||
return list(dict.fromkeys(kb_uuids))
|
||||
|
||||
|
||||
def iter_config_model_refs(
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
runner_config: dict[str, typing.Any],
|
||||
) -> typing.Iterator[tuple[str, str]]:
|
||||
"""Yield model references declared by schema-defined model selector fields."""
|
||||
@@ -167,7 +166,7 @@ def iter_config_model_refs(
|
||||
|
||||
|
||||
def set_empty_llm_model_selection(
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
runner_config: dict[str, typing.Any],
|
||||
model_uuid: str,
|
||||
) -> bool:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Agent run context builder for provisioning AgentRunContext envelopes."""
|
||||
"""Agent run context builder for provisioning RunnerContext envelopes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -7,7 +7,7 @@ import time
|
||||
import typing
|
||||
|
||||
from ...core import app
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .persistent_state_store import get_persistent_state_store
|
||||
from .host_models import AgentEventEnvelope, AgentBinding
|
||||
|
||||
@@ -125,10 +125,10 @@ class AgentRuntimeContext(typing.TypedDict):
|
||||
metadata: dict[str, typing.Any]
|
||||
|
||||
|
||||
class AgentRunContextPayload(typing.TypedDict):
|
||||
"""AgentRunContext payload passed to an agent runner.
|
||||
class RunnerContextPayload(typing.TypedDict):
|
||||
"""RunnerContext payload passed to an agent runner.
|
||||
|
||||
Protocol v1 structure - matches SDK AgentRunContext.
|
||||
Protocol v1 structure - matches SDK RunnerContext.
|
||||
|
||||
Note: The 'config' field contains the current Agent/runner config
|
||||
from ai.runner_config[runner_id] while the current Query entry remains
|
||||
@@ -152,8 +152,8 @@ class AgentRunContextPayload(typing.TypedDict):
|
||||
metadata: dict[str, typing.Any] # Additional metadata
|
||||
|
||||
|
||||
class AgentRunContextBuilder:
|
||||
"""Builder for provisioning AgentRunContext.
|
||||
class RunnerContextBuilder:
|
||||
"""Builder for provisioning RunnerContext.
|
||||
|
||||
Responsibilities:
|
||||
- Generate new run_id (UUID, not query id)
|
||||
@@ -222,10 +222,10 @@ class AgentRunContextBuilder:
|
||||
self,
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
resources: AgentResources,
|
||||
) -> AgentRunContextPayload:
|
||||
"""Build AgentRunContext from event-first envelope.
|
||||
) -> RunnerContextPayload:
|
||||
"""Build RunnerContext from event-first envelope.
|
||||
|
||||
This is the main entry point for Protocol v1.
|
||||
Does NOT inline full history by default.
|
||||
@@ -237,7 +237,7 @@ class AgentRunContextBuilder:
|
||||
resources: Built resources
|
||||
|
||||
Returns:
|
||||
AgentRunContextPayload for the runner
|
||||
RunnerContextPayload for the runner
|
||||
"""
|
||||
# Generate new run_id
|
||||
run_id = str(uuid.uuid4())
|
||||
@@ -351,7 +351,7 @@ class AgentRunContextBuilder:
|
||||
}
|
||||
|
||||
# Build full context - Protocol v1 structure
|
||||
context: AgentRunContextPayload = {
|
||||
context: RunnerContextPayload = {
|
||||
'run_id': run_id,
|
||||
'trigger': trigger,
|
||||
'conversation': conversation,
|
||||
@@ -397,7 +397,7 @@ class AgentRunContextBuilder:
|
||||
async def _build_context_access(
|
||||
self,
|
||||
event: AgentEventEnvelope,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
binding: AgentBinding | None = None,
|
||||
) -> dict[str, typing.Any]:
|
||||
"""Build ContextAccess with actual values from stores.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Default AgentRunner binding configuration helpers."""
|
||||
"""Default Runner binding configuration helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -11,8 +11,8 @@ from . import config_schema
|
||||
from .config_resolver import RunnerConfigResolver
|
||||
|
||||
|
||||
class AgentRunnerDefaultConfigService:
|
||||
"""Apply AgentRunner schema-defined defaults to host binding config."""
|
||||
class RunnerDefaultConfigService:
|
||||
"""Apply Runner schema-defined defaults to host binding config."""
|
||||
|
||||
ap: app.Application
|
||||
|
||||
@@ -20,7 +20,7 @@ class AgentRunnerDefaultConfigService:
|
||||
self.ap = ap
|
||||
|
||||
async def _get_runner_descriptor(self, context: TenantContext, runner_id: str):
|
||||
registry = getattr(self.ap, 'agent_runner_registry', None)
|
||||
registry = getattr(self.ap, 'runner_registry', None)
|
||||
if registry is None:
|
||||
return None
|
||||
try:
|
||||
@@ -28,7 +28,7 @@ class AgentRunnerDefaultConfigService:
|
||||
except Exception as e:
|
||||
logger = getattr(self.ap, 'logger', None)
|
||||
if logger:
|
||||
logger.warning(f'Failed to load AgentRunner descriptor while setting default model: {e}')
|
||||
logger.warning(f'Failed to load Runner descriptor while setting default model: {e}')
|
||||
return None
|
||||
|
||||
async def auto_set_default_pipeline_llm_model(
|
||||
@@ -39,8 +39,7 @@ class AgentRunnerDefaultConfigService:
|
||||
"""Set model_uuid into the default pipeline runner config when the selector is empty."""
|
||||
result = await self.ap.persistence_mgr.execute_async(
|
||||
sqlalchemy.select(persistence_pipeline.LegacyPipeline).where(
|
||||
persistence_pipeline.LegacyPipeline.workspace_uuid
|
||||
== require_workspace_uuid(context),
|
||||
persistence_pipeline.LegacyPipeline.workspace_uuid == require_workspace_uuid(context),
|
||||
persistence_pipeline.LegacyPipeline.is_default == True,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -5,13 +5,13 @@ from __future__ import annotations
|
||||
import typing
|
||||
import pydantic
|
||||
|
||||
from langbot_plugin.api.entities.builtin.agent_runner.manifest import (
|
||||
AgentRunnerCapabilities,
|
||||
AgentRunnerPermissions,
|
||||
from langbot_plugin.api.entities.builtin.runner.manifest import (
|
||||
RunnerCapabilities,
|
||||
RunnerPermissions,
|
||||
)
|
||||
|
||||
|
||||
class AgentRunnerDescriptor(pydantic.BaseModel):
|
||||
class RunnerDescriptor(pydantic.BaseModel):
|
||||
"""Descriptor for an agent runner.
|
||||
|
||||
Represents the discovered metadata for a runner, including
|
||||
@@ -37,7 +37,7 @@ class AgentRunnerDescriptor(pydantic.BaseModel):
|
||||
"""Plugin name from manifest"""
|
||||
|
||||
runner_name: str
|
||||
"""AgentRunner component name from manifest"""
|
||||
"""Runner component name from manifest"""
|
||||
|
||||
plugin_version: str | None = None
|
||||
"""Optional plugin version"""
|
||||
@@ -45,16 +45,17 @@ class AgentRunnerDescriptor(pydantic.BaseModel):
|
||||
config_schema: list[dict[str, typing.Any]] = pydantic.Field(default_factory=list)
|
||||
"""Configuration schema using DynamicForm format"""
|
||||
|
||||
capabilities: AgentRunnerCapabilities = pydantic.Field(default_factory=AgentRunnerCapabilities)
|
||||
capabilities: RunnerCapabilities = pydantic.Field(default_factory=RunnerCapabilities)
|
||||
"""Runner capabilities: streaming, tool_calling, knowledge_retrieval, etc."""
|
||||
|
||||
permissions: AgentRunnerPermissions = pydantic.Field(default_factory=AgentRunnerPermissions)
|
||||
permissions: RunnerPermissions = pydantic.Field(default_factory=RunnerPermissions)
|
||||
"""Requested LangBot resource permissions."""
|
||||
|
||||
raw_manifest: dict[str, typing.Any] = pydantic.Field(default_factory=dict)
|
||||
"""Original manifest for reference"""
|
||||
|
||||
component_kind: typing.Literal['AgentRunner', 'EventProcessor'] = 'AgentRunner'
|
||||
component_kind: typing.Literal['Runner'] = 'Runner'
|
||||
usages: list[typing.Literal['agent', 'event']] = pydantic.Field(default_factory=lambda: ['agent'])
|
||||
supported_event_patterns: list[str] = pydantic.Field(default_factory=lambda: ['*'])
|
||||
|
||||
model_config = pydantic.ConfigDict(
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class AgentRunnerError(Exception):
|
||||
class RunnerError(Exception):
|
||||
"""Base error for agent runner operations."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RunnerNotFoundError(AgentRunnerError):
|
||||
class RunnerNotFoundError(RunnerError):
|
||||
"""Runner not found in registry."""
|
||||
|
||||
def __init__(self, runner_id: str):
|
||||
@@ -17,7 +17,7 @@ class RunnerNotFoundError(AgentRunnerError):
|
||||
super().__init__(f'Agent runner not found: {runner_id}')
|
||||
|
||||
|
||||
class RunnerNotAuthorizedError(AgentRunnerError):
|
||||
class RunnerNotAuthorizedError(RunnerError):
|
||||
"""Runner not authorized for this binding."""
|
||||
|
||||
def __init__(self, runner_id: str, bound_plugins: list[str] | None):
|
||||
@@ -26,7 +26,7 @@ class RunnerNotAuthorizedError(AgentRunnerError):
|
||||
super().__init__(f'Agent runner {runner_id} not authorized for bound_plugins={bound_plugins}')
|
||||
|
||||
|
||||
class RunnerProtocolError(AgentRunnerError):
|
||||
class RunnerProtocolError(RunnerError):
|
||||
"""Runner protocol version mismatch or invalid manifest."""
|
||||
|
||||
def __init__(self, runner_id: str, message: str):
|
||||
@@ -34,7 +34,7 @@ class RunnerProtocolError(AgentRunnerError):
|
||||
super().__init__(f'Agent runner protocol error for {runner_id}: {message}')
|
||||
|
||||
|
||||
class RunnerExecutionError(AgentRunnerError):
|
||||
class RunnerExecutionError(RunnerError):
|
||||
"""Runner execution failed."""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Canonical AgentRunner event names reserved for future EBA integration."""
|
||||
"""Canonical Runner event names reserved for future EBA integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Host-only Query compatibility views for AgentRunner tool execution."""
|
||||
"""Host-only Query compatibility views for Runner tool execution."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ from __future__ import annotations
|
||||
import typing
|
||||
import pydantic
|
||||
|
||||
from langbot_plugin.api.entities.builtin.agent_runner.event import (
|
||||
from langbot_plugin.api.entities.builtin.runner.event import (
|
||||
ActorContext,
|
||||
SubjectContext,
|
||||
RawEventRef,
|
||||
)
|
||||
from langbot_plugin.api.entities.builtin.agent_runner.input import AgentInput
|
||||
from langbot_plugin.api.entities.builtin.agent_runner.delivery import DeliveryContext
|
||||
from langbot_plugin.api.entities.builtin.runner.input import AgentInput
|
||||
from langbot_plugin.api.entities.builtin.runner.delivery import DeliveryContext
|
||||
|
||||
|
||||
class AgentEventEnvelope(pydantic.BaseModel):
|
||||
|
||||
@@ -31,7 +31,7 @@ def parse_runner_id(runner_id: str) -> RunnerIdParts:
|
||||
Raises:
|
||||
ValueError: If runner_id format is invalid
|
||||
"""
|
||||
if runner_id.startswith(('plugin:', 'event_processor:')):
|
||||
if runner_id.startswith('plugin:'):
|
||||
source, value = runner_id.split(':', 1)
|
||||
parts = value.split('/')
|
||||
if len(parts) != 3:
|
||||
@@ -71,7 +71,7 @@ def format_runner_id(
|
||||
Returns:
|
||||
Runner ID string
|
||||
"""
|
||||
if source in {'plugin', 'event_processor'}:
|
||||
if source == 'plugin':
|
||||
return f'{source}:{plugin_author}/{plugin_name}/{runner_name}'
|
||||
else:
|
||||
raise ValueError(f'Invalid runner source: {source}')
|
||||
@@ -86,4 +86,4 @@ def is_plugin_runner_id(runner_id: str) -> bool:
|
||||
Returns:
|
||||
True if runner ID starts with 'plugin:'
|
||||
"""
|
||||
return runner_id.startswith(('plugin:', 'event_processor:'))
|
||||
return runner_id.startswith('plugin:')
|
||||
|
||||
@@ -10,7 +10,7 @@ import pydantic
|
||||
|
||||
from langbot_plugin.api.entities.builtin.platform import message as platform_message
|
||||
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .errors import RunnerProtocolError
|
||||
from .host_models import AgentBinding, AgentEventEnvelope
|
||||
from .interaction_store import InteractionStore
|
||||
@@ -97,7 +97,7 @@ class InteractionManager:
|
||||
result_dict: dict[str, typing.Any],
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
run_id: str,
|
||||
adapter_context: dict[str, typing.Any] | None,
|
||||
) -> bool:
|
||||
@@ -230,7 +230,7 @@ class InteractionManager:
|
||||
*,
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
processor_id: str,
|
||||
conversation_id: str | None,
|
||||
) -> dict[str, typing.Any] | None:
|
||||
@@ -390,7 +390,7 @@ class InteractionManager:
|
||||
raise ValueError(f'interaction submission option is not present in the request: {field_id}')
|
||||
|
||||
@staticmethod
|
||||
def _authorize(descriptor: AgentRunnerDescriptor, binding: AgentBinding) -> None:
|
||||
def _authorize(descriptor: RunnerDescriptor, binding: AgentBinding) -> None:
|
||||
supports_interactions = bool(getattr(descriptor.capabilities, 'interactions', False))
|
||||
permissions = set(getattr(descriptor.permissions, 'interactions', []) or [])
|
||||
if not supports_interactions or 'request' not in permissions:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Plugin-runtime invocation for AgentRunner executions."""
|
||||
"""Plugin-runtime invocation for Runner executions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -10,13 +10,13 @@ import typing
|
||||
from langbot_plugin.entities.io.errors import ActionCallTimeoutError
|
||||
|
||||
from ...core import app
|
||||
from .context_builder import AgentRunContextPayload
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .context_builder import RunnerContextPayload
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .errors import RunnerExecutionError
|
||||
|
||||
|
||||
class AgentRunnerInvoker:
|
||||
"""Invoke an AgentRunner through the plugin runtime.
|
||||
class RunnerInvoker:
|
||||
"""Invoke an Runner through the plugin runtime.
|
||||
|
||||
This keeps runtime transport, deadline enforcement, and transport error
|
||||
mapping out of the orchestration state machine.
|
||||
@@ -29,8 +29,8 @@ class AgentRunnerInvoker:
|
||||
|
||||
async def invoke(
|
||||
self,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
context: AgentRunContextPayload,
|
||||
descriptor: RunnerDescriptor,
|
||||
context: RunnerContextPayload,
|
||||
) -> typing.AsyncGenerator[dict[str, typing.Any], None]:
|
||||
"""Invoke the runner and yield raw result dictionaries."""
|
||||
if not self.ap.plugin_connector.is_enable_plugin:
|
||||
@@ -41,18 +41,7 @@ class AgentRunnerInvoker:
|
||||
)
|
||||
|
||||
try:
|
||||
if descriptor.component_kind == 'EventProcessor':
|
||||
context = {
|
||||
**context,
|
||||
'runtime': {
|
||||
**context['runtime'],
|
||||
'metadata': {
|
||||
**context['runtime'].get('metadata', {}),
|
||||
'component_kind': 'EventProcessor',
|
||||
},
|
||||
},
|
||||
}
|
||||
gen = self.ap.plugin_connector.run_agent(
|
||||
gen = self.ap.plugin_connector.run_runner(
|
||||
plugin_author=descriptor.plugin_author,
|
||||
plugin_name=descriptor.plugin_name,
|
||||
runner_name=descriptor.runner_name,
|
||||
@@ -93,8 +82,8 @@ class AgentRunnerInvoker:
|
||||
async def _next_with_deadline(
|
||||
self,
|
||||
gen: typing.AsyncGenerator[dict[str, typing.Any], None],
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
context: AgentRunContextPayload,
|
||||
descriptor: RunnerDescriptor,
|
||||
context: RunnerContextPayload,
|
||||
) -> dict[str, typing.Any]:
|
||||
"""Read the next runner result while enforcing the run deadline."""
|
||||
remaining = self._remaining_deadline_seconds(context)
|
||||
@@ -116,7 +105,7 @@ class AgentRunnerInvoker:
|
||||
|
||||
def _remaining_deadline_seconds(
|
||||
self,
|
||||
context: AgentRunContextPayload,
|
||||
context: RunnerContextPayload,
|
||||
) -> float | None:
|
||||
runtime = context.get('runtime') or {}
|
||||
deadline_at = runtime.get('deadline_at')
|
||||
@@ -127,14 +116,14 @@ class AgentRunnerInvoker:
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
def _is_deadline_exhausted(self, context: AgentRunContextPayload) -> bool:
|
||||
def _is_deadline_exhausted(self, context: RunnerContextPayload) -> bool:
|
||||
remaining = self._remaining_deadline_seconds(context)
|
||||
return remaining is not None and remaining <= 0
|
||||
|
||||
async def _close_generator(
|
||||
self,
|
||||
gen: typing.AsyncGenerator[dict[str, typing.Any], None],
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> None:
|
||||
try:
|
||||
await gen.aclose()
|
||||
|
||||
@@ -15,8 +15,8 @@ from ...core import app
|
||||
from ...api.http.context import ExecutionContext
|
||||
from ...pipeline.pool import get_query_execution_context
|
||||
from .binding_resolver import AgentBindingResolver
|
||||
from .context_builder import AgentRunContextBuilder, AgentRunContextPayload
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .context_builder import RunnerContextBuilder, RunnerContextPayload
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .execution_context import (
|
||||
append_mcp_resource_context_to_event,
|
||||
build_mcp_resource_context_addition,
|
||||
@@ -26,10 +26,10 @@ from .execution_context import (
|
||||
project_mcp_resource_config,
|
||||
)
|
||||
from .host_models import AgentBinding, AgentEventEnvelope
|
||||
from .invoker import AgentRunnerInvoker
|
||||
from .invoker import RunnerInvoker
|
||||
from .interaction_manager import InteractionManager
|
||||
from .query_bridge import QueryRunBridge
|
||||
from .registry import AgentRunnerRegistry
|
||||
from .registry import RunnerRegistry
|
||||
from .resource_builder import AgentResourceBuilder
|
||||
from .platform_tools import freeze_platform_context
|
||||
from .result_normalizer import AgentResultNormalizer
|
||||
@@ -43,7 +43,7 @@ ACTIVATED_SKILL_NAMES_STATE_KEY = 'host.activated_skills'
|
||||
|
||||
|
||||
class AgentRunOrchestrator:
|
||||
"""Coordinate one AgentRunner execution.
|
||||
"""Coordinate one Runner execution.
|
||||
|
||||
The orchestrator keeps the run state machine readable and delegates
|
||||
transport, Query bridging, and persistence side effects to narrower
|
||||
@@ -51,13 +51,13 @@ class AgentRunOrchestrator:
|
||||
"""
|
||||
|
||||
ap: app.Application
|
||||
registry: AgentRunnerRegistry
|
||||
context_builder: AgentRunContextBuilder
|
||||
registry: RunnerRegistry
|
||||
context_builder: RunnerContextBuilder
|
||||
resource_builder: AgentResourceBuilder
|
||||
result_normalizer: AgentResultNormalizer
|
||||
binding_resolver: AgentBindingResolver
|
||||
query_bridge: QueryRunBridge
|
||||
invoker: AgentRunnerInvoker
|
||||
invoker: RunnerInvoker
|
||||
interaction_manager: InteractionManager
|
||||
journal: AgentRunJournal
|
||||
_session_registry: AgentRunSessionRegistry
|
||||
@@ -65,16 +65,16 @@ class AgentRunOrchestrator:
|
||||
def __init__(
|
||||
self,
|
||||
ap: app.Application,
|
||||
registry: AgentRunnerRegistry,
|
||||
registry: RunnerRegistry,
|
||||
):
|
||||
self.ap = ap
|
||||
self.registry = registry
|
||||
self.context_builder = AgentRunContextBuilder(ap)
|
||||
self.context_builder = RunnerContextBuilder(ap)
|
||||
self.resource_builder = AgentResourceBuilder(ap)
|
||||
self.result_normalizer = AgentResultNormalizer(ap)
|
||||
self.binding_resolver = AgentBindingResolver()
|
||||
self.query_bridge = QueryRunBridge(self.binding_resolver)
|
||||
self.invoker = AgentRunnerInvoker(ap)
|
||||
self.invoker = RunnerInvoker(ap)
|
||||
self.interaction_manager = InteractionManager(ap)
|
||||
self.journal = AgentRunJournal(ap)
|
||||
self._session_registry = get_session_registry()
|
||||
@@ -86,7 +86,7 @@ class AgentRunOrchestrator:
|
||||
bound_plugins: list[str] | None = None,
|
||||
adapter_context: dict[str, typing.Any] | None = None,
|
||||
) -> typing.AsyncGenerator[provider_message.Message | provider_message.MessageChunk, None]:
|
||||
"""Run an AgentRunner from an event-first envelope."""
|
||||
"""Run an Runner from an event-first envelope."""
|
||||
runner_id = binding.runner_id
|
||||
execution_query = adapter_context.get('_query') if adapter_context else None
|
||||
execution_context = adapter_context.get('_execution_context') if adapter_context else None
|
||||
@@ -105,9 +105,9 @@ class AgentRunOrchestrator:
|
||||
bound_plugins,
|
||||
)
|
||||
|
||||
expected_kind = 'EventProcessor' if binding.processor_type == 'event_processor' else 'AgentRunner'
|
||||
if descriptor.component_kind != expected_kind:
|
||||
raise ValueError('Processor kind does not match the selected plugin component')
|
||||
usage = 'event' if binding.processor_type == 'event_processor' else 'agent'
|
||||
if usage not in descriptor.usages:
|
||||
raise ValueError(f'The selected Runner does not support {usage} usage')
|
||||
|
||||
if execution_query is None:
|
||||
execution_query = build_execution_query(event, [])
|
||||
@@ -391,7 +391,7 @@ class AgentRunOrchestrator:
|
||||
self,
|
||||
query: pipeline_query.Query,
|
||||
) -> typing.AsyncGenerator[provider_message.Message | provider_message.MessageChunk, None]:
|
||||
"""Run an AgentRunner from the current Pipeline Query entry point."""
|
||||
"""Run an Runner from the current Pipeline Query entry point."""
|
||||
plan = self.query_bridge.build_plan(query)
|
||||
adapter_context = dict(plan.adapter_context)
|
||||
adapter_context['_query'] = query
|
||||
@@ -566,8 +566,8 @@ class AgentRunOrchestrator:
|
||||
|
||||
async def _invoke_runner(
|
||||
self,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
context: AgentRunContextPayload,
|
||||
descriptor: RunnerDescriptor,
|
||||
context: RunnerContextPayload,
|
||||
) -> typing.AsyncGenerator[dict[str, typing.Any], None]:
|
||||
"""Compatibility delegate for older tests and internal callers."""
|
||||
async for result in self.invoker.invoke(descriptor, context):
|
||||
@@ -576,24 +576,24 @@ class AgentRunOrchestrator:
|
||||
async def _next_with_deadline(
|
||||
self,
|
||||
gen: typing.AsyncGenerator[dict[str, typing.Any], None],
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
context: AgentRunContextPayload,
|
||||
descriptor: RunnerDescriptor,
|
||||
context: RunnerContextPayload,
|
||||
) -> dict[str, typing.Any]:
|
||||
return await self.invoker._next_with_deadline(gen, descriptor, context)
|
||||
|
||||
def _remaining_deadline_seconds(
|
||||
self,
|
||||
context: AgentRunContextPayload,
|
||||
context: RunnerContextPayload,
|
||||
) -> float | None:
|
||||
return self.invoker._remaining_deadline_seconds(context)
|
||||
|
||||
def _is_deadline_exhausted(self, context: AgentRunContextPayload) -> bool:
|
||||
def _is_deadline_exhausted(self, context: RunnerContextPayload) -> bool:
|
||||
return self.invoker._is_deadline_exhausted(context)
|
||||
|
||||
async def _close_generator(
|
||||
self,
|
||||
gen: typing.AsyncGenerator[dict[str, typing.Any], None],
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> None:
|
||||
await self.invoker._close_generator(gen, descriptor)
|
||||
|
||||
@@ -602,7 +602,7 @@ class AgentRunOrchestrator:
|
||||
result_dict: dict[str, typing.Any],
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> None:
|
||||
await self.journal.handle_state_updated_event(result_dict, event, binding, descriptor)
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""Persistent state store for AgentRunner protocol state.
|
||||
"""Persistent state store for Runner protocol state.
|
||||
|
||||
This module provides a database-backed state store for event-first Protocol v1.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
@@ -16,7 +17,7 @@ from sqlalchemy.dialects.postgresql import insert as postgresql_insert
|
||||
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .host_models import AgentEventEnvelope, AgentBinding
|
||||
from .state_scope import (
|
||||
VALID_STATE_SCOPES,
|
||||
@@ -24,7 +25,7 @@ from .state_scope import (
|
||||
get_binding_identity,
|
||||
normalize_state_key,
|
||||
)
|
||||
from ...entity.persistence.agent_runner_state import AgentRunnerState
|
||||
from ...entity.persistence.runner_state import RunnerState
|
||||
|
||||
|
||||
# Maximum value_json size (256KB)
|
||||
@@ -32,7 +33,7 @@ MAX_VALUE_JSON_BYTES = 256 * 1024
|
||||
|
||||
|
||||
class PersistentStateStore:
|
||||
"""Database-backed state store for AgentRunner protocol state.
|
||||
"""Database-backed state store for Runner protocol state.
|
||||
|
||||
IMPORTANT: This is HOST-OWNED protocol state, NOT plugin instance state.
|
||||
|
||||
@@ -55,7 +56,7 @@ class PersistentStateStore:
|
||||
scope: str,
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> str | None:
|
||||
"""Get scope key for given scope."""
|
||||
return build_state_scope_key(scope, event, binding, descriptor)
|
||||
@@ -104,7 +105,7 @@ class PersistentStateStore:
|
||||
dialect_name = self._db_engine.dialect.name
|
||||
|
||||
if dialect_name == 'sqlite':
|
||||
stmt = sqlite_insert(AgentRunnerState).values(**values)
|
||||
stmt = sqlite_insert(RunnerState).values(**values)
|
||||
await conn.execute(
|
||||
stmt.on_conflict_do_update(
|
||||
index_elements=constraint_columns,
|
||||
@@ -114,7 +115,7 @@ class PersistentStateStore:
|
||||
return
|
||||
|
||||
if dialect_name == 'postgresql':
|
||||
stmt = postgresql_insert(AgentRunnerState).values(**values)
|
||||
stmt = postgresql_insert(RunnerState).values(**values)
|
||||
await conn.execute(
|
||||
stmt.on_conflict_do_update(
|
||||
index_elements=constraint_columns,
|
||||
@@ -124,12 +125,12 @@ class PersistentStateStore:
|
||||
return
|
||||
|
||||
try:
|
||||
await conn.execute(sqlalchemy.insert(AgentRunnerState).values(**values))
|
||||
await conn.execute(sqlalchemy.insert(RunnerState).values(**values))
|
||||
except IntegrityError:
|
||||
await conn.execute(
|
||||
update(AgentRunnerState)
|
||||
.where(AgentRunnerState.scope_key == values['scope_key'])
|
||||
.where(AgentRunnerState.state_key == values['state_key'])
|
||||
update(RunnerState)
|
||||
.where(RunnerState.scope_key == values['scope_key'])
|
||||
.where(RunnerState.state_key == values['state_key'])
|
||||
.values(**update_values)
|
||||
)
|
||||
|
||||
@@ -139,7 +140,7 @@ class PersistentStateStore:
|
||||
self,
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> dict[str, dict[str, typing.Any]]:
|
||||
"""Build state snapshot for all scopes from event and binding.
|
||||
|
||||
@@ -174,8 +175,7 @@ class PersistentStateStore:
|
||||
|
||||
# Query all state entries for this scope_key
|
||||
result = await conn.execute(
|
||||
select(AgentRunnerState.state_key, AgentRunnerState.value_json)
|
||||
.where(AgentRunnerState.scope_key == scope_key)
|
||||
select(RunnerState.state_key, RunnerState.value_json).where(RunnerState.scope_key == scope_key)
|
||||
)
|
||||
rows = result.fetchall()
|
||||
|
||||
@@ -199,7 +199,7 @@ class PersistentStateStore:
|
||||
self,
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
scope: str,
|
||||
key: str,
|
||||
value: typing.Any,
|
||||
@@ -280,9 +280,9 @@ class PersistentStateStore:
|
||||
|
||||
async with self._db_engine.connect() as conn:
|
||||
result = await conn.execute(
|
||||
select(AgentRunnerState.value_json)
|
||||
.where(AgentRunnerState.scope_key == scope_key)
|
||||
.where(AgentRunnerState.state_key == state_key)
|
||||
select(RunnerState.value_json)
|
||||
.where(RunnerState.scope_key == scope_key)
|
||||
.where(RunnerState.state_key == state_key)
|
||||
)
|
||||
row = result.first()
|
||||
|
||||
@@ -358,9 +358,7 @@ class PersistentStateStore:
|
||||
|
||||
async with self._db_engine.begin() as conn:
|
||||
result = await conn.execute(
|
||||
delete(AgentRunnerState)
|
||||
.where(AgentRunnerState.scope_key == scope_key)
|
||||
.where(AgentRunnerState.state_key == state_key)
|
||||
delete(RunnerState).where(RunnerState.scope_key == scope_key).where(RunnerState.state_key == state_key)
|
||||
)
|
||||
return (result.rowcount or 0) > 0
|
||||
|
||||
@@ -379,17 +377,15 @@ class PersistentStateStore:
|
||||
|
||||
async with self._db_engine.connect() as conn:
|
||||
query = (
|
||||
select(AgentRunnerState.state_key)
|
||||
.where(AgentRunnerState.scope_key == scope_key)
|
||||
.order_by(AgentRunnerState.state_key)
|
||||
select(RunnerState.state_key)
|
||||
.where(RunnerState.scope_key == scope_key)
|
||||
.order_by(RunnerState.state_key)
|
||||
.limit(limit + 1) # Fetch one extra to check has_more
|
||||
)
|
||||
|
||||
if prefix:
|
||||
prefix = normalize_state_key(prefix)
|
||||
query = query.where(
|
||||
AgentRunnerState.state_key.like(f'{prefix}%')
|
||||
)
|
||||
query = query.where(RunnerState.state_key.like(f'{prefix}%'))
|
||||
|
||||
result = await conn.execute(query)
|
||||
rows = result.fetchall()
|
||||
@@ -402,7 +398,7 @@ class PersistentStateStore:
|
||||
async def clear_all(self) -> None:
|
||||
"""Clear all state entries (for testing)."""
|
||||
async with self._db_engine.begin() as conn:
|
||||
await conn.execute(delete(AgentRunnerState))
|
||||
await conn.execute(delete(RunnerState))
|
||||
|
||||
|
||||
# Global singleton persistent state store
|
||||
@@ -423,7 +419,7 @@ def get_persistent_state_store(db_engine: AsyncEngine | None = None) -> Persiste
|
||||
with _persistent_state_store_lock:
|
||||
if _persistent_state_store is None:
|
||||
if db_engine is None:
|
||||
raise RuntimeError("db_engine required for first call to get_persistent_state_store")
|
||||
raise RuntimeError('db_engine required for first call to get_persistent_state_store')
|
||||
_persistent_state_store = PersistentStateStore(db_engine)
|
||||
return _persistent_state_store
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Run-scoped platform and event action tools exposed to AgentRunners."""
|
||||
"""Run-scoped platform and event action tools exposed to Runners."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Pipeline Query bridge for AgentRunner execution."""
|
||||
"""Pipeline Query bridge for Runner execution."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@ import typing
|
||||
|
||||
from langbot_plugin.api.entities.builtin.pipeline import query as pipeline_query
|
||||
from langbot_plugin.api.entities.builtin.platform import message as platform_message
|
||||
from langbot_plugin.api.entities.builtin.agent_runner.event import (
|
||||
from langbot_plugin.api.entities.builtin.runner.event import (
|
||||
AgentEventContext,
|
||||
ConversationContext,
|
||||
ActorContext,
|
||||
SubjectContext,
|
||||
RawEventRef,
|
||||
)
|
||||
from langbot_plugin.api.entities.builtin.agent_runner.input import AgentInput
|
||||
from langbot_plugin.api.entities.builtin.agent_runner.delivery import DeliveryContext
|
||||
from langbot_plugin.api.entities.builtin.runner.input import AgentInput
|
||||
from langbot_plugin.api.entities.builtin.runner.delivery import DeliveryContext
|
||||
|
||||
from .host_models import (
|
||||
AgentConfig,
|
||||
|
||||
@@ -5,23 +5,23 @@ from __future__ import annotations
|
||||
import typing
|
||||
import asyncio
|
||||
|
||||
from langbot_plugin.api.entities.builtin.agent_runner.manifest import (
|
||||
AgentRunnerManifest,
|
||||
from langbot_plugin.api.entities.builtin.runner.manifest import (
|
||||
RunnerManifest,
|
||||
)
|
||||
|
||||
from ...core import app
|
||||
from ...api.http.context import ExecutionContext
|
||||
from ...api.http.service.tenant import TenantContext
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .id import parse_runner_id, format_runner_id
|
||||
from .errors import RunnerNotFoundError, RunnerNotAuthorizedError
|
||||
|
||||
|
||||
class AgentRunnerRegistry:
|
||||
class RunnerRegistry:
|
||||
"""Registry for discovering and managing agent runners.
|
||||
|
||||
Responsibilities:
|
||||
- Discover runners from plugin runtime via LIST_AGENT_RUNNERS
|
||||
- Discover runners from plugin runtime via LIST_RUNNERS
|
||||
- Validate runner manifests (kind, metadata, spec)
|
||||
- Cache discovered runners for performance
|
||||
- Filter runners by bound plugins
|
||||
@@ -30,7 +30,7 @@ class AgentRunnerRegistry:
|
||||
|
||||
ap: app.Application
|
||||
|
||||
_cache: dict[tuple[str, str, int], dict[str, AgentRunnerDescriptor]]
|
||||
_cache: dict[tuple[str, str, int], dict[str, RunnerDescriptor]]
|
||||
"""Runner descriptors keyed by immutable Workspace execution scope."""
|
||||
|
||||
_cache_lock: asyncio.Lock
|
||||
@@ -52,7 +52,7 @@ class AgentRunnerRegistry:
|
||||
async def _resolve_context(self, context: TenantContext) -> ExecutionContext:
|
||||
return await self.ap.plugin_connector.require_workspace_context(context)
|
||||
|
||||
async def _discover_runners(self) -> dict[str, AgentRunnerDescriptor]:
|
||||
async def _discover_runners(self) -> dict[str, RunnerDescriptor]:
|
||||
"""Discover runners from plugin runtime.
|
||||
|
||||
Always discovers ALL runners (no bound_plugins filter).
|
||||
@@ -64,11 +64,11 @@ class AgentRunnerRegistry:
|
||||
if not self.ap.plugin_connector.is_enable_plugin:
|
||||
return {}
|
||||
|
||||
runners: dict[str, AgentRunnerDescriptor] = {}
|
||||
runners: dict[str, RunnerDescriptor] = {}
|
||||
|
||||
try:
|
||||
# Always list all runners (bound_plugins=None)
|
||||
plugin_runners = await self.ap.plugin_connector.list_agent_runners(None)
|
||||
plugin_runners = await self.ap.plugin_connector.list_runners(None)
|
||||
|
||||
for runner_data in plugin_runners:
|
||||
try:
|
||||
@@ -90,16 +90,16 @@ class AgentRunnerRegistry:
|
||||
|
||||
return runners
|
||||
|
||||
def _validate_and_build_descriptor(self, runner_data: dict[str, typing.Any]) -> AgentRunnerDescriptor | None:
|
||||
def _validate_and_build_descriptor(self, runner_data: dict[str, typing.Any]) -> RunnerDescriptor | None:
|
||||
"""Validate runner manifest and build descriptor.
|
||||
|
||||
Args:
|
||||
runner_data: Raw runner data from plugin runtime with fields:
|
||||
- plugin_author, plugin_name, runner_name
|
||||
- manifest (typed AgentRunnerManifest)
|
||||
- manifest (typed RunnerManifest)
|
||||
|
||||
Returns:
|
||||
AgentRunnerDescriptor if valid, None if invalid
|
||||
RunnerDescriptor if valid, None if invalid
|
||||
"""
|
||||
plugin_author = runner_data.get('plugin_author', '')
|
||||
plugin_name = runner_data.get('plugin_name', '')
|
||||
@@ -110,18 +110,19 @@ class AgentRunnerRegistry:
|
||||
|
||||
manifest = runner_data.get('manifest', {})
|
||||
runner_id = format_runner_id(
|
||||
source='event_processor' if manifest.get('component_kind') == 'EventProcessor' else 'plugin',
|
||||
source='plugin',
|
||||
plugin_author=plugin_author,
|
||||
plugin_name=plugin_name,
|
||||
runner_name=runner_name,
|
||||
)
|
||||
|
||||
typed_manifest = AgentRunnerManifest.model_validate(manifest)
|
||||
typed_manifest = RunnerManifest.model_validate(manifest)
|
||||
config_schema = [item.model_dump(mode='json') for item in typed_manifest.config_schema]
|
||||
|
||||
return AgentRunnerDescriptor(
|
||||
return RunnerDescriptor(
|
||||
id=runner_id,
|
||||
component_kind=typed_manifest.component_kind,
|
||||
usages=typed_manifest.usages,
|
||||
supported_event_patterns=typed_manifest.supported_event_patterns,
|
||||
source='plugin',
|
||||
label=typed_manifest.label,
|
||||
@@ -152,8 +153,8 @@ class AgentRunnerRegistry:
|
||||
context: TenantContext,
|
||||
bound_plugins: list[str] | None = None,
|
||||
use_cache: bool = True,
|
||||
component_kind: str = 'AgentRunner',
|
||||
) -> list[AgentRunnerDescriptor]:
|
||||
usage: typing.Literal['agent', 'event'] | None = 'agent',
|
||||
) -> list[RunnerDescriptor]:
|
||||
"""List available runners.
|
||||
|
||||
Args:
|
||||
@@ -173,7 +174,7 @@ class AgentRunnerRegistry:
|
||||
return [
|
||||
r
|
||||
for r in self._filter_runners_by_bound_plugins(cached, bound_plugins)
|
||||
if r.component_kind == component_kind
|
||||
if usage is None or usage in r.usages
|
||||
]
|
||||
|
||||
# Discover fresh (always full list)
|
||||
@@ -187,14 +188,14 @@ class AgentRunnerRegistry:
|
||||
return [
|
||||
r
|
||||
for r in self._filter_runners_by_bound_plugins(runners, bound_plugins)
|
||||
if r.component_kind == component_kind
|
||||
if usage is None or usage in r.usages
|
||||
]
|
||||
|
||||
def _filter_runners_by_bound_plugins(
|
||||
self,
|
||||
runners: dict[str, AgentRunnerDescriptor],
|
||||
runners: dict[str, RunnerDescriptor],
|
||||
bound_plugins: list[str] | None,
|
||||
) -> list[AgentRunnerDescriptor]:
|
||||
) -> list[RunnerDescriptor]:
|
||||
"""Filter runners by bound plugins.
|
||||
|
||||
Args:
|
||||
@@ -222,7 +223,7 @@ class AgentRunnerRegistry:
|
||||
context: TenantContext,
|
||||
runner_id: str,
|
||||
bound_plugins: list[str] | None = None,
|
||||
) -> AgentRunnerDescriptor:
|
||||
) -> RunnerDescriptor:
|
||||
"""Get a specific runner descriptor.
|
||||
|
||||
Args:
|
||||
@@ -230,7 +231,7 @@ class AgentRunnerRegistry:
|
||||
bound_plugins: Optional bound plugins filter
|
||||
|
||||
Returns:
|
||||
AgentRunnerDescriptor
|
||||
RunnerDescriptor
|
||||
|
||||
Raises:
|
||||
RunnerNotFoundError: If runner not found
|
||||
@@ -242,8 +243,7 @@ class AgentRunnerRegistry:
|
||||
except ValueError as e:
|
||||
raise RunnerNotFoundError(runner_id) from e
|
||||
|
||||
component_kind = 'EventProcessor' if runner_id.startswith('event_processor:') else 'AgentRunner'
|
||||
runners = await self.list_runners(context, bound_plugins=None, component_kind=component_kind)
|
||||
runners = await self.list_runners(context, bound_plugins=None, usage=None)
|
||||
descriptor = next((item for item in runners if item.id == runner_id), None)
|
||||
if descriptor is None:
|
||||
# The runtime launches installed plugins asynchronously, so an
|
||||
@@ -252,7 +252,7 @@ class AgentRunnerRegistry:
|
||||
context,
|
||||
bound_plugins=None,
|
||||
use_cache=False,
|
||||
component_kind=component_kind,
|
||||
usage=None,
|
||||
)
|
||||
descriptor = next((item for item in runners if item.id == runner_id), None)
|
||||
if descriptor is None:
|
||||
|
||||
@@ -6,7 +6,7 @@ import typing
|
||||
|
||||
from ...core import app
|
||||
from ...api.http.context import ExecutionContext
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .context_builder import (
|
||||
AgentResources,
|
||||
ModelResource,
|
||||
@@ -54,7 +54,7 @@ class AgentResourceBuilder:
|
||||
execution_context: ExecutionContext,
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> AgentResources:
|
||||
"""Build AgentResources from event and binding.
|
||||
|
||||
@@ -127,7 +127,7 @@ class AgentResourceBuilder:
|
||||
execution_context: ExecutionContext,
|
||||
manifest_perms: typing.Any,
|
||||
resource_policy: typing.Any,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
runner_config: dict[str, typing.Any],
|
||||
) -> list[ModelResource]:
|
||||
"""Build models list from binding."""
|
||||
@@ -174,7 +174,7 @@ class AgentResourceBuilder:
|
||||
execution_context: ExecutionContext,
|
||||
manifest_perms: typing.Any,
|
||||
resource_policy: typing.Any,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
runner_config: dict[str, typing.Any],
|
||||
) -> list[ToolResource]:
|
||||
"""Build tools list from binding."""
|
||||
@@ -274,7 +274,7 @@ class AgentResourceBuilder:
|
||||
execution_context: ExecutionContext,
|
||||
manifest_perms: typing.Any,
|
||||
resource_policy: typing.Any,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
runner_config: dict[str, typing.Any],
|
||||
) -> list[KnowledgeBaseResource]:
|
||||
"""Build knowledge bases list from binding."""
|
||||
@@ -321,7 +321,7 @@ class AgentResourceBuilder:
|
||||
self,
|
||||
execution_context: ExecutionContext,
|
||||
resource_policy: typing.Any,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> list[SkillResource]:
|
||||
"""Build pipeline-visible skill resource facts.
|
||||
|
||||
@@ -372,7 +372,7 @@ class AgentResourceBuilder:
|
||||
execution_context: ExecutionContext,
|
||||
models: list[ModelResource],
|
||||
seen_model_ids: set[str],
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
runner_config: dict[str, typing.Any],
|
||||
include_llm: bool,
|
||||
include_rerank: bool,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Project AgentRunner configuration into Host resource policy."""
|
||||
"""Project Runner configuration into Host resource policy."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""Agent result normalizer for converting AgentRunResult to Pipeline messages."""
|
||||
"""Agent result normalizer for converting RunnerResult to Pipeline messages."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
import pydantic
|
||||
from langbot_plugin.api.entities.builtin.agent_runner.result import (
|
||||
from langbot_plugin.api.entities.builtin.runner.result import (
|
||||
ActionRequestedPayload,
|
||||
MessageCompletedPayload,
|
||||
MessageDeltaPayload,
|
||||
@@ -19,7 +19,7 @@ from langbot_plugin.api.entities.builtin.agent_runner.result import (
|
||||
from langbot_plugin.api.entities.builtin.provider import message as provider_message
|
||||
|
||||
from ...core import app
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .errors import RunnerExecutionError, RunnerProtocolError
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ STRICT_RESULT_PAYLOADS: dict[str, type[pydantic.BaseModel]] = {
|
||||
|
||||
|
||||
class AgentResultNormalizer:
|
||||
"""Normalizer for converting AgentRunResult to Pipeline messages.
|
||||
"""Normalizer for converting RunnerResult to Pipeline messages.
|
||||
|
||||
Responsibilities:
|
||||
- Accept only supported result types (message.delta, message.completed, etc.)
|
||||
@@ -71,9 +71,9 @@ class AgentResultNormalizer:
|
||||
async def normalize(
|
||||
self,
|
||||
result_dict: dict[str, typing.Any],
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> provider_message.Message | provider_message.MessageChunk | None:
|
||||
"""Normalize AgentRunResult to Message or MessageChunk.
|
||||
"""Normalize RunnerResult to Message or MessageChunk.
|
||||
|
||||
Args:
|
||||
result_dict: Raw result dict from plugin runtime
|
||||
@@ -186,7 +186,7 @@ class AgentResultNormalizer:
|
||||
self,
|
||||
result_type: str,
|
||||
data: typing.Any,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> bool:
|
||||
"""Validate typed payloads that affect Host state or delivery.
|
||||
|
||||
@@ -210,7 +210,7 @@ class AgentResultNormalizer:
|
||||
def _normalize_message_delta(
|
||||
self,
|
||||
data: dict[str, typing.Any],
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> provider_message.MessageChunk:
|
||||
"""Normalize message.delta to MessageChunk."""
|
||||
chunk_data = data.get('chunk', {})
|
||||
@@ -226,7 +226,7 @@ class AgentResultNormalizer:
|
||||
def _normalize_message_completed(
|
||||
self,
|
||||
data: dict[str, typing.Any],
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> provider_message.Message:
|
||||
"""Normalize message.completed to Message."""
|
||||
message_data = data.get('message', {})
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""Run-side effects for AgentRunner executions."""
|
||||
"""Run-side effects for Runner executions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from ...core import app
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .errors import RunnerProtocolError
|
||||
from .host_models import AgentBinding, AgentEventEnvelope
|
||||
from .persistent_state_store import PersistentStateStore, get_persistent_state_store
|
||||
@@ -73,7 +73,7 @@ class AgentRunJournal:
|
||||
*,
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
context: dict[str, typing.Any],
|
||||
authorization: dict[str, typing.Any],
|
||||
) -> dict[str, typing.Any]:
|
||||
@@ -112,7 +112,7 @@ class AgentRunJournal:
|
||||
source: str = 'runner',
|
||||
metadata: dict[str, typing.Any] | None = None,
|
||||
) -> dict[str, typing.Any]:
|
||||
"""Persist one AgentRunResult in the run ledger."""
|
||||
"""Persist one RunnerResult in the run ledger."""
|
||||
usage = result_dict.get('usage')
|
||||
if hasattr(usage, 'model_dump'):
|
||||
usage = usage.model_dump(mode='json')
|
||||
@@ -153,7 +153,7 @@ class AgentRunJournal:
|
||||
result_dict: dict[str, typing.Any],
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
run_id: str | None = None,
|
||||
) -> None:
|
||||
"""Handle state.updated result in event-first mode."""
|
||||
|
||||
@@ -61,7 +61,7 @@ class AgentRunSession(typing.TypedDict):
|
||||
Stored in AgentRunSessionRegistry for proxy action permission validation.
|
||||
|
||||
Fields:
|
||||
run_id: Unique run identifier (UUID from AgentRunContext)
|
||||
run_id: Unique run identifier (UUID from RunnerContext)
|
||||
runner_id: Runner descriptor ID (plugin:author/name/runner)
|
||||
query_id: Host entry query ID, only present for query-based adapters
|
||||
execution_query: Host-only Query used by providers and tool loaders
|
||||
@@ -84,10 +84,10 @@ class AgentRunSession(typing.TypedDict):
|
||||
class AgentRunSessionRegistry:
|
||||
"""Registry for active agent run sessions.
|
||||
|
||||
Host-owned registry for tracking active AgentRunner executions.
|
||||
Host-owned registry for tracking active Runner executions.
|
||||
Used by proxy actions in handler.py to validate resource access.
|
||||
|
||||
Key: run_id (UUID from AgentRunContext)
|
||||
Key: run_id (UUID from RunnerContext)
|
||||
Value: AgentRunSession with authorized resources
|
||||
|
||||
Thread-safe via asyncio.Lock.
|
||||
@@ -130,7 +130,7 @@ class AgentRunSessionRegistry:
|
||||
bot_id: Bot UUID for history/event access
|
||||
workspace_id: Workspace ID for history/event access
|
||||
thread_id: Thread ID for history/event access
|
||||
available_apis: Run-scoped pull APIs exposed in AgentRunContext
|
||||
available_apis: Run-scoped pull APIs exposed in RunnerContext
|
||||
state_policy: State policy from binding (enable_state, state_scopes)
|
||||
state_context: Context for state API (scope_keys, binding_identity, etc.)
|
||||
execution_query: Host-only Query used for provider and tool execution
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"""State scope key helpers for AgentRunner host-owned state."""
|
||||
"""State scope key helpers for Runner host-owned state."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import typing
|
||||
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .host_models import AgentBinding, AgentEventEnvelope
|
||||
|
||||
|
||||
@@ -47,7 +48,7 @@ def _scope_hash(scope: str, parts: dict[str, typing.Any]) -> str:
|
||||
def _base_scope_parts(
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> dict[str, typing.Any]:
|
||||
return {
|
||||
'runner_id': descriptor.id,
|
||||
@@ -61,7 +62,7 @@ def build_state_scope_key(
|
||||
scope: str,
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> str | None:
|
||||
"""Build the storage key for one state scope.
|
||||
|
||||
@@ -72,29 +73,38 @@ def build_state_scope_key(
|
||||
if scope == 'conversation':
|
||||
if not event.conversation_id:
|
||||
return None
|
||||
return _scope_hash(scope, {
|
||||
**base_parts,
|
||||
'conversation_id': event.conversation_id,
|
||||
'thread_id': event.thread_id,
|
||||
})
|
||||
return _scope_hash(
|
||||
scope,
|
||||
{
|
||||
**base_parts,
|
||||
'conversation_id': event.conversation_id,
|
||||
'thread_id': event.thread_id,
|
||||
},
|
||||
)
|
||||
|
||||
if scope == 'actor':
|
||||
if not event.actor or not event.actor.actor_id:
|
||||
return None
|
||||
return _scope_hash(scope, {
|
||||
**base_parts,
|
||||
'actor_type': event.actor.actor_type or 'user',
|
||||
'actor_id': event.actor.actor_id,
|
||||
})
|
||||
return _scope_hash(
|
||||
scope,
|
||||
{
|
||||
**base_parts,
|
||||
'actor_type': event.actor.actor_type or 'user',
|
||||
'actor_id': event.actor.actor_id,
|
||||
},
|
||||
)
|
||||
|
||||
if scope == 'subject':
|
||||
if not event.subject or not event.subject.subject_id:
|
||||
return None
|
||||
return _scope_hash(scope, {
|
||||
**base_parts,
|
||||
'subject_type': event.subject.subject_type or 'unknown',
|
||||
'subject_id': event.subject.subject_id,
|
||||
})
|
||||
return _scope_hash(
|
||||
scope,
|
||||
{
|
||||
**base_parts,
|
||||
'subject_type': event.subject.subject_type or 'unknown',
|
||||
'subject_id': event.subject.subject_id,
|
||||
},
|
||||
)
|
||||
|
||||
if scope == 'runner':
|
||||
return _scope_hash(scope, base_parts)
|
||||
@@ -105,7 +115,7 @@ def build_state_scope_key(
|
||||
def build_state_scope_keys(
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> dict[str, str]:
|
||||
"""Build all available scope keys for an event/binding pair."""
|
||||
scope_keys: dict[str, str] = {}
|
||||
@@ -119,7 +129,7 @@ def build_state_scope_keys(
|
||||
def build_state_context(
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
descriptor: RunnerDescriptor,
|
||||
) -> dict[str, typing.Any]:
|
||||
"""Build the State API context stored in the run session."""
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Transcript store for writing and querying conversation history."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -46,9 +47,7 @@ class TranscriptStore:
|
||||
|
||||
def __init__(self, engine: AsyncEngine):
|
||||
self.engine = engine
|
||||
self._session_factory = sessionmaker(
|
||||
engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
self._session_factory = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async def append_transcript(
|
||||
self,
|
||||
@@ -62,7 +61,7 @@ class TranscriptStore:
|
||||
content_json: dict[str, typing.Any] | None = None,
|
||||
attachment_refs: list[dict[str, typing.Any]] | None = None,
|
||||
thread_id: str | None = None,
|
||||
item_type: str = "message",
|
||||
item_type: str = 'message',
|
||||
run_id: str | None = None,
|
||||
runner_id: str | None = None,
|
||||
metadata: dict[str, typing.Any] | None = None,
|
||||
@@ -93,7 +92,7 @@ class TranscriptStore:
|
||||
|
||||
# Truncate content if too long
|
||||
if content and len(content) > self.MAX_CONTENT_LENGTH:
|
||||
content = content[:self.MAX_CONTENT_LENGTH - 3] + "..."
|
||||
content = content[: self.MAX_CONTENT_LENGTH - 3] + '...'
|
||||
|
||||
async with self._session_factory() as session:
|
||||
item = Transcript(
|
||||
@@ -127,7 +126,7 @@ class TranscriptStore:
|
||||
before_seq: int | None = None,
|
||||
after_seq: int | None = None,
|
||||
limit: int = 50,
|
||||
direction: str = "backward",
|
||||
direction: str = 'backward',
|
||||
include_attachments: bool = False,
|
||||
bot_id: str | None = None,
|
||||
workspace_id: str | None = None,
|
||||
@@ -154,15 +153,13 @@ class TranscriptStore:
|
||||
limit = min(limit, self.HARD_LIMIT)
|
||||
|
||||
async with self._session_factory() as session:
|
||||
query = sqlalchemy.select(Transcript).where(
|
||||
Transcript.conversation_id == conversation_id
|
||||
)
|
||||
query = sqlalchemy.select(Transcript).where(Transcript.conversation_id == conversation_id)
|
||||
query = self._apply_scope_filters(query, bot_id, workspace_id, thread_id, strict_thread)
|
||||
|
||||
if direction == "backward" and before_seq is not None:
|
||||
if direction == 'backward' and before_seq is not None:
|
||||
query = query.where(Transcript.seq < before_seq)
|
||||
query = query.order_by(Transcript.seq.desc())
|
||||
elif direction == "forward" and after_seq is not None:
|
||||
elif direction == 'forward' and after_seq is not None:
|
||||
query = query.where(Transcript.seq > after_seq)
|
||||
query = query.order_by(Transcript.seq.asc())
|
||||
else:
|
||||
@@ -181,7 +178,7 @@ class TranscriptStore:
|
||||
next_seq = None
|
||||
prev_seq = None
|
||||
|
||||
if direction == "backward":
|
||||
if direction == 'backward':
|
||||
# Items are in descending order
|
||||
if items:
|
||||
next_seq = items[-1].get('seq') if has_more else None
|
||||
@@ -225,7 +222,7 @@ class TranscriptStore:
|
||||
async with self._session_factory() as session:
|
||||
query = sqlalchemy.select(Transcript).where(
|
||||
Transcript.conversation_id == conversation_id,
|
||||
Transcript.content.ilike(f"%{query_text}%"),
|
||||
Transcript.content.ilike(f'%{query_text}%'),
|
||||
)
|
||||
query = self._apply_scope_filters(query, bot_id, workspace_id, thread_id, strict_thread)
|
||||
|
||||
@@ -278,14 +275,14 @@ class TranscriptStore:
|
||||
) -> list[provider_message.Message]:
|
||||
"""Project Transcript rows into the legacy provider Message view.
|
||||
|
||||
AgentRunner history is canonical in Transcript. This view exists for
|
||||
Runner history is canonical in Transcript. This view exists for
|
||||
legacy Pipeline readers such as PromptPreProcessing that still expect
|
||||
query.messages.
|
||||
"""
|
||||
items, _, _, _ = await self.page_transcript(
|
||||
conversation_id=conversation_id,
|
||||
limit=limit,
|
||||
direction="backward",
|
||||
direction='backward',
|
||||
bot_id=bot_id,
|
||||
workspace_id=workspace_id,
|
||||
thread_id=thread_id,
|
||||
@@ -353,9 +350,7 @@ class TranscriptStore:
|
||||
) -> int:
|
||||
"""Delete Transcript rows created before the supplied timestamp."""
|
||||
async with self._session_factory() as session:
|
||||
result = await session.execute(
|
||||
sqlalchemy.delete(Transcript).where(Transcript.created_at < before)
|
||||
)
|
||||
result = await session.execute(sqlalchemy.delete(Transcript).where(Transcript.created_at < before))
|
||||
await session.commit()
|
||||
return result.rowcount or 0
|
||||
|
||||
@@ -363,8 +358,9 @@ class TranscriptStore:
|
||||
"""Fallback next sequence number for stores that cannot expose autoincrement IDs."""
|
||||
async with self._session_factory() as session:
|
||||
result = await session.execute(
|
||||
sqlalchemy.select(sqlalchemy.func.max(Transcript.seq))
|
||||
.where(Transcript.conversation_id == conversation_id)
|
||||
sqlalchemy.select(sqlalchemy.func.max(Transcript.seq)).where(
|
||||
Transcript.conversation_id == conversation_id
|
||||
)
|
||||
)
|
||||
max_seq = result.scalar()
|
||||
return (max_seq or 0) + 1
|
||||
|
||||
Reference in New Issue
Block a user