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
42 changes: 21 additions & 21 deletions packages/bub-acp-server/src/bub_acp_server/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,14 @@
ResumeSessionResponse,
)
from acp.helpers import start_tool_call, tool_content, update_tool_call
from bub import RuntimeChoice, RuntimeOptions, hookimpl
from bub import hookimpl
from bub.channels.contracts import ChannelRouter
from bub.channels.message import ChannelMessage, MediaItem, MediaType
from bub.envelope import content_of, field_of
from bub.runtime import StreamEvent
from bub.envelope import Envelope, content_of, field_of
from bub.model_selection import ModelChoice, ModelOptions
from bub.streaming import StreamEvent
from bub.tape import TapeEntry, TapeQuery
from bub.types import Envelope, OutboundChannelRouter, TurnResult
from bub.turn import TurnResult

from bub_acp_server.config import ACPServerSettings

Expand Down Expand Up @@ -362,7 +364,7 @@ async def close_session(

async def cancel(self, session_id: str, **kwargs: Any) -> None:
del kwargs
await self.framework.quit_via_router(session_id)
await self.framework.quit_via_channel_router(session_id)

async def set_config_option(
self,
Expand All @@ -374,9 +376,7 @@ async def set_config_option(
del kwargs
session = self._sessions.get(session_id) or self._adopt_session(session_id)
session.touch()
config_options = await self._set_session_runtime_option(
session, config_id, value
)
config_options = await self._set_session_model_option(session, config_id, value)
self._save_sessions()
return SetSessionConfigOptionResponse(config_options=config_options)

Expand Down Expand Up @@ -551,14 +551,14 @@ async def _load_tape_entries(self, session: ACPSession) -> list[TapeEntry]:
async def _session_config_options(
self, session: ACPSession
) -> list[SessionConfigOptionSelect] | None:
runtime_options = await self.framework.get_runtime_options(
model_options = await self.framework.get_model_options(
session_id=session.session_id,
workspace=session.cwd,
)
acp_options = _runtime_options_to_acp_config_options(runtime_options, session)
acp_options = _model_options_to_acp_config_options(model_options, session)
return acp_options or None

async def _set_session_runtime_option(
async def _set_session_model_option(
self,
session: ACPSession,
config_id: str,
Expand Down Expand Up @@ -603,18 +603,18 @@ async def _process_inbound_with_streaming(
async with self._prompt_lock:
router = ACPStreamRouter(client, session.session_id)
previous_router = cast(
OutboundChannelRouter | None,
getattr(self.framework, "_outbound_router", None),
ChannelRouter | None,
getattr(self.framework, "_channel_router", None),
)
previous_workspace = self.framework.workspace
self.framework.workspace = session.cwd
self.framework.bind_outbound_router(router)
self.framework.bind_channel_router(router)
try:
result = await self.framework.process_inbound(
inbound, stream_output=True
)
finally:
self.framework.bind_outbound_router(previous_router)
self.framework.bind_channel_router(previous_router)
self.framework.workspace = previous_workspace
if result.model_output and not router.sent_text:
await client.session_update(
Expand Down Expand Up @@ -754,17 +754,17 @@ def _framework_tape_store(framework: BubFramework) -> object | None:
return store if hasattr(store, "fetch_all") else None


def _runtime_options_to_acp_config_options(
runtime_options: RuntimeOptions, session: ACPSession
def _model_options_to_acp_config_options(
model_options: ModelOptions, session: ACPSession
) -> list[SessionConfigOptionSelect]:
choices = runtime_options.models
choices = model_options.models
if not choices:
return []

choice_ids = {choice.id for choice in choices}
current_value = session.runtime.get("model")
if current_value not in choice_ids:
current_value = runtime_options.current_model
current_value = model_options.current_model
if current_value not in choice_ids:
current_value = choices[0].id
return [
Expand All @@ -773,13 +773,13 @@ def _runtime_options_to_acp_config_options(
id="model",
name="Model",
current_value=current_value,
options=[_runtime_choice_to_acp_option(choice) for choice in choices],
options=[_model_choice_to_acp_option(choice) for choice in choices],
category="model",
)
]


def _runtime_choice_to_acp_option(choice: RuntimeChoice) -> SessionConfigSelectOption:
def _model_choice_to_acp_option(choice: ModelChoice) -> SessionConfigSelectOption:
return SessionConfigSelectOption(
value=choice.id,
name=choice.name or choice.id,
Expand Down
42 changes: 21 additions & 21 deletions packages/bub-acp-server/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

import pytest
from acp.schema import TextContentBlock
from bub import RuntimeChoice, RuntimeOptions
from bub.types import TurnResult
from bub.runtime import StreamEvent
from bub.model_selection import ModelChoice, ModelOptions
from bub.streaming import StreamEvent
from bub.tape import TapeEntry, TapeQuery
from bub.turn import TurnResult

from bub_acp_server import plugin
from bub_acp_server.plugin import BubACPAgent
Expand Down Expand Up @@ -37,17 +37,17 @@ def __init__(self) -> None:
self.messages: list[object] = []
self.stream_output_values: list[bool] = []

def bind_outbound_router(self, router: object) -> None:
def bind_channel_router(self, router: object) -> None:
self.previous_routers.append(router)
self.router = router

async def quit_via_router(self, session_id: str) -> None:
async def quit_via_channel_router(self, session_id: str) -> None:
return None

async def get_runtime_options(
async def get_model_options(
self, *, session_id: str, workspace: Path
) -> RuntimeOptions:
return RuntimeOptions()
) -> ModelOptions:
return ModelOptions()

async def process_inbound(
self, inbound: object, stream_output: bool = False
Expand Down Expand Up @@ -114,20 +114,20 @@ async def stream():
class ConfigFramework(FakeFramework):
def __init__(self) -> None:
super().__init__()
self.runtime_queries: list[tuple[str, Path]] = []
self.model_queries: list[tuple[str, Path]] = []

async def get_runtime_options(
async def get_model_options(
self, *, session_id: str, workspace: Path
) -> RuntimeOptions:
self.runtime_queries.append((session_id, workspace))
return RuntimeOptions(
) -> ModelOptions:
self.model_queries.append((session_id, workspace))
return ModelOptions(
models=[
RuntimeChoice(
ModelChoice(
id="openai:gpt-5",
name="GPT-5",
description="OpenAI model",
),
RuntimeChoice(
ModelChoice(
id="anthropic:claude-sonnet-4-5",
name="Claude Sonnet",
),
Expand Down Expand Up @@ -253,7 +253,7 @@ async def test_session_lifecycle_returns_config_options(tmp_path: Path) -> None:
assert loaded.config_options[0].id == "model"
assert resumed.config_options is not None
assert resumed.config_options[0].id == "model"
assert framework.runtime_queries == [
assert framework.model_queries == [
(created.session_id, tmp_path),
(created.session_id, tmp_path),
(created.session_id, tmp_path),
Expand All @@ -279,26 +279,26 @@ async def test_set_config_option_updates_session_runtime_and_returns_config_opti
}
assert response.config_options[0].id == "model"
assert response.config_options[0].current_value == "anthropic:claude-sonnet-4-5"
assert framework.runtime_queries == [
assert framework.model_queries == [
(created.session_id, tmp_path),
(created.session_id, tmp_path),
]


def test_runtime_options_fall_back_when_persisted_model_is_unavailable(
def test_model_options_fall_back_when_persisted_model_is_unavailable(
tmp_path: Path,
) -> None:
session = plugin.ACPSession(
session_id="session",
cwd=tmp_path,
runtime={"model": "removed:model"},
)
options = RuntimeOptions(
models=[RuntimeChoice(id="available:model")],
options = ModelOptions(
models=[ModelChoice(id="available:model")],
current_model="available:model",
)

config_options = plugin._runtime_options_to_acp_config_options(options, session)
config_options = plugin._model_options_to_acp_config_options(options, session)

assert config_options[0].current_value == "available:model"

Expand Down
18 changes: 9 additions & 9 deletions packages/bub-codex/src/bub_codex/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

import bub
from bub import hookimpl
from bub.runtime import StreamEvent
from bub.types import State
from bub.streaming import StreamEvent
from bub.turn import TurnState
from pydantic import Field
from pydantic_settings import SettingsConfigDict

Expand All @@ -21,11 +21,11 @@

class RuntimeAgent(Protocol):
async def run_stream(
self, *, session_id: str, prompt: str | list[dict], state: State
self, *, session_id: str, prompt: str | list[dict], state: TurnState
) -> AsyncIterable[StreamEvent]: ...


def _load_thread_id(session_id: str, state: State) -> str | None:
def _load_thread_id(session_id: str, state: TurnState) -> str | None:
workpace = workspace_from_state(state)
threads_file = workpace / THREADS_FILE
with contextlib.suppress(FileNotFoundError):
Expand All @@ -34,7 +34,7 @@ def _load_thread_id(session_id: str, state: State) -> str | None:
return threads.get(session_id)


def _save_thread_id(session_id: str, thread_id: str, state: State) -> None:
def _save_thread_id(session_id: str, thread_id: str, state: TurnState) -> None:
workpace = workspace_from_state(state)
threads_file = workpace / THREADS_FILE
if threads_file.exists():
Expand All @@ -47,7 +47,7 @@ def _save_thread_id(session_id: str, thread_id: str, state: State) -> None:
json.dump(threads, f, indent=2)


def workspace_from_state(state: State) -> Path:
def workspace_from_state(state: TurnState) -> Path:
raw = state.get("_runtime_workspace")
if isinstance(raw, str) and raw.strip():
return Path(raw).expanduser().resolve()
Expand All @@ -69,15 +69,15 @@ def _settings() -> CodexSettings:
return bub.ensure_config(CodexSettings)


def _runtime_agent_from_state(state: State) -> RuntimeAgent | None:
def _runtime_agent_from_state(state: TurnState) -> RuntimeAgent | None:
agent = state.get("_runtime_agent")
if agent is None:
return None
return cast("RuntimeAgent", agent)


async def _run_internal_command(
prompt: str, session_id: str, state: State
prompt: str, session_id: str, state: TurnState
) -> str | None:
if not prompt.strip().startswith(","):
return None
Expand All @@ -93,7 +93,7 @@ async def _run_internal_command(


@hookimpl
async def run_model(prompt: str, session_id: str, state: State) -> str:
async def run_model(prompt: str, session_id: str, state: TurnState) -> str:
internal_command_result = await _run_internal_command(prompt, session_id, state)
if internal_command_result is not None:
return internal_command_result
Expand Down
2 changes: 1 addition & 1 deletion packages/bub-codex/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pathlib import Path

import pytest
from bub.runtime import AsyncStreamEvents, StreamEvent
from bub.streaming import AsyncStreamEvents, StreamEvent

from bub_codex import plugin

Expand Down
20 changes: 10 additions & 10 deletions packages/bub-cursor/src/bub_cursor/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
import typer
from bub import BubFramework, hookimpl
from bub.builtin.auth import app as auth_app
from bub.runtime import StreamEvent
from bub.types import State
from bub.streaming import StreamEvent
from bub.turn import TurnState
from pydantic import Field
from pydantic_settings import SettingsConfigDict

Expand All @@ -37,7 +37,7 @@

class RuntimeAgent(Protocol):
async def run_stream(
self, *, session_id: str, prompt: str | list[dict], state: State
self, *, session_id: str, prompt: str | list[dict], state: TurnState
) -> AsyncIterable[StreamEvent]: ...


Expand All @@ -60,14 +60,14 @@ def _settings() -> CursorSettings:
return bub.ensure_config(CursorSettings)


def workspace_from_state(state: State) -> Path:
def workspace_from_state(state: TurnState) -> Path:
raw = state.get("_runtime_workspace")
if isinstance(raw, str) and raw.strip():
return Path(raw).expanduser().resolve()
return Path.cwd().resolve()


def _load_thread_id(session_id: str, state: State) -> str | None:
def _load_thread_id(session_id: str, state: TurnState) -> str | None:
threads_file = workspace_from_state(state) / THREADS_FILE
with contextlib.suppress(FileNotFoundError, json.JSONDecodeError):
with threads_file.open() as f:
Expand All @@ -78,7 +78,7 @@ def _load_thread_id(session_id: str, state: State) -> str | None:
return None


def _save_thread_id(session_id: str, thread_id: str, state: State) -> None:
def _save_thread_id(session_id: str, thread_id: str, state: TurnState) -> None:
threads_file = workspace_from_state(state) / THREADS_FILE
if threads_file.exists():
with threads_file.open() as f:
Expand All @@ -90,7 +90,7 @@ def _save_thread_id(session_id: str, thread_id: str, state: State) -> None:
json.dump(threads, f, indent=2)


def _runtime_agent_from_state(state: State) -> RuntimeAgent | None:
def _runtime_agent_from_state(state: TurnState) -> RuntimeAgent | None:
agent = state.get("_runtime_agent")
if agent is None:
return None
Expand All @@ -108,7 +108,7 @@ def _prompt_to_text(prompt: str | list[dict[str, Any]]) -> str:


async def _run_internal_command(
prompt: str, session_id: str, state: State
prompt: str, session_id: str, state: TurnState
) -> str | None:
if not prompt.strip().startswith(","):
return None
Expand Down Expand Up @@ -156,7 +156,7 @@ def _cursor_command(
return command


def _result_from_stdout(stdout_text: str, session_id: str, state: State) -> str:
def _result_from_stdout(stdout_text: str, session_id: str, state: TurnState) -> str:
try:
data = json.loads(stdout_text)
except json.JSONDecodeError:
Expand All @@ -176,7 +176,7 @@ def _result_from_stdout(stdout_text: str, session_id: str, state: State) -> str:

@hookimpl
async def run_model(
prompt: str | list[dict[str, Any]], session_id: str, state: State
prompt: str | list[dict[str, Any]], session_id: str, state: TurnState
) -> str:
prompt_text = _prompt_to_text(prompt)
internal_command_result = await _run_internal_command(
Expand Down
2 changes: 1 addition & 1 deletion packages/bub-cursor/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pathlib import Path

import pytest
from bub.runtime import AsyncStreamEvents, StreamEvent
from bub.streaming import AsyncStreamEvents, StreamEvent
from typer.testing import CliRunner

from bub.builtin.auth import app as auth_app
Expand Down
Loading