Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 12 additions & 47 deletions bindings/python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ impl PySession {
/// Rust controls the lifecycle:
/// 1. Checks initialization flag (error if not initialized)
/// 2. Emits pre-execution events (session:start or session:resume)
/// with optional `raw` field when session.raw=true
/// 3. Delegates orchestrator call to `_session_exec.run_orchestrator()`
/// via `into_future` (Python handles mount point access + kwargs)
/// 4. Checks cancellation after execution
Expand All @@ -512,32 +513,27 @@ impl PySession {
// Step 2: Prepare the Python orchestrator coroutine (we have the GIL here)
let helper = py.import("amplifier_core._session_exec")?;
let run_fn = helper.getattr("run_orchestrator")?;
let debug_fn = helper.getattr("emit_debug_events")?;
let raw_fn = helper.getattr("emit_raw_field_if_configured")?;

// Prepare the orchestrator call coroutine
let orch_coro = run_fn.call1((self.coordinator.bind(py), &prompt))?;
let orch_coro_py: Py<PyAny> = orch_coro.unbind();

// Determine event names based on is_resumed
let (event_base, event_debug, event_raw) = if self.is_resumed {
(
"session:resume",
"session:resume:debug",
"session:resume:raw",
)
// Determine event name based on is_resumed
let event_base = if self.is_resumed {
"session:resume"
} else {
("session:start", "session:start:debug", "session:start:raw")
"session:start"
};

// Prepare debug events coroutine
let debug_coro = debug_fn.call1((
// Prepare raw-field emission coroutine (no-op if session.raw=false)
let raw_coro = raw_fn.call1((
self.coordinator.bind(py),
self.config.bind(py),
&self.cached_session_id,
event_debug,
event_raw,
event_base,
))?;
let debug_coro_py: Py<PyAny> = debug_coro.unbind();
let debug_coro_py: Py<PyAny> = raw_coro.unbind();

// Get the inner HookRegistry for direct Rust emit (avoids PyO3 Future/coroutine mismatch:
// calling a #[pymethods] fn that uses future_into_py returns a Future object, but
Expand Down Expand Up @@ -2632,35 +2628,14 @@ fn _engine(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(compute_delay, m)?)?;

// -----------------------------------------------------------------------
// Event constants — expose all 51 canonical events from amplifier_core
// Event constants — expose all 41 canonical events from amplifier_core
// -----------------------------------------------------------------------

// Session lifecycle
m.add("SESSION_START", amplifier_core::events::SESSION_START)?;
m.add(
"SESSION_START_DEBUG",
amplifier_core::events::SESSION_START_DEBUG,
)?;
m.add(
"SESSION_START_RAW",
amplifier_core::events::SESSION_START_RAW,
)?;
m.add("SESSION_END", amplifier_core::events::SESSION_END)?;
m.add("SESSION_FORK", amplifier_core::events::SESSION_FORK)?;
m.add(
"SESSION_FORK_DEBUG",
amplifier_core::events::SESSION_FORK_DEBUG,
)?;
m.add("SESSION_FORK_RAW", amplifier_core::events::SESSION_FORK_RAW)?;
m.add("SESSION_RESUME", amplifier_core::events::SESSION_RESUME)?;
m.add(
"SESSION_RESUME_DEBUG",
amplifier_core::events::SESSION_RESUME_DEBUG,
)?;
m.add(
"SESSION_RESUME_RAW",
amplifier_core::events::SESSION_RESUME_RAW,
)?;

// Prompt lifecycle
m.add("PROMPT_SUBMIT", amplifier_core::events::PROMPT_SUBMIT)?;
Expand Down Expand Up @@ -2688,19 +2663,9 @@ fn _engine(m: &Bound<'_, PyModule>) -> PyResult<()> {
)?;
m.add("PROVIDER_RESOLVE", amplifier_core::events::PROVIDER_RESOLVE)?;

// LLM request/response
// LLM events
m.add("LLM_REQUEST", amplifier_core::events::LLM_REQUEST)?;
m.add(
"LLM_REQUEST_DEBUG",
amplifier_core::events::LLM_REQUEST_DEBUG,
)?;
m.add("LLM_REQUEST_RAW", amplifier_core::events::LLM_REQUEST_RAW)?;
m.add("LLM_RESPONSE", amplifier_core::events::LLM_RESPONSE)?;
m.add(
"LLM_RESPONSE_DEBUG",
amplifier_core::events::LLM_RESPONSE_DEBUG,
)?;
m.add("LLM_RESPONSE_RAW", amplifier_core::events::LLM_RESPONSE_RAW)?;

// Content block events
m.add(
Expand Down
30 changes: 10 additions & 20 deletions bindings/python/tests/test_event_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@
import pytest


# All 51 event constant names that should be importable from _engine
# All 41 event constant names that should be importable from _engine
ALL_EVENT_NAMES = [
"SESSION_START",
"SESSION_START_DEBUG",
"SESSION_START_RAW",
"SESSION_END",
"SESSION_FORK",
"SESSION_FORK_DEBUG",
"SESSION_FORK_RAW",
"SESSION_RESUME",
"SESSION_RESUME_DEBUG",
"SESSION_RESUME_RAW",
"PROMPT_SUBMIT",
"PROMPT_COMPLETE",
"PLAN_START",
Expand All @@ -27,11 +21,7 @@
"PROVIDER_TOOL_SEQUENCE_REPAIRED",
"PROVIDER_RESOLVE",
"LLM_REQUEST",
"LLM_REQUEST_DEBUG",
"LLM_REQUEST_RAW",
"LLM_RESPONSE",
"LLM_RESPONSE_DEBUG",
"LLM_RESPONSE_RAW",
"CONTENT_BLOCK_START",
"CONTENT_BLOCK_DELTA",
"CONTENT_BLOCK_END",
Expand Down Expand Up @@ -60,7 +50,7 @@


class TestAllEventConstantsImportable:
"""Test that all 51 event constants are importable from _engine and are strings."""
"""Test that all 41 event constants are importable from _engine and are strings."""

@pytest.mark.parametrize("name", ALL_EVENT_NAMES)
def test_event_constant_importable_and_is_string(self, name):
Expand Down Expand Up @@ -91,30 +81,30 @@ def test_provider_tool_sequence_repaired(self):


class TestAllEventsList:
"""Test that ALL_EVENTS is exposed as a list with all 51 items."""
"""Test that ALL_EVENTS is exposed as a list with all 41 items."""

def test_all_events_is_list(self):
from amplifier_core._engine import ALL_EVENTS
from amplifier_core.events import ALL_EVENTS

assert isinstance(ALL_EVENTS, list), (
f"ALL_EVENTS should be a list, got {type(ALL_EVENTS)}"
)

def test_all_events_count(self):
from amplifier_core._engine import ALL_EVENTS
from amplifier_core.events import ALL_EVENTS

assert len(ALL_EVENTS) == 51, f"Expected 51 events, got {len(ALL_EVENTS)}"
assert len(ALL_EVENTS) == 41, f"Expected 41 events, got {len(ALL_EVENTS)}"

def test_all_events_contains_all_constants(self):
import amplifier_core._engine as engine
from amplifier_core._engine import ALL_EVENTS
import amplifier_core.events as events
from amplifier_core.events import ALL_EVENTS

for name in ALL_EVENT_NAMES:
value = getattr(engine, name)
value = getattr(events, name)
assert value in ALL_EVENTS, f"{name}={value!r} not found in ALL_EVENTS"

def test_all_events_all_strings(self):
from amplifier_core._engine import ALL_EVENTS
from amplifier_core.events import ALL_EVENTS

for event in ALL_EVENTS:
assert isinstance(event, str), (
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/tests/test_python_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_events_reexport_provider_throttle():
def test_events_reexport_all_events():
from amplifier_core.events import ALL_EVENTS

assert len(ALL_EVENTS) == 51
assert len(ALL_EVENTS) == 41


def test_capabilities_reexport_tools():
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/tests/test_schema_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def test_event_constants_match():
assert TOOL_ERROR == "tool:error"
assert CANCEL_REQUESTED == "cancel:requested"
assert CANCEL_COMPLETED == "cancel:completed"
assert len(ALL_EVENTS) == 51
assert len(ALL_EVENTS) == 41


def test_hook_result_json_roundtrip():
Expand Down
6 changes: 3 additions & 3 deletions bindings/python/tests/test_switchover_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,12 @@ def test_session_exec_is_thin_helper():
"""_session_exec.py must still exist as a thin boundary helper called by Rust.

Rust's PySession::execute() imports amplifier_core._session_exec and calls
run_orchestrator() and emit_debug_events(). CANNOT be deleted.
run_orchestrator() and emit_raw_field_if_configured(). CANNOT be deleted.
"""
from amplifier_core._session_exec import run_orchestrator, emit_debug_events
from amplifier_core._session_exec import run_orchestrator, emit_raw_field_if_configured

assert callable(run_orchestrator)
assert callable(emit_debug_events)
assert callable(emit_raw_field_if_configured)


def test_collect_helper_is_boundary_helper():
Expand Down
Loading