@aerovato/operator-helper 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import importlib.metadata
6
7
  import os
7
8
  import subprocess
8
9
  import threading
@@ -10,7 +11,7 @@ from collections.abc import AsyncGenerator
10
11
  from contextlib import asynccontextmanager, suppress
11
12
  from typing import Any
12
13
 
13
- from code_puppy.callbacks import CustomCommandResult, register_callback
14
+ from code_puppy.callbacks import register_callback
14
15
  from code_puppy.config import get_current_session_name
15
16
  from code_puppy.messaging import emit_warning
16
17
  from code_puppy.tools.subagent_context import get_conversation_root_id
@@ -27,6 +28,20 @@ from pydantic_ai.settings import ModelSettings
27
28
 
28
29
  _MARKER_KEY = "operator_memory"
29
30
  _MARKER_VALUE = "synthetic-preamble-v1"
31
+ _MODEL_MESSAGE_TRANSFORM_VERSION = (0, 0, 753)
32
+
33
+ try:
34
+ from code_puppy.callbacks import CustomCommandResult
35
+ except ImportError:
36
+ try:
37
+ # Code Puppy < 0.0.698 exposes the same contract in the
38
+ # customizable-commands plugin.
39
+ from code_puppy.plugins.customizable_commands.register_callbacks import (
40
+ MarkdownCommandResult as CustomCommandResult,
41
+ )
42
+ except ImportError:
43
+ CustomCommandResult = None
44
+
30
45
  _RENDER_TIMEOUT_SECONDS = 30
31
46
  _COMMAND_TIMEOUT_SECONDS = 60
32
47
  _render_tasks: dict[str, asyncio.Task[str]] = {}
@@ -107,6 +122,12 @@ async def _inject_preamble(
107
122
  return transformed
108
123
 
109
124
 
125
+ async def _transform_model_messages(
126
+ _agent_name: str | None, messages: list[ModelMessage]
127
+ ) -> None:
128
+ messages[:] = await _inject_preamble(messages, _conversation_key())
129
+
130
+
110
131
  def _is_operator_request(message: ModelMessage) -> bool:
111
132
  return (
112
133
  isinstance(message, ModelRequest)
@@ -187,6 +208,31 @@ def _conversation_key() -> str:
187
208
  return get_conversation_root_id() or get_current_session_name()
188
209
 
189
210
 
211
+ def _installed_code_puppy_version() -> str | None:
212
+ try:
213
+ return importlib.metadata.version("code-puppy")
214
+ except Exception:
215
+ return None
216
+
217
+
218
+ def _supports_model_message_transform(version: str | None) -> bool:
219
+ if version is None:
220
+ return False
221
+ release = version.partition("+")[0].partition("-")[0]
222
+ try:
223
+ parts = tuple(int(part) for part in release.split("."))
224
+ except ValueError:
225
+ return False
226
+ return parts >= _MODEL_MESSAGE_TRANSFORM_VERSION
227
+
228
+
229
+ def _register_preamble_injection(version: str | None) -> None:
230
+ if _supports_model_message_transform(version):
231
+ register_callback("transform_model_messages", _transform_model_messages)
232
+ else:
233
+ register_callback("agent_run_context", _agent_run_context)
234
+
235
+
190
236
  @asynccontextmanager
191
237
  async def _agent_run_context(
192
238
  _agent: Any, pydantic_agent: Any, _group_id: str, _mcp_servers: Any
@@ -195,10 +241,36 @@ async def _agent_run_context(
195
241
  if model is None:
196
242
  yield
197
243
  return
244
+ if _is_dbos_agent(pydantic_agent):
245
+ _warn_dbos_incompatible()
246
+ yield
247
+ return
198
248
  with pydantic_agent.override(model=OperatorModel(model, _conversation_key())):
199
249
  yield
200
250
 
201
251
 
252
+ def _is_dbos_agent(pydantic_agent: Any) -> bool:
253
+ # DBOSAgent installs its own captured DBOSModel through a nested
254
+ # override inside run(), which supersedes any override applied here.
255
+ # Duck-type: avoid importing the optional durable_exec module.
256
+ return hasattr(pydantic_agent, "_dbos_overrides")
257
+
258
+
259
+ _DBOS_WARNED = False
260
+
261
+
262
+ def _warn_dbos_incompatible() -> None:
263
+ global _DBOS_WARNED
264
+ if _DBOS_WARNED:
265
+ return
266
+ _DBOS_WARNED = True
267
+ emit_warning(
268
+ "Operator Memory preamble injection is disabled: DBOS durable execution "
269
+ "supersedes the model override it relies on. Disable DBOS to restore "
270
+ "Operator Memory: /set enable_dbos false, then restart Code Puppy."
271
+ )
272
+
273
+
202
274
  async def _session_end() -> None:
203
275
  tasks = tuple(_render_tasks.values())
204
276
  _render_tasks.clear()
@@ -361,10 +433,17 @@ async def _agent_run_end(*_args: Any) -> None:
361
433
  _paint_status_indicator("")
362
434
 
363
435
 
364
- register_callback("agent_run_context", _agent_run_context)
436
+ _register_preamble_injection(_installed_code_puppy_version())
365
437
  register_callback("session_end", _session_end)
366
- register_callback("custom_command", _custom_command)
367
- register_callback("custom_command_help", _custom_command_help)
438
+ if CustomCommandResult is not None:
439
+ register_callback("custom_command", _custom_command)
440
+ register_callback("custom_command_help", _custom_command_help)
441
+ else:
442
+ emit_warning(
443
+ "Operator commands are disabled: this Code Puppy version is too old to "
444
+ "process command output as user input. Preamble injection still works. "
445
+ "Upgrade Code Puppy to enable the /operator:* commands."
446
+ )
368
447
  register_callback("startup", _show_status_indicator)
369
448
  register_callback("agent_run_start", _agent_run_start)
370
449
  register_callback("agent_run_end", _agent_run_end)