11from __future__ import annotations
22
33import logging
4+ import time
45from typing import Any , Literal
56
67import orjson
@@ -117,6 +118,28 @@ def execute(cls, organization: Organization, run_id: int) -> None:
117118 on_completion=NotifyOnComplete
118119 )
119120 run_id = client.start_run("Analyze this issue")
121+
122+ # WITH CODE EDITING AND PR CREATION
123+ client = SeerExplorerClient(
124+ organization,
125+ user,
126+ enable_coding=True, # Enable code editing tools
127+ )
128+
129+ run_id = client.start_run("Fix the null pointer exception in auth.py")
130+ state = client.get_run(run_id, blocking=True)
131+
132+ # Check if agent made code changes and if they need to be pushed
133+ has_changes, is_synced = state.has_code_changes()
134+ if has_changes and not is_synced:
135+ # Push changes to PR (creates new PR or updates existing)
136+ state = client.push_changes(run_id)
137+
138+ # Get PR info for each repo
139+ for repo_name in state.get_file_patches_by_repo().keys():
140+ pr_state = state.get_pr_state(repo_name)
141+ if pr_state and pr_state.pr_url:
142+ print(f"PR created: {pr_state.pr_url}")
120143 ```
121144
122145 Args:
@@ -128,6 +151,7 @@ def execute(cls, organization: Organization, run_id: int) -> None:
128151 on_completion_hook: Optional `ExplorerOnCompletionHook` class to call when the agent completes. The hook's execute() method receives the organization and run ID. This is called whether or not the agent was successful. Hook classes must be module-level (not nested classes).
129152 intelligence_level: Optionally set the intelligence level of the agent. Higher intelligence gives better result quality at the cost of significantly higher latency and cost.
130153 is_interactive: Enable full interactive, human-like features of the agent. Only enable if you support *all* available interactions in Seer. An example use of this is the explorer chat in Sentry UI.
154+ enable_coding: Enable code editing tools. When disabled, the agent cannot make code changes. Default is False.
131155 """
132156
133157 def __init__ (
@@ -140,6 +164,7 @@ def __init__(
140164 on_completion_hook : type [ExplorerOnCompletionHook ] | None = None ,
141165 intelligence_level : Literal ["low" , "medium" , "high" ] = "medium" ,
142166 is_interactive : bool = False ,
167+ enable_coding : bool = False ,
143168 ):
144169 self .organization = organization
145170 self .user = user
@@ -149,6 +174,7 @@ def __init__(
149174 self .category_key = category_key
150175 self .category_value = category_value
151176 self .is_interactive = is_interactive
177+ self .enable_coding = enable_coding
152178
153179 # Validate that category_key and category_value are provided together
154180 if category_key == "" or category_value == "" :
@@ -198,6 +224,7 @@ def start_run(
198224 "user_org_context" : collect_user_org_context (self .user , self .organization ),
199225 "intelligence_level" : self .intelligence_level ,
200226 "is_interactive" : self .is_interactive ,
227+ "enable_coding" : self .enable_coding ,
201228 }
202229
203230 # Add artifact key and schema if provided
@@ -273,6 +300,7 @@ def continue_run(
273300 "insert_index" : insert_index ,
274301 "on_page_context" : on_page_context ,
275302 "is_interactive" : self .is_interactive ,
303+ "enable_coding" : self .enable_coding ,
276304 }
277305
278306 # Add artifact key and schema if provided
@@ -384,3 +412,68 @@ def get_runs(
384412
385413 runs = [ExplorerRun (** run ) for run in result .get ("data" , [])]
386414 return runs
415+
416+ def push_changes (
417+ self ,
418+ run_id : int ,
419+ repo_name : str | None = None ,
420+ poll_interval : float = 2.0 ,
421+ poll_timeout : float = 120.0 ,
422+ ) -> SeerRunState :
423+ """
424+ Push code changes to PR(s) and wait for completion.
425+
426+ Creates new PRs or updates existing ones with current file patches.
427+ Polls until all PR operations complete.
428+
429+ Args:
430+ run_id: The run ID
431+ repo_name: Specific repo to push, or None for all repos with changes
432+ poll_interval: Seconds between polls
433+ poll_timeout: Maximum seconds to wait
434+
435+ Returns:
436+ SeerRunState: Final state with PR info
437+
438+ Raises:
439+ TimeoutError: If polling exceeds timeout
440+ requests.HTTPError: If the Seer API request fails
441+ """
442+ # Trigger PR creation
443+ path = "/v1/automation/explorer/update"
444+ payload = {
445+ "run_id" : run_id ,
446+ "payload" : {
447+ "type" : "create_pr" ,
448+ "repo_name" : repo_name ,
449+ },
450+ }
451+ body = orjson .dumps (payload , option = orjson .OPT_NON_STR_KEYS )
452+ response = requests .post (
453+ f"{ settings .SEER_AUTOFIX_URL } { path } " ,
454+ data = body ,
455+ headers = {
456+ "content-type" : "application/json;charset=utf-8" ,
457+ ** sign_with_seer_secret (body ),
458+ },
459+ )
460+ response .raise_for_status ()
461+
462+ # Poll until PR creation completes
463+ start_time = time .time ()
464+
465+ while True :
466+ state = fetch_run_status (run_id , self .organization )
467+
468+ # Check if any PRs are still being created
469+ any_creating = any (
470+ pr .pr_creation_status == "creating" for pr in state .repo_pr_states .values ()
471+ )
472+
473+ if not any_creating :
474+ return state
475+
476+ if time .time () - start_time > poll_timeout :
477+ raise TimeoutError (f"PR creation timed out after { poll_timeout } s" )
478+
479+ time .sleep (poll_interval )
0 commit comments