93 lines
3.9 KiB
Python
93 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Set a goal and observe automatic rounds through the final model reply."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import math
|
|
import os
|
|
import time
|
|
from pathlib import Path
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from deepseek_harness import RunCommandResult, DeepSeekHarness, Notification
|
|
from deepseek_harness.api import final_response
|
|
|
|
|
|
class WaitResult(BaseModel):
|
|
"""The empty session/wait result."""
|
|
|
|
|
|
def display(notification: Notification) -> dict | None:
|
|
"""Print durable assistant output and return the observed root Session event."""
|
|
if notification.method != "session.event":
|
|
return None
|
|
event = notification.payload["event"]
|
|
if event["type"] == "assistant/message":
|
|
text = final_response([event])
|
|
if text:
|
|
print(text, flush=True)
|
|
elif event["type"] == "goal/change":
|
|
print(f"Goal: {event['data']['operation']}", flush=True)
|
|
elif event["type"] == "turn/end" and event["data"]["reason"]["kind"] != "completed":
|
|
raise RuntimeError(f"Goal activity ended: {event['data']['reason']}")
|
|
return event
|
|
|
|
|
|
def main() -> None:
|
|
"""Observe goal state before waiting for the final activity interval."""
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--dsh-home", required=True, type=Path)
|
|
parser.add_argument("--workspace", type=Path, default=Path.cwd())
|
|
parser.add_argument("--model", default=os.environ.get("DSH_MODEL", "deepseek-v4-flash"))
|
|
parser.add_argument("--timeout-seconds", type=float, default=300)
|
|
parser.add_argument("objective", help="Objective for the native /goal command")
|
|
args = parser.parse_args()
|
|
if not args.objective.strip() or not math.isfinite(args.timeout_seconds) or args.timeout_seconds <= 0:
|
|
parser.error("objective must be non-blank and timeout-seconds must be finite and positive")
|
|
with DeepSeekHarness(
|
|
dsh_home=str(args.dsh_home.resolve()), cwd=str(args.workspace.resolve()),
|
|
profile="sdk", model=args.model, env={"DSH_PERMISSION_MODE": "workspace-write"},
|
|
request_timeout_seconds=args.timeout_seconds,
|
|
) as harness:
|
|
session = harness.start_session()
|
|
with harness.client.subscribe_notifications(lambda notice: notice.payload.get("sessionId") == session.id) as notifications:
|
|
deadline = time.monotonic() + args.timeout_seconds
|
|
|
|
def remaining_seconds() -> float:
|
|
remaining = deadline - time.monotonic()
|
|
if remaining <= 0:
|
|
raise TimeoutError("Goal observation timed out")
|
|
return remaining
|
|
|
|
def observe(notification: Notification) -> dict | None:
|
|
remaining_seconds()
|
|
event = display(notification)
|
|
remaining_seconds()
|
|
return event
|
|
|
|
execution = session.run(f"/goal {args.objective}")
|
|
if not isinstance(execution, RunCommandResult):
|
|
raise RuntimeError("The SDK profile did not execute the requested command")
|
|
print(execution.result.text, flush=True)
|
|
if execution.result.kind == "error":
|
|
raise RuntimeError(execution.result.text)
|
|
while True:
|
|
event = observe(notifications.next(timeout_seconds=remaining_seconds()))
|
|
if event is not None and event["type"] == "goal/change" and event["data"]["operation"] in {
|
|
"complete", "block", "pause", "clear",
|
|
}:
|
|
break
|
|
# A terminal goal event can precede the model's closing reply.
|
|
harness.client.request(
|
|
"session/wait", {"sessionId": session.id}, response_model=WaitResult,
|
|
timeout_seconds=remaining_seconds(),
|
|
on_notification=observe, notification_subscription=notifications,
|
|
)
|
|
remaining_seconds()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|