feat(backends,conversations,frontends): opus 5.5 progress updates reach telegram, current claude cli in the image

This commit is contained in:
hh committed 2026-09-22 18:02:01 +00:00
1 parent e58153628c
commit bbce425eeb
9 files changed
+140 -20

No files matched your search

+3
View File
@@ -24,6 +24,9 @@ class ClaudeOptions(BaseModel):
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
effort: str | None = None
thinking: Mapping[str, str] | None = None
"""Thinking config for the SDK; ``None`` keeps the CLI default."""
include_partial_messages: bool = True
"""Token-level deltas on the wire; off means one delta per finished block."""
+11
View File
@@ -726,6 +726,7 @@ class ClaudeSdkBackend:
return ClaudeAgentOptions(
model=agent.model or None,
effort=cast("Any", opt.effort),
thinking=cast("Any", dict(opt.thinking) if opt.thinking else None),
system_prompt=system_prompt,
setting_sources=[],
strict_mcp_config=True,
@@ -933,7 +934,17 @@ async def _disconnect(live: Session) -> None:
_log.exception("disconnect failed for session %s", live.session_id)
CLI_ENV = "BEAVER_CLAUDE_CLI"
"""Path to a ``claude`` CLI that wins over the one bundled in the wheel."""
def _claude_binary() -> str:
override = os.environ.get(CLI_ENV)
if override:
if not Path(override).is_file():
msg = f"{CLI_ENV} points at {override!r}, which is not a file"
raise FileNotFoundError(msg)
return override
bundled = Path(claude_agent_sdk.__file__).parent / "_bundled" / "claude"
if bundled.is_file():
return str(bundled)
+19 -8
View File
@@ -32,11 +32,11 @@ from beaver_gateway.frontends.accumulate import StreamAccumulator
from beaver_gateway.storage.models import Conversation, InjectQueueItem, RateLimit
if TYPE_CHECKING:
from collections.abc import AsyncIterator, Callable, Sequence
from collections.abc import AsyncIterator, Callable, Iterable, Sequence
from beaver_gateway.events.stream import MessageStreamEvent
__all__ = ["Turns", "content_of", "image_block"]
__all__ = ["Turns", "content_of", "image_block", "reply_text"]
_log = logging.getLogger(__name__)
@@ -164,12 +164,7 @@ class Turns(Seeds):
):
acc.feed(event)
message = acc.finalize(model=agent.model)
reply = "\n\n".join(
getattr(b, "text", "")
for b in message.content
if getattr(b, "type", "") == "text"
).strip()
return reply, capture
return reply_text(message.content), capture
async def before_turn(self, conv: Conversation) -> str | None: # noqa: ARG002
return None
@@ -489,6 +484,22 @@ class Turns(Seeds):
IMAGE_MAX_BYTES = 5 * 1024 * 1024
def reply_text(content: Iterable[Any]) -> str:
"""Text blocks plus non-empty thinking blocks, in block order.
What the model writes between tool calls rides in thinking blocks; the
hidden reasoning in them is empty and drops out on its own.
"""
parts: list[str] = []
for block in content:
kind = getattr(block, "type", "")
if kind == "text":
parts.append(getattr(block, "text", "") or "")
elif kind == "thinking":
parts.append(getattr(block, "thinking", "") or "")
return "\n\n".join(p for p in parts if p).strip()
def content_of(
text: str, attachments: Sequence[dict[str, Any]] | None
) -> str | list[dict[str, Any]]:
@@ -983,7 +983,12 @@ class TelegramFrontend(Frontend):
draft.set_status(self.texts.writing)
draft.append(str(delta.get("text") or ""))
elif delta.get("type") == "thinking_delta":
draft.set_status(self.texts.thinking)
progress = str(delta.get("thinking") or "")
if progress:
draft.set_status(self.texts.writing)
draft.append(progress)
else:
draft.set_status(self.texts.thinking)
elif kind == "content_block_start":
block = raw.get("content_block") or {}
if block.get("type") == "tool_use":