update
This commit is contained in:
16
Makefile
16
Makefile
@@ -62,13 +62,19 @@ ollama:
|
||||
ollama pull qwen3.5:9b
|
||||
@echo "Ollama bereit — Provider 'Lokal' ist aktiv (Modelle anpassen: backend/config.py + dev-ops/opencode.json)."
|
||||
|
||||
# Remote wird für die DB-Kopie gestoppt (sonst reißt der Snapshot mitten im Schreibvorgang)
|
||||
# und danach wieder gestartet.
|
||||
sync: stop
|
||||
@mkdir -p storage/themen
|
||||
@mkdir -p storage/themen storage/topics uni
|
||||
@rm -f storage/creator.db-shm storage/creator.db-wal
|
||||
ssh root@178.104.67.87 'cd /var/www/creator && docker compose down'
|
||||
rsync -avz --progress root@178.104.67.87:/var/www/creator/storage/creator.db storage/
|
||||
rsync -avz --progress root@178.104.67.87:/var/www/creator/storage/creator.db-wal storage/
|
||||
-rsync -avz --progress root@178.104.67.87:/var/www/creator/storage/creator.db-wal storage/
|
||||
rsync -avz --progress --delete root@178.104.67.87:/var/www/creator/storage/themen/ storage/themen/
|
||||
@echo "Sync abgeschlossen."
|
||||
rsync -avz --progress --delete root@178.104.67.87:/var/www/creator/storage/topics/ storage/topics/
|
||||
rsync -avz --progress --delete root@178.104.67.87:/var/www/creator/uni/ uni/
|
||||
ssh root@178.104.67.87 'cd /var/www/creator && docker compose up -d'
|
||||
@echo "Sync abgeschlossen — Remote läuft wieder."
|
||||
|
||||
# Projekte vom Server holen: `make sync-projects` (auch: `make sync projects`).
|
||||
# Mit --delete — exakter Spiegel: lokale Projekte ohne Server-Pendant werden gelöscht.
|
||||
@@ -84,13 +90,15 @@ sync-all: sync sync-projects
|
||||
# Stoppt den Remote-Server (DB-Sicherheit), pusht DB + Themen + Projekte, startet ihn neu.
|
||||
# --delete = exakter Spiegel: Remote-Dateien ohne lokales Pendant werden gelöscht.
|
||||
sync-all-reverse: stop
|
||||
@echo "ACHTUNG: überschreibt den Remote-Stand (DB, themen/, projects/) mit dem lokalen."
|
||||
@echo "ACHTUNG: überschreibt den Remote-Stand (DB, themen/, topics/, uni/, projects/) mit dem lokalen."
|
||||
@[ -f storage/creator.db ] || { echo "Keine lokale DB — abgebrochen."; exit 1; }
|
||||
ssh root@178.104.67.87 'cd /var/www/creator && docker compose down'
|
||||
ssh root@178.104.67.87 'rm -f /var/www/creator/storage/creator.db-shm /var/www/creator/storage/creator.db-wal'
|
||||
rsync -avz --progress storage/creator.db root@178.104.67.87:/var/www/creator/storage/
|
||||
@[ -f storage/creator.db-wal ] && rsync -avz --progress storage/creator.db-wal root@178.104.67.87:/var/www/creator/storage/ || true
|
||||
rsync -avz --progress --delete storage/themen/ root@178.104.67.87:/var/www/creator/storage/themen/
|
||||
rsync -avz --progress --delete storage/topics/ root@178.104.67.87:/var/www/creator/storage/topics/
|
||||
rsync -avz --progress --delete uni/ root@178.104.67.87:/var/www/creator/uni/
|
||||
rsync -avz --progress --delete projects/ root@178.104.67.87:/var/www/creator/projects/
|
||||
ssh root@178.104.67.87 'cd /var/www/creator && docker compose up -d --build'
|
||||
@echo "Reverse-Sync abgeschlossen — Remote läuft wieder."
|
||||
|
||||
@@ -15,6 +15,7 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.request
|
||||
@@ -289,6 +290,9 @@ async def run_agent(
|
||||
meta = {"provider": provider, "model": model, "role": role, "rc": rc}
|
||||
if err_tail:
|
||||
meta["stderr"] = err_tail
|
||||
if PROVIDERS[provider]["cli"] == "opencode": # token accounting per agent
|
||||
if (tok := await asyncio.to_thread(_session_tokens, agent_key)):
|
||||
meta["tokens"] = tok
|
||||
await on_event(topic=scope, kind="agent", key=agent_key, label=label,
|
||||
status=status, dur_ms=int((time.monotonic() - start) * 1000),
|
||||
wait_ms=wait_ms, meta=meta)
|
||||
@@ -377,6 +381,31 @@ async def _run_claude_cli(agent_key: str, prompt: str, timeout: int, model: str,
|
||||
return await _communicate(agent_key, cmd, prompt.encode("utf-8"), timeout, label=label)
|
||||
|
||||
|
||||
_OPENCODE_DB = Path.home() / ".local" / "share" / "opencode" / "opencode.db"
|
||||
|
||||
|
||||
def _session_tokens(agent_key: str) -> dict | None:
|
||||
"""Token counters of the newest OpenCode session titled `agent_key` (set via run --title).
|
||||
Best-effort read-only lookup — None when DB/row is missing; never fails the agent.
|
||||
Timeouts count too: their sessions consumed tokens, that waste should be visible."""
|
||||
try:
|
||||
con = sqlite3.connect(f"file:{_OPENCODE_DB}?mode=ro", uri=True, timeout=1)
|
||||
try:
|
||||
row = con.execute(
|
||||
"SELECT tokens_input, tokens_output, tokens_reasoning,"
|
||||
" tokens_cache_read, tokens_cache_write"
|
||||
" FROM session WHERE title=? ORDER BY time_created DESC LIMIT 1",
|
||||
(agent_key,)).fetchone()
|
||||
finally:
|
||||
con.close()
|
||||
except Exception:
|
||||
return None
|
||||
if row is None:
|
||||
return None
|
||||
keys = ("input", "output", "reasoning", "cache_read", "cache_write")
|
||||
return {k: int(v or 0) for k, v in zip(keys, row)}
|
||||
|
||||
|
||||
async def _run_opencode(agent_key: str, prompt: str, timeout: int, provider: str, model: str, capabilities: str, on_line=None, label: str = "") -> tuple[int, str, str]:
|
||||
cfg = PROVIDERS[provider]
|
||||
# Prompt via temp file instead of argv (ARG_MAX protection for large project prompts)
|
||||
@@ -391,6 +420,7 @@ async def _run_opencode(agent_key: str, prompt: str, timeout: int, provider: str
|
||||
"-m", model,
|
||||
"--agent", _OPENCODE_AGENTS.get(capabilities, "text"),
|
||||
"--dangerously-skip-permissions",
|
||||
"--title", agent_key, # token accounting: joins the OpenCode session to our event
|
||||
"-f", str(prompt_path),
|
||||
]
|
||||
if on_line is not None:
|
||||
|
||||
@@ -226,3 +226,60 @@ async def test_remove_guide_format_clears_everything(testdb, monkeypatch):
|
||||
assert res["removed"] == 3
|
||||
assert await db.list_guides() == [] or all(g["topic"] != TOPIC for g in await db.list_guides())
|
||||
assert await db.list_guide_cards(TOPIC, "Guide") == []
|
||||
|
||||
|
||||
# ── Token-Logging pro Agent (OpenCode-Session → Event-Meta) ──────────────────────────
|
||||
|
||||
def test_session_tokens_reads_newest(tmp_path, monkeypatch):
|
||||
"""--title = Agent-Key: neueste Session gewinnt (Retry); fehlende DB/Zeile → None."""
|
||||
import sqlite3
|
||||
dbf = tmp_path / "oc.db"
|
||||
con = sqlite3.connect(dbf)
|
||||
con.execute("CREATE TABLE session (title TEXT, time_created INT, tokens_input INT,"
|
||||
" tokens_output INT, tokens_reasoning INT, tokens_cache_read INT, tokens_cache_write INT)")
|
||||
con.execute("INSERT INTO session VALUES ('k', 1, 1, 1, 0, 10, 0)")
|
||||
con.execute("INSERT INTO session VALUES ('k', 2, 7, 3, 0, 99, 5)")
|
||||
con.commit()
|
||||
con.close()
|
||||
monkeypatch.setattr(agents, "_OPENCODE_DB", dbf)
|
||||
assert agents._session_tokens("k") == {"input": 7, "output": 3, "reasoning": 0,
|
||||
"cache_read": 99, "cache_write": 5}
|
||||
assert agents._session_tokens("fehlt") is None
|
||||
monkeypatch.setattr(agents, "_OPENCODE_DB", tmp_path / "nope.db")
|
||||
assert agents._session_tokens("k") is None
|
||||
|
||||
|
||||
async def test_opencode_cmd_sets_title(monkeypatch):
|
||||
"""Der Agent-Key wird Session-Titel — der Join-Schlüssel fürs Token-Logging."""
|
||||
seen = {}
|
||||
|
||||
async def fake_comm(agent_key, cmd, stdin, timeout, stagger=False, on_line=None, label="", env=None):
|
||||
seen["cmd"] = cmd
|
||||
return 0, "", ""
|
||||
|
||||
monkeypatch.setattr(agents, "_communicate", fake_comm)
|
||||
await agents._run_opencode("blocks-t-z", "p", 5, "minimax", "m", "none")
|
||||
i = seen["cmd"].index("--title")
|
||||
assert seen["cmd"][i + 1] == "blocks-t-z"
|
||||
|
||||
|
||||
async def test_run_agent_logs_opencode_tokens(testdb, monkeypatch):
|
||||
"""OpenCode-Lauf: Token-Zähler der Session landen im Event-Meta."""
|
||||
recorded = []
|
||||
|
||||
async def sink(**kw):
|
||||
recorded.append(kw)
|
||||
|
||||
async def fake_oc(agent_key, prompt, timeout, provider, model, capabilities, on_line=None, label=""):
|
||||
return 0, "out", ""
|
||||
|
||||
monkeypatch.setattr(agents, "on_event", sink)
|
||||
monkeypatch.setattr(agents, "_run_opencode", fake_oc)
|
||||
monkeypatch.setattr(agents.shutil, "which", lambda c: "/bin/true")
|
||||
monkeypatch.setattr(agents, "resolve_role", lambda p, r: ("minimax", "test-model"))
|
||||
monkeypatch.setattr(agents, "_session_tokens",
|
||||
lambda k: {"input": 5, "output": 2, "reasoning": 0,
|
||||
"cache_read": 100, "cache_write": 0})
|
||||
rc, *_ = await agents.run_agent("blocks-t-tok", "p", 5, provider="minimax", scope=TOPIC)
|
||||
assert rc == 0
|
||||
assert recorded and recorded[0]["meta"]["tokens"]["cache_read"] == 100
|
||||
|
||||
Reference in New Issue
Block a user