Skip to content

Commit a147a1f

Browse files
feat: add Grok 4.5 computer use support (#102)
* feat: add Grok 4.5 computer use support * style: ruff format * Fix GrokComputerUseStepResponse xAI-style step field types Align created_at, completed_at, and error with CuaStepResponse so completed Grok task payloads parse without Pydantic validation errors. Reuse CUA step helper models for structured error, incomplete_details, and reasoning fields. Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 06f2e43 commit a147a1f

8 files changed

Lines changed: 361 additions & 0 deletions

File tree

hyperbrowser/client/managers/async_manager/agents/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .claude_computer_use import ClaudeComputerUseManager
44
from .hyper_agent import HyperAgentManager
55
from .gemini_computer_use import GeminiComputerUseManager
6+
from .grok_computer_use import GrokComputerUseManager
67

78

89
class Agents:
@@ -12,3 +13,4 @@ def __init__(self, client):
1213
self.claude_computer_use = ClaudeComputerUseManager(client)
1314
self.hyper_agent = HyperAgentManager(client)
1415
self.gemini_computer_use = GeminiComputerUseManager(client)
16+
self.grok_computer_use = GrokComputerUseManager(client)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import asyncio
2+
3+
from hyperbrowser.exceptions import HyperbrowserError
4+
5+
from .....models import (
6+
POLLING_ATTEMPTS,
7+
BasicResponse,
8+
GrokComputerUseTaskResponse,
9+
GrokComputerUseTaskStatusResponse,
10+
StartGrokComputerUseTaskParams,
11+
StartGrokComputerUseTaskResponse,
12+
)
13+
14+
15+
class GrokComputerUseManager:
16+
def __init__(self, client):
17+
self._client = client
18+
19+
async def start(
20+
self, params: StartGrokComputerUseTaskParams
21+
) -> StartGrokComputerUseTaskResponse:
22+
response = await self._client.transport.post(
23+
self._client._build_url("/task/grok-computer-use"),
24+
data=params.model_dump(exclude_none=True, by_alias=True),
25+
)
26+
return StartGrokComputerUseTaskResponse(**response.data)
27+
28+
async def get(self, job_id: str) -> GrokComputerUseTaskResponse:
29+
response = await self._client.transport.get(
30+
self._client._build_url(f"/task/grok-computer-use/{job_id}")
31+
)
32+
return GrokComputerUseTaskResponse(**response.data)
33+
34+
async def get_status(self, job_id: str) -> GrokComputerUseTaskStatusResponse:
35+
response = await self._client.transport.get(
36+
self._client._build_url(f"/task/grok-computer-use/{job_id}/status")
37+
)
38+
return GrokComputerUseTaskStatusResponse(**response.data)
39+
40+
async def stop(self, job_id: str) -> BasicResponse:
41+
response = await self._client.transport.put(
42+
self._client._build_url(f"/task/grok-computer-use/{job_id}/stop")
43+
)
44+
return BasicResponse(**response.data)
45+
46+
async def start_and_wait(
47+
self, params: StartGrokComputerUseTaskParams
48+
) -> GrokComputerUseTaskResponse:
49+
job_start_resp = await self.start(params)
50+
job_id = job_start_resp.job_id
51+
if not job_id:
52+
raise HyperbrowserError("Failed to start Grok Computer Use task job")
53+
54+
failures = 0
55+
while True:
56+
try:
57+
job_response = await self.get_status(job_id)
58+
if (
59+
job_response.status == "completed"
60+
or job_response.status == "failed"
61+
or job_response.status == "stopped"
62+
):
63+
return await self.get(job_id)
64+
failures = 0
65+
except Exception as e:
66+
failures += 1
67+
if failures >= POLLING_ATTEMPTS:
68+
raise HyperbrowserError(
69+
f"Failed to poll Grok Computer Use task job {job_id} after {POLLING_ATTEMPTS} attempts: {e}"
70+
)
71+
await asyncio.sleep(2)

hyperbrowser/client/managers/sync_manager/agents/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .claude_computer_use import ClaudeComputerUseManager
44
from .hyper_agent import HyperAgentManager
55
from .gemini_computer_use import GeminiComputerUseManager
6+
from .grok_computer_use import GrokComputerUseManager
67

78

89
class Agents:
@@ -12,3 +13,4 @@ def __init__(self, client):
1213
self.claude_computer_use = ClaudeComputerUseManager(client)
1314
self.hyper_agent = HyperAgentManager(client)
1415
self.gemini_computer_use = GeminiComputerUseManager(client)
16+
self.grok_computer_use = GrokComputerUseManager(client)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import time
2+
3+
from hyperbrowser.exceptions import HyperbrowserError
4+
5+
from .....models import (
6+
POLLING_ATTEMPTS,
7+
BasicResponse,
8+
GrokComputerUseTaskResponse,
9+
GrokComputerUseTaskStatusResponse,
10+
StartGrokComputerUseTaskParams,
11+
StartGrokComputerUseTaskResponse,
12+
)
13+
14+
15+
class GrokComputerUseManager:
16+
def __init__(self, client):
17+
self._client = client
18+
19+
def start(
20+
self, params: StartGrokComputerUseTaskParams
21+
) -> StartGrokComputerUseTaskResponse:
22+
response = self._client.transport.post(
23+
self._client._build_url("/task/grok-computer-use"),
24+
data=params.model_dump(exclude_none=True, by_alias=True),
25+
)
26+
return StartGrokComputerUseTaskResponse(**response.data)
27+
28+
def get(self, job_id: str) -> GrokComputerUseTaskResponse:
29+
response = self._client.transport.get(
30+
self._client._build_url(f"/task/grok-computer-use/{job_id}")
31+
)
32+
return GrokComputerUseTaskResponse(**response.data)
33+
34+
def get_status(self, job_id: str) -> GrokComputerUseTaskStatusResponse:
35+
response = self._client.transport.get(
36+
self._client._build_url(f"/task/grok-computer-use/{job_id}/status")
37+
)
38+
return GrokComputerUseTaskStatusResponse(**response.data)
39+
40+
def stop(self, job_id: str) -> BasicResponse:
41+
response = self._client.transport.put(
42+
self._client._build_url(f"/task/grok-computer-use/{job_id}/stop")
43+
)
44+
return BasicResponse(**response.data)
45+
46+
def start_and_wait(
47+
self, params: StartGrokComputerUseTaskParams
48+
) -> GrokComputerUseTaskResponse:
49+
job_start_resp = self.start(params)
50+
job_id = job_start_resp.job_id
51+
if not job_id:
52+
raise HyperbrowserError("Failed to start Grok Computer Use task job")
53+
54+
failures = 0
55+
while True:
56+
try:
57+
job_response = self.get_status(job_id)
58+
if (
59+
job_response.status == "completed"
60+
or job_response.status == "failed"
61+
or job_response.status == "stopped"
62+
):
63+
return self.get(job_id)
64+
failures = 0
65+
except Exception as e:
66+
failures += 1
67+
if failures >= POLLING_ATTEMPTS:
68+
raise HyperbrowserError(
69+
f"Failed to poll Grok Computer Use task job {job_id} after {POLLING_ATTEMPTS} attempts: {e}"
70+
)
71+
time.sleep(2)

hyperbrowser/models/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@
117117
StartGeminiComputerUseTaskResponse,
118118
GeminiComputerUseApiKeys,
119119
)
120+
from .agents.grok_computer_use import (
121+
GrokComputerUseTaskStatus,
122+
GrokComputerUseStepResponse,
123+
GrokComputerUseTaskData,
124+
GrokComputerUseTaskResponse,
125+
GrokComputerUseTaskStatusResponse,
126+
StartGrokComputerUseTaskParams,
127+
StartGrokComputerUseTaskResponse,
128+
GrokComputerUseApiKeys,
129+
)
120130
from .agents.cua import (
121131
CuaTaskData,
122132
CuaTaskResponse,
@@ -147,6 +157,8 @@
147157
BrowserUseLlm,
148158
ClaudeComputerUseLlm,
149159
CuaLlm,
160+
GrokComputerUseLlm,
161+
GrokReasoningEffort,
150162
Country,
151163
DownloadsStatus,
152164
FetchScreenshotFormat,
@@ -458,6 +470,13 @@
458470
"GeminiComputerUseTaskStatusResponse",
459471
"StartGeminiComputerUseTaskParams",
460472
"StartGeminiComputerUseTaskResponse",
473+
"GrokComputerUseTaskStatus",
474+
"GrokComputerUseStepResponse",
475+
"GrokComputerUseTaskData",
476+
"GrokComputerUseTaskResponse",
477+
"GrokComputerUseTaskStatusResponse",
478+
"StartGrokComputerUseTaskParams",
479+
"StartGrokComputerUseTaskResponse",
461480
"CuaTaskStatus",
462481
"CuaTaskData",
463482
"CuaTaskResponse",
@@ -467,8 +486,11 @@
467486
"BrowserUseApiKeys",
468487
"ClaudeComputerUseApiKeys",
469488
"GeminiComputerUseApiKeys",
489+
"GrokComputerUseApiKeys",
470490
"CuaApiKeys",
471491
"HyperAgentApiKeys",
492+
"GrokComputerUseLlm",
493+
"GrokReasoningEffort",
472494
"HyperAgentOutputV110",
473495
"HyperAgentStepV110",
474496
# crawl
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from typing import Any, Literal, Optional
2+
3+
from pydantic import BaseModel, ConfigDict, Field
4+
5+
from .cua import CuaStepIncompleteDetails, CuaStepReasoning, CuaStepResponseError
6+
from ..session import CreateSessionParams
7+
from ..consts import GrokComputerUseLlm, GrokReasoningEffort
8+
9+
GrokComputerUseTaskStatus = Literal[
10+
"pending", "running", "completed", "failed", "stopped"
11+
]
12+
13+
14+
class GrokComputerUseApiKeys(BaseModel):
15+
"""
16+
API keys for the Grok Computer Use task.
17+
"""
18+
19+
model_config = ConfigDict(
20+
populate_by_alias=True,
21+
)
22+
23+
xai: Optional[str] = Field(default=None, serialization_alias="xai")
24+
25+
26+
class StartGrokComputerUseTaskParams(BaseModel):
27+
"""
28+
Parameters for creating a new Grok Computer Use task.
29+
"""
30+
31+
model_config = ConfigDict(
32+
populate_by_alias=True,
33+
)
34+
35+
task: str
36+
llm: Optional[GrokComputerUseLlm] = Field(default=None, serialization_alias="llm")
37+
reasoning_effort: Optional[GrokReasoningEffort] = Field(
38+
default=None, serialization_alias="reasoningEffort"
39+
)
40+
session_id: Optional[str] = Field(default=None, serialization_alias="sessionId")
41+
max_failures: Optional[int] = Field(default=None, serialization_alias="maxFailures")
42+
max_steps: Optional[int] = Field(default=None, serialization_alias="maxSteps")
43+
keep_browser_open: Optional[bool] = Field(
44+
default=None, serialization_alias="keepBrowserOpen"
45+
)
46+
session_options: Optional[CreateSessionParams] = Field(
47+
default=None, serialization_alias="sessionOptions"
48+
)
49+
use_custom_api_keys: Optional[bool] = Field(
50+
default=None, serialization_alias="useCustomApiKeys"
51+
)
52+
api_keys: Optional[GrokComputerUseApiKeys] = Field(
53+
default=None, serialization_alias="apiKeys"
54+
)
55+
use_computer_action: Optional[bool] = Field(
56+
default=None, serialization_alias="useComputerAction"
57+
)
58+
59+
60+
class StartGrokComputerUseTaskResponse(BaseModel):
61+
"""
62+
Response from starting a Grok Computer Use task.
63+
"""
64+
65+
model_config = ConfigDict(
66+
populate_by_alias=True,
67+
)
68+
69+
job_id: str = Field(alias="jobId")
70+
live_url: Optional[str] = Field(default=None, alias="liveUrl")
71+
72+
73+
class GrokComputerUseTaskStatusResponse(BaseModel):
74+
"""
75+
Response from getting a Grok Computer Use task status.
76+
"""
77+
78+
model_config = ConfigDict(
79+
populate_by_alias=True,
80+
)
81+
82+
status: GrokComputerUseTaskStatus
83+
84+
85+
class GrokComputerUseStepResponse(BaseModel):
86+
"""
87+
Response from a single Grok Computer Use step.
88+
"""
89+
90+
model_config = ConfigDict(
91+
populate_by_alias=True,
92+
)
93+
94+
created_at: Optional[int] = Field(default=None, serialization_alias="created_at")
95+
completed_at: Optional[int] = Field(
96+
default=None, serialization_alias="completed_at"
97+
)
98+
output_text: Optional[str] = Field(default=None, serialization_alias="output_text")
99+
error: Optional[CuaStepResponseError] = Field(
100+
default=None, serialization_alias="error"
101+
)
102+
incomplete_details: Optional[CuaStepIncompleteDetails] = Field(
103+
default=None, serialization_alias="incomplete_details"
104+
)
105+
model: Optional[str] = Field(default=None, serialization_alias="model")
106+
output: Optional[list[Any]] = Field(default=None, serialization_alias="output")
107+
reasoning: Optional[CuaStepReasoning] = Field(
108+
default=None, serialization_alias="reasoning"
109+
)
110+
status: Optional[str] = Field(default=None, serialization_alias="status")
111+
112+
113+
class GrokComputerUseTaskData(BaseModel):
114+
model_config = ConfigDict(
115+
populate_by_alias=True,
116+
)
117+
118+
steps: list[GrokComputerUseStepResponse]
119+
final_result: Optional[str] = Field(default=None, alias="finalResult")
120+
121+
122+
class GrokComputerUseTaskMetadata(BaseModel):
123+
model_config = ConfigDict(
124+
populate_by_alias=True,
125+
)
126+
127+
input_tokens: Optional[int] = Field(default=None, alias="inputTokens")
128+
output_tokens: Optional[int] = Field(default=None, alias="outputTokens")
129+
num_task_steps_completed: Optional[int] = Field(
130+
default=None, alias="numTaskStepsCompleted"
131+
)
132+
133+
134+
class GrokComputerUseTaskResponse(BaseModel):
135+
"""
136+
Response from a Grok Computer Use task.
137+
"""
138+
139+
model_config = ConfigDict(
140+
populate_by_alias=True,
141+
)
142+
143+
job_id: str = Field(alias="jobId")
144+
status: GrokComputerUseTaskStatus
145+
metadata: Optional[GrokComputerUseTaskMetadata] = Field(
146+
default=None, alias="metadata"
147+
)
148+
data: Optional[GrokComputerUseTaskData] = Field(default=None, alias="data")
149+
error: Optional[str] = Field(default=None, alias="error")
150+
live_url: Optional[str] = Field(default=None, alias="liveUrl")

hyperbrowser/models/consts.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@
9090
"gemini-3-flash-preview",
9191
"gemini-2.5-computer-use-preview-10-2025",
9292
]
93+
GrokComputerUseLlm = Literal["grok-4.5",]
94+
GrokReasoningEffort = Literal[
95+
"low",
96+
"medium",
97+
"high",
98+
]
9399
SessionRegion = Literal[
94100
"us-central",
95101
"asia-south",

0 commit comments

Comments
 (0)