| import asyncio, inspect, re | |
| from typing import Optional | |
| import signals | |
| from config import CFG | |
| from vlog import log, exception | |
| SPOKEN_DISCIPLINE = """ | |
| Spoken discipline: | |
| Answer for the ear, not the screen. Use short conversational sentences. | |
| Do not use markdown, code blocks, bullets, numbered lists, tables, or long | |
| formatted passages when speaking. If code or structured content is necessary, | |
| explain it naturally and briefly. TTS performs punctuation, so make punctuation | |
| sound natural. After the first sentence, prefer two-sentence breaths instead | |
| of isolated one-sentence fragments. For tool use, say a short natural filler | |
| such as "On it, checking now." before a potentially long tool operation. | |
| """ | |
| class Brain: | |
| def __init__(self, deep=False): | |
| self.deep = deep | |
| self.client = None | |
| self.last_interrupted = False | |
| async def start(self): | |
| from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions | |
| model = CFG.claude_deep_model if self.deep else CFG.claude_model | |
| opts = ClaudeAgentOptions( | |
| model=model, | |
| cwd=str(CFG.cwd), | |
| system_prompt={"type": "preset", "preset": "claude_code", | |
| "append": SPOKEN_DISCIPLINE}, | |
| ) | |
| self.client = ClaudeSDKClient(options=opts) | |
| await self.client.connect() | |
| log("brain_started", requested_model=model, cwd=str(CFG.cwd)) | |
| async def warmup(self): | |
| # A harmless startup query pays the cache/warm-session cost behind a greeting. | |
| try: | |
| await self.client.query( | |
| "Warm up this voice session. Reply with exactly: Voice line ready." | |
| ) | |
| async for _ in self.client.receive_response(): | |
| pass | |
| log("brain_warmup_complete") | |
| except Exception as e: | |
| exception("brain_warmup_error", e) | |
| def _text_of(msg): | |
| if hasattr(msg, "text"): | |
| return msg.text or "" | |
| if isinstance(msg, dict): | |
| return msg.get("text", "") | |
| return "" | |
| async def ask(self, text, mouth): | |
| await self.client.query(text) | |
| buf = "" | |
| try: | |
| async for msg in self.client.receive_response(): | |
| # SDK message objects vary by release; content blocks expose text. | |
| chunks = [] | |
| content = getattr(msg, "content", None) | |
| if isinstance(content, list): | |
| for block in content: | |
| t = getattr(block, "text", None) | |
| if t: | |
| chunks.append(t) | |
| else: | |
| t = self._text_of(msg) | |
| if t: | |
| chunks.append(t) | |
| for t in chunks: | |
| buf += t | |
| # Sentence chunking; retain punctuation in spoken text. | |
| while True: | |
| m = re.search(r"(.+?[.!?](?:[\"')\]]*)\s+)", buf, re.S) | |
| if not m: | |
| break | |
| sentence = m.group(1).strip() | |
| buf = buf[m.end():] | |
| await mouth.enqueue(sentence) | |
| # Flush content block fragments so pre-tool filler is not silent. | |
| if content and not buf.strip(): | |
| pass | |
| if buf.strip(): | |
| await mouth.enqueue(buf.strip()) | |
| except asyncio.CancelledError: | |
| self.last_interrupted = True | |
| raise | |
| except Exception as e: | |
| exception("brain_turn_error", e) | |
| raise | |
| async def realign_after_interrupt(self): | |
| """ | |
| The Agent SDK has one shared stream. After an interrupted reader, stale | |
| messages can remain buffered. Interrupt the client, then drain until a | |
| ResultMessage boundary or timeout. If that cannot be established, rebuild. | |
| """ | |
| try: | |
| if hasattr(self.client, "interrupt"): | |
| result = self.client.interrupt() | |
| if inspect.isawaitable(result): | |
| await result | |
| deadline = asyncio.get_running_loop().time() + 3.0 | |
| async for msg in self.client.receive_response(): | |
| name = type(msg).__name__ | |
| if name == "ResultMessage": | |
| log("brain_realign_ok") | |
| return True | |
| if asyncio.get_running_loop().time() > deadline: | |
| break | |
| except Exception as e: | |
| exception("brain_realign_error", e) | |
| log("brain_realign_rebuild") | |
| try: | |
| if self.client and hasattr(self.client, "disconnect"): | |
| r = self.client.disconnect() | |
| if inspect.isawaitable(r): | |
| await r | |
| except Exception: | |
| pass | |
| self.client = None | |
| await self.start() | |
| return False | |
Xet Storage Details
- Size:
- 4.96 kB
- Xet hash:
- 0b6ecca2d0553a6b562e81ad3ee039e812bf530ef0d7170fdf5931b1dcac2a6a
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.