mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 04:49:26 +00:00
Imported from andrewyng/aisuite@1b4bbf303e (contents of its platform/ directory, hoisted to the repo root). Development history prior to this commit lives in that repository. Co-authored-by: Devika <devikaverma11@gmail.com>
153 lines
5.1 KiB
Python
153 lines
5.1 KiB
Python
"""FB-005 (server half) — canonical messages carry a `ts` sidecar.
|
|
|
|
`ts` (unix seconds) is stamped when a message is appended to the canonical history,
|
|
persisted, and served raw by `GET /v1/sessions/{id}/messages` — but, like `source` and
|
|
`_display`, it must NEVER reach a provider payload."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import time
|
|
|
|
import aisuite as ai
|
|
from fastapi.testclient import TestClient
|
|
|
|
from coworker.engine import TurnEngine
|
|
from coworker.permissions import PermissionEngine
|
|
from coworker.providers import (
|
|
AssistantTurn,
|
|
ModelCapabilities,
|
|
ProviderClient,
|
|
ToolCall,
|
|
)
|
|
from coworker.server import SessionManager, create_app
|
|
from coworker.tools import ToolRegistry
|
|
|
|
|
|
class CapturingProvider(ProviderClient):
|
|
"""Queued turns + a record of every message list the provider was handed."""
|
|
|
|
def __init__(self, turns):
|
|
self._turns = list(turns)
|
|
self.calls: list[list[dict]] = []
|
|
|
|
def complete(self, *, model, messages, tools=None, **settings):
|
|
self.calls.append([dict(m) for m in messages])
|
|
return self._turns.pop(0)
|
|
|
|
def capabilities(self, model):
|
|
return ModelCapabilities()
|
|
|
|
|
|
def _text(text):
|
|
return AssistantTurn(text=text, finish_reason="stop")
|
|
|
|
|
|
def _tool(name, args, call_id="call_1"):
|
|
return AssistantTurn(
|
|
tool_calls=[ToolCall(id=call_id, name=name, arguments=args)],
|
|
finish_reason="tool_calls",
|
|
)
|
|
|
|
|
|
def _engine(tmp_path, turns):
|
|
provider = CapturingProvider(turns)
|
|
registry = ToolRegistry()
|
|
registry.register_all(ai.toolkits.files(root=str(tmp_path), allow_write=True))
|
|
engine = TurnEngine(
|
|
provider=provider,
|
|
registry=registry,
|
|
permissions=PermissionEngine(workspace_root=tmp_path),
|
|
model="gpt-5.5",
|
|
)
|
|
return engine, provider
|
|
|
|
|
|
def _run(engine, text):
|
|
async def _go():
|
|
return [ev async for ev in engine.run(text)]
|
|
|
|
return asyncio.run(_go())
|
|
|
|
|
|
def test_appended_messages_carry_ts(tmp_path):
|
|
(tmp_path / "a.txt").write_text("hello", encoding="utf-8")
|
|
engine, _ = _engine(
|
|
tmp_path,
|
|
[_tool("read_file", {"path": "a.txt"}), _text("it says hello")],
|
|
)
|
|
before = time.time()
|
|
_run(engine, "read a.txt")
|
|
after = time.time()
|
|
assert [m["role"] for m in engine.messages] == [
|
|
"user",
|
|
"assistant",
|
|
"tool",
|
|
"assistant",
|
|
]
|
|
for m in engine.messages: # user, assistant, AND tool messages are stamped
|
|
assert isinstance(m["ts"], float)
|
|
assert before <= m["ts"] <= after
|
|
|
|
|
|
def test_provider_payload_never_carries_ts(tmp_path):
|
|
(tmp_path / "a.txt").write_text("hello", encoding="utf-8")
|
|
engine, provider = _engine(
|
|
tmp_path,
|
|
[_tool("read_file", {"path": "a.txt"}), _text("done")],
|
|
)
|
|
_run(engine, "read a.txt")
|
|
assert provider.calls, "provider should have been invoked"
|
|
assert all("ts" not in m for call in provider.calls for m in call)
|
|
# the strip is a copy — the canonical history keeps its timestamps
|
|
assert all("ts" in m for m in engine.messages)
|
|
|
|
|
|
def test_outbound_strip_is_unconditional(tmp_path):
|
|
# Direct check on the single provider feed, no-context early-return path included.
|
|
engine, _ = _engine(tmp_path, [])
|
|
engine.messages.append({"role": "user", "content": "hi", "ts": 1700000000.0})
|
|
out = engine._outbound_messages()
|
|
assert all("ts" not in m for m in out)
|
|
assert engine.messages[-1]["ts"] == 1700000000.0 # original untouched
|
|
|
|
|
|
def _no_ts_keys(value) -> bool:
|
|
if isinstance(value, dict):
|
|
return all(k != "ts" and _no_ts_keys(v) for k, v in value.items())
|
|
if isinstance(value, list):
|
|
return all(_no_ts_keys(v) for v in value)
|
|
return True
|
|
|
|
|
|
def test_provider_adapters_drop_ts():
|
|
"""Defense in depth: the native Anthropic/Gemini payload builders rebuild messages
|
|
from role/content, so a `ts` that somehow slipped past the engine strip still never
|
|
reaches the wire."""
|
|
from coworker.providers.anthropic_provider import convert_messages as to_anthropic
|
|
from coworker.providers.gemini_provider import convert_messages as to_gemini
|
|
|
|
history = [
|
|
{"role": "system", "content": "be brief"},
|
|
{"role": "user", "content": "hi", "ts": 1700000000.0},
|
|
{"role": "assistant", "content": "hello", "ts": 1700000001.0},
|
|
]
|
|
for convert in (to_anthropic, to_gemini):
|
|
_system, payload = convert(history)
|
|
assert _no_ts_keys(payload)
|
|
|
|
|
|
def test_messages_endpoint_returns_ts(tmp_path):
|
|
manager = SessionManager(
|
|
workspace=tmp_path, provider=CapturingProvider([_text("hi there")])
|
|
)
|
|
client = TestClient(create_app(manager))
|
|
with client.websocket_connect("/ws/session/ts1") as ws:
|
|
assert ws.receive_json()["type"] == "ready"
|
|
ws.send_json({"type": "user_message", "text": "hello world"})
|
|
while ws.receive_json()["type"] != "turn_done":
|
|
pass
|
|
msgs = client.get("/v1/sessions/ts1/messages").json()["messages"]
|
|
stamped = [m for m in msgs if m.get("role") in ("user", "assistant")]
|
|
assert stamped and all(isinstance(m.get("ts"), float) for m in stamped)
|