forked from Azure-Samples/computer-use
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
200 lines (171 loc) · 7.27 KB
/
Copy pathmain.py
File metadata and controls
200 lines (171 loc) · 7.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
"""
Computer-use agent built on the GitHub Copilot SDK.
This sample drives a real computer with natural-language instructions. The
GitHub Copilot SDK runs the agentic loop: the model captures a screenshot,
decides the next action, calls the matching computer tool, observes the
resulting screenshot, and repeats until the task is complete.
The computer-control capability is exposed to the model as a set of custom SDK
tools (see ``cua.build_computer_tools``).
Prerequisites:
* GitHub Copilot CLI installed and authenticated (``copilot auth login``)
* ``pip install -r requirements.txt``
"""
from __future__ import annotations
import argparse
import asyncio
import logging
import sys
import webbrowser
from pathlib import Path
from urllib.parse import urlencode
import cua
import local_computer
from copilot import CopilotClient
from copilot.session import PermissionHandler
from copilot.session_events import SessionEventType
def signal_done(message: str | None = None) -> None:
"""Open ``done.html`` in the default browser as a visible 'finished' signal.
Best-effort: silently does nothing if the page is missing or no browser can
be launched. Pass ``message`` to show a short summary on the page.
"""
page = Path(__file__).resolve().parent / "done.html"
if not page.exists():
return
url = page.as_uri()
if message:
url += "?" + urlencode({"msg": message})
try:
webbrowser.open(url)
except Exception:
pass
# Default model chosen empirically (see eval.py, 2026-06-11). Across the eval
# tasks all candidate models tied on success, so the differentiator was
# efficiency: gpt-5.5 used the fewest tool calls and lowest latency, and was far
# cheaper than Claude Opus 4.8 for no gain. Override with --model, or run
# --list-models to see what your Copilot account exposes.
DEFAULT_MODEL = "gpt-5.5"
SYSTEM_PROMPT = """You are an autonomous computer-use agent that controls a real \
computer by calling tools.
The screen you control is {width}x{height} pixels. Coordinate (0, 0) is the \
top-left corner. Every coordinate you pass to a tool MUST fall inside that range. \
The screen may span multiple physical monitors arranged side by side, so it can \
be much wider than a single display; scan the whole screenshot for the relevant \
window, which may appear on the left or right portion of the screen.
Workflow:
1. Call `screenshot` first to see the current state of the screen.
2. Choose the single best next action and call the matching tool (click, \
double_click, move, scroll, type, keypress, drag, or wait).
3. Each action tool returns a fresh screenshot. Inspect it to confirm the action \
worked before deciding the next step.
4. Repeat until the user's task is complete, then briefly summarize what you did.
Rules:
- Only click coordinates that are visible in the most recent screenshot.
- Prefer keyboard shortcuts (keypress) when they are more reliable than clicking.
- If the UI is loading or animating, call `wait` and then take another screenshot.
- Work autonomously; do not ask the user to confirm individual actions.
"""
async def main():
logging.basicConfig(level=logging.WARNING, format="%(message)s")
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
parser = argparse.ArgumentParser(description="Copilot SDK computer-use agent")
parser.add_argument(
"--instructions",
dest="instructions",
default="Open web browser and go to microsoft.com.",
help="Initial task to perform",
)
parser.add_argument(
"--model",
dest="model",
default=DEFAULT_MODEL,
help=f"Copilot model to use (default: {DEFAULT_MODEL})",
)
parser.add_argument(
"--reasoning-effort",
dest="reasoning_effort",
choices=["low", "medium", "high", "xhigh"],
default=None,
help="Reasoning effort for models that support it (omit to use the model default)",
)
parser.add_argument(
"--list-models",
dest="list_models",
action="store_true",
help="List the models available to your Copilot account and exit",
)
parser.add_argument(
"--timeout",
dest="timeout",
type=float,
default=1800.0,
help="Seconds to allow each task to run before timing out (default: 1800)",
)
parser.add_argument(
"--monitor",
dest="monitor",
default="all",
help="Which monitor to view and control: 'all' for the full virtual desktop "
"spanning every display (default), or a 1-based monitor index (1 = first).",
)
parser.add_argument(
"--no-done",
dest="done_signal",
action="store_false",
help="Do not open the done.html 'I'm done' page in the browser when finished",
)
args = parser.parse_args()
# Computer takes screenshots and performs mouse/keyboard actions.
# Scaler resizes the screen to a model-friendly resolution and translates
# the model's coordinates back to real screen coordinates.
computer = cua.Scaler(local_computer.LocalComputer(monitor=args.monitor))
tools = cua.build_computer_tools(computer)
width, height = computer.dimensions
async with CopilotClient() as client:
if args.list_models:
for model in await client.list_models():
logger.info(f"{getattr(model, 'id', model)}\t{getattr(model, 'name', '')}")
return
# Log each tool call (the model's actions) as it happens. Permissions are
# already auto-approved via on_permission_request, so this only logs.
async def on_pre_tool_use(payload, invocation):
logger.info(f"\n -> {payload.toolName} {payload.toolArgs}")
return None
session_kwargs = dict(
on_permission_request=PermissionHandler.approve_all,
model=args.model,
tools=tools,
streaming=True,
system_message={
"mode": "replace",
"content": SYSTEM_PROMPT.format(width=width, height=height),
},
hooks={"on_pre_tool_use": on_pre_tool_use},
)
if args.reasoning_effort:
session_kwargs["reasoning_effort"] = args.reasoning_effort
async with await client.create_session(**session_kwargs) as session:
def on_event(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
sys.stdout.write(event.data.delta_content or "")
sys.stdout.flush()
session.on(on_event)
user_input = args.instructions
while True:
if not user_input:
try:
user_input = input("\nUser (blank or 'exit' to quit): ").strip()
except EOFError:
break
if not user_input or user_input.lower() in {"exit", "quit"}:
break
logger.info(f"\nUser: {user_input}")
sys.stdout.write("\nAgent: ")
sys.stdout.flush()
await session.send_and_wait(user_input, timeout=args.timeout)
print()
user_input = ""
if args.done_signal:
signal_done("The computer-use agent has finished.")
if __name__ == "__main__":
asyncio.run(main())