chore: import upstream snapshot with attribution
This commit is contained in:
+154
@@ -0,0 +1,154 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
from typing import Any, Iterable
|
||||
from uuid import uuid4
|
||||
|
||||
ORIGINAL_STDOUT = sys.stdout
|
||||
|
||||
|
||||
def event_stdout():
|
||||
if sys.stdout.__class__.__name__ == "_DiscardStream":
|
||||
return ORIGINAL_STDOUT
|
||||
return sys.stdout
|
||||
|
||||
|
||||
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Run the ViMax agent loop.")
|
||||
parser.add_argument("--session", default="", help="Existing session id to activate before the run starts.")
|
||||
parser.add_argument("--new-session", action="store_true", help="Create and activate a new empty session before the run starts.")
|
||||
parser.add_argument("--jsonl", action="store_true", help="Print one JSON event per line.")
|
||||
parser.add_argument("--once", default="", help="Run a single prompt and exit. If omitted and stdin is not a TTY, stdin is consumed as one prompt.")
|
||||
parser.add_argument("--stdin-repl", action="store_true", help=argparse.SUPPRESS)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def load_runtime():
|
||||
from agent_runtime import build_runtime
|
||||
|
||||
return build_runtime(".")
|
||||
|
||||
|
||||
def load_session_index():
|
||||
from agent_runtime.session_index import SessionIndex
|
||||
|
||||
return SessionIndex(".")
|
||||
|
||||
|
||||
def print_event(event: dict[str, Any], *, jsonl: bool) -> None:
|
||||
out = event_stdout()
|
||||
if jsonl:
|
||||
print(json.dumps(event, ensure_ascii=False, default=str), file=out, flush=True)
|
||||
return
|
||||
event_type = event.get("type")
|
||||
if event_type == "turn":
|
||||
print(f"· turn: {event.get('turn_id', '')}", file=out, flush=True)
|
||||
elif event_type == "token":
|
||||
print(event.get("delta", ""), end="", file=out, flush=True)
|
||||
elif event_type == "tool_start":
|
||||
tool = event.get("tool", {})
|
||||
print(f"\n· tool: {tool.get('name')} started", file=out, flush=True)
|
||||
elif event_type == "tool_progress":
|
||||
progress = event.get("progress", {})
|
||||
tool = event.get("tool", {})
|
||||
print(f"· tool: {tool.get('name')} {progress.get('stage', 'running')}: {progress.get('message', '')}", file=out, flush=True)
|
||||
elif event_type == "tool_result":
|
||||
result = event["tool_result"]
|
||||
status = "done" if result.get("ok") else "error"
|
||||
print(f"· tool: {result.get('name')} {status}", file=out, flush=True)
|
||||
elif event_type == "terminal":
|
||||
stream = event.get("stream", "stdout")
|
||||
print(f"· terminal[{stream}]: {event.get('line', '')}", file=out, flush=True)
|
||||
elif event_type == "status":
|
||||
print(f"· status: {event.get('phase')}: {event.get('message', '')}", file=out, flush=True)
|
||||
elif event_type == "session":
|
||||
session = (event.get("session") or {}).get("session") or {}
|
||||
if session:
|
||||
print(f"· session: {session.get('session_id')} {session.get('stage', '')}", file=out, flush=True)
|
||||
elif event_type == "done":
|
||||
print("", file=out, flush=True)
|
||||
elif event_type == "error":
|
||||
print(f"\nerror: {event.get('message', '')}", file=out, flush=True)
|
||||
|
||||
|
||||
def prompt_inputs(args: argparse.Namespace) -> Iterable[str]:
|
||||
if args.once:
|
||||
yield args.once
|
||||
return
|
||||
if args.stdin_repl:
|
||||
for line in sys.stdin:
|
||||
user_input = line.strip()
|
||||
if user_input:
|
||||
yield user_input
|
||||
return
|
||||
if not sys.stdin.isatty():
|
||||
payload = sys.stdin.read().strip()
|
||||
if payload:
|
||||
yield payload
|
||||
return
|
||||
while True:
|
||||
try:
|
||||
user_input = input("› " if not args.jsonl else "")
|
||||
except EOFError:
|
||||
break
|
||||
if user_input.strip():
|
||||
yield user_input.strip()
|
||||
|
||||
|
||||
async def amain(argv: list[str] | None = None) -> int:
|
||||
args = parse_args(argv)
|
||||
if args.session and args.new_session:
|
||||
print("error: --session and --new-session cannot be used together", file=sys.stderr)
|
||||
return 2
|
||||
if args.session or args.new_session:
|
||||
try:
|
||||
session_index = load_session_index()
|
||||
if args.new_session:
|
||||
session_index.create()
|
||||
else:
|
||||
session_index.set_active(args.session)
|
||||
except KeyError:
|
||||
print(f"error: unknown session id: {args.session}", file=sys.stderr)
|
||||
return 2
|
||||
except ValueError as exc:
|
||||
print(f"error: invalid session id: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
runtime = load_runtime()
|
||||
interactive = sys.stdin.isatty() and not args.once
|
||||
if interactive and not args.jsonl:
|
||||
print("ViMax agent ready. Ctrl+C to exit.")
|
||||
for user_input in prompt_inputs(args):
|
||||
if user_input.strip() == "/compact":
|
||||
turn_id = f"turn-{uuid4().hex[:12]}"
|
||||
print_event({"type": "turn", "turn_id": turn_id, "turn": {"id": turn_id}}, jsonl=args.jsonl)
|
||||
print_event({"type": "status", "turn_id": turn_id, "phase": "compact", "message": "Compacting context"}, jsonl=args.jsonl)
|
||||
message = await runtime.compact_history(reason="manual")
|
||||
print_event({"type": "token", "turn_id": turn_id, "delta": message}, jsonl=args.jsonl)
|
||||
print_event({"type": "done", "turn_id": turn_id, "assistant": message, "tool_results": []}, jsonl=args.jsonl)
|
||||
print_event({"type": "session", "turn_id": turn_id, "session": runtime.session_index.snapshot()}, jsonl=args.jsonl)
|
||||
continue
|
||||
try:
|
||||
async for event in runtime.stream_events(user_input):
|
||||
print_event(event, jsonl=args.jsonl)
|
||||
except Exception as exc:
|
||||
# Keep the REPL alive: one failed turn must not kill the process
|
||||
# (and with it the TUI driving us over stdio).
|
||||
turn_id = f"turn-{uuid4().hex[:12]}"
|
||||
print_event({"type": "error", "turn_id": turn_id, "message": f"turn failed: {exc}"}, jsonl=args.jsonl)
|
||||
print_event({"type": "done", "turn_id": turn_id, "assistant": "", "tool_results": []}, jsonl=args.jsonl)
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
raise SystemExit(asyncio.run(amain()))
|
||||
except KeyboardInterrupt:
|
||||
print("", file=sys.stderr)
|
||||
raise SystemExit(130)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user