87a7ce35d7
CI / benchmark-regression (push) Has been skipped
CI / helm (push) Successful in 38s
CI / lint (push) Successful in 1m14s
CI / build (push) Successful in 1m10s
CI / push-validation (push) Successful in 49s
CI / quality (push) Successful in 1m35s
CI / typecheck (push) Successful in 1m39s
CI / security (push) Successful in 1m45s
CI / integration_tests (push) Successful in 3m44s
CI / e2e_tests (push) Successful in 4m29s
CI / unit_tests (push) Successful in 5m10s
CI / docker (push) Successful in 1m55s
CI / coverage (push) Successful in 11m8s
CI / status-check (push) Successful in 3s
CI / benchmark-publish (push) Successful in 1h20m21s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Failing after 1m35s
CI / docker (pull_request) Successful in 1m26s
CI / unit_tests (pull_request) Successful in 6m40s
CI / push-validation (pull_request) Successful in 1m24s
CI / quality (pull_request) Successful in 4m4s
CI / integration_tests (pull_request) Successful in 5m44s
CI / e2e_tests (pull_request) Failing after 6m5s
CI / helm (pull_request) Successful in 1m10s
CI / build (pull_request) Successful in 2m39s
CI / lint (pull_request) Successful in 2m58s
CI / typecheck (pull_request) Successful in 3m56s
CI / security (pull_request) Successful in 3m56s
CI / coverage (pull_request) Successful in 10m52s
CI / status-check (pull_request) Failing after 3s
Replace the M3 stub in `agents session tell` with real orchestrator actor
invocation via `SessionWorkflow.tell()`. The stub echoed a canned
`Acknowledged: ...` response without calling any LLM or actor; this
commit wires up the full pipeline:
Architecture:
- New `SessionWorkflow` (Application layer) orchestrates LLM invocation
for session tell. Accepts a `ProviderRegistry` and `ToolRegistry`;
falls back to `FakeListLLM` when no provider is configured.
- `LangChainSessionCaller` implements the `LLMCaller` protocol, building
history-aware LangChain message lists from the session conversation and
invoking the LLM via `ToolCallingRuntime.run_tool_loop()`.
- `TellResult` (Pydantic BaseModel) carries the assistant response plus
token usage (input_tokens, output_tokens, cost_usd, duration_ms).
Domain / service layer:
- `SessionActorNotConfiguredError` added to the session domain model;
raised when tell is invoked with no actor on the session and no
`--actor` override. Clear message; CLI exits with code 1.
- `SessionService.get_messages()` abstract method added (+ implementation
in `PersistentSessionService`) to load ordered message history.
A2A facade:
- `A2aLocalFacade` gains `message/send` and `message/stream` standard
A2A operation handlers that route to `SessionWorkflow.tell()`.
Total supported operations count: 42 → 44.
CLI:
- `session tell` command delegates to `_build_session_workflow()`
(patchable factory) instead of directly calling `SessionService`.
- Non-streaming: `SessionWorkflow.tell()` returns `TellResult`; output
includes a Usage panel (Rich/Plain) or a `usage` object (JSON/YAML).
- Streaming: `SessionWorkflow.tell_stream()` yields tokens; CLI prints
them via `console.print` (not raw `sys.stdout.write`).
- Token usage recorded via `SessionService.update_token_usage()` in
both paths.
Tests:
- New Behave feature `session_tell_llm.feature` (4 scenarios): real LLM
response persisted; streaming yields tokens; no-actor exits code 1;
`--actor` override resolves correctly.
- New Robot suite `session_tell_llm.robot` (4 tests): end-to-end with
stub LLM injected via monkey-patching `_resolve_llm`.
- Updated all existing `session tell` test steps to patch
`_build_session_workflow` returning a mock `TellResult` so tests
remain isolated from the LLM layer.
- Updated operation-count assertions (42 → 44) in
`a2a_cli_facade_integration`, `consolidated_misc`,
`m6_autonomy_acceptance` feature files and steps.
Cycle 7 (Review ID 8088) fixes:
- Blocker 4: Split `session_workflow.py` (was 801 lines) into two files:
`session_workflow.py` (467 lines) and `session_caller.py` (318 lines).
Extracted: `LangChainSessionCaller`, `extract_content`,
`extract_token_usage`, `estimate_cost`, `estimate_tokens`,
`history_to_langchain_messages`, and stub classes.
- Blocker 5: Route CLI streaming path through
`_facade_dispatch("message/stream", ...)` instead of directly
calling `workflow.tell_stream()`. The facade's
`_handle_message_stream` falls back to non-streaming with
`streamed: false` (acceptable for this milestone per the spec).
- Updated streaming test assertion to validate full response presence
(no longer checks token-by-token word positions, since the facade
fallback returns a complete message).
ISSUES CLOSED: #5784
129 lines
3.8 KiB
Python
129 lines
3.8 KiB
Python
"""Helper script for a2a_facade.robot smoke tests.
|
|
|
|
Each subcommand is a self-contained check that prints a sentinel on success.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Ensure local source tree is importable
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
from cleveragents.a2a.errors import ( # noqa: E402
|
|
A2aNotAvailableError,
|
|
A2aVersionMismatchError,
|
|
)
|
|
from cleveragents.a2a.events import A2aEventQueue # noqa: E402
|
|
from cleveragents.a2a.facade import A2aLocalFacade # noqa: E402
|
|
from cleveragents.a2a.models import A2aEvent, A2aRequest # noqa: E402
|
|
from cleveragents.a2a.transport import A2aHttpTransport # noqa: E402
|
|
from cleveragents.a2a.versioning import A2aVersionNegotiator # noqa: E402
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Subcommands
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def facade_dispatch() -> None:
|
|
"""Dispatch session.create via local facade."""
|
|
facade = A2aLocalFacade()
|
|
request = A2aRequest(method="session.create")
|
|
response = facade.dispatch(request)
|
|
if (
|
|
response.error is None
|
|
and response.result is not None
|
|
and "session_id" in response.result
|
|
):
|
|
print("a2a-facade-dispatch-ok")
|
|
else:
|
|
print(f"FAIL: unexpected response {response}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def transport_stub() -> None:
|
|
"""Verify transport stub raises A2aNotAvailableError."""
|
|
transport = A2aHttpTransport()
|
|
try:
|
|
transport.connect("http://localhost:8080")
|
|
print("FAIL: should have raised", file=sys.stderr)
|
|
sys.exit(1)
|
|
except A2aNotAvailableError:
|
|
pass
|
|
|
|
if transport.is_connected() is False:
|
|
print("a2a-transport-stub-ok")
|
|
else:
|
|
print("FAIL: is_connected should be False", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def event_queue() -> None:
|
|
"""Verify local event queue publish/subscribe."""
|
|
queue = A2aEventQueue()
|
|
received: list[A2aEvent] = []
|
|
queue.subscribe_local(lambda e: received.append(e))
|
|
queue.publish(A2aEvent(event_type="test.event"))
|
|
if len(received) == 1 and received[0].event_type == "test.event":
|
|
print("a2a-event-queue-ok")
|
|
else:
|
|
print(f"FAIL: received={received}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def version_negotiate() -> None:
|
|
"""Verify version negotiation."""
|
|
negotiator = A2aVersionNegotiator()
|
|
result = negotiator.negotiate("1.0")
|
|
if result != "1.0":
|
|
print("FAIL: expected 1.0", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
try:
|
|
negotiator.negotiate("2.0")
|
|
print("FAIL: should have raised", file=sys.stderr)
|
|
sys.exit(1)
|
|
except A2aVersionMismatchError:
|
|
pass
|
|
|
|
print("a2a-version-negotiate-ok")
|
|
|
|
|
|
def list_operations() -> None:
|
|
"""Verify list_operations returns expected operations."""
|
|
facade = A2aLocalFacade()
|
|
ops = facade.list_operations()
|
|
expected = {"session.create", "plan.create", "plan.execute", "context.get"}
|
|
if expected.issubset(set(ops)) and len(ops) == 44:
|
|
print("a2a-list-operations-ok")
|
|
else:
|
|
print(f"FAIL: ops={ops}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dispatch
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_COMMANDS = {
|
|
"facade-dispatch": facade_dispatch,
|
|
"transport-stub": transport_stub,
|
|
"event-queue": event_queue,
|
|
"version-negotiate": version_negotiate,
|
|
"list-operations": list_operations,
|
|
}
|
|
|
|
|
|
def main() -> None:
|
|
if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
|
|
print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr)
|
|
sys.exit(2)
|
|
_COMMANDS[sys.argv[1]]()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|