Skip to content

Commit b8a90c9

Browse files
committed
Revert "refactor: move harness resources to .github/harness/ (aws#992)"
This reverts commit aef3890.
1 parent aef3890 commit b8a90c9

7 files changed

Lines changed: 66 additions & 117 deletions

File tree

.github/harness/Dockerfile

Lines changed: 0 additions & 33 deletions
This file was deleted.

.github/harness/README.md

Lines changed: 0 additions & 39 deletions
This file was deleted.
File renamed without changes.
File renamed without changes.
Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Invoke Bedrock AgentCore Harness to review a GitHub PR.
22
33
Reads PR_URL from the environment. Streams harness output to stdout.
4-
Uses the boto3 bedrock-agentcore client's invoke_harness API.
4+
Uses raw HTTP with SigV4 signing — no custom service model needed.
55
"""
66

77
import json
@@ -11,6 +11,11 @@
1111
import uuid
1212

1313
import boto3
14+
from botocore.auth import SigV4Auth
15+
from botocore.awsrequest import AWSRequest
16+
from botocore.eventstream import EventStreamBuffer
17+
from urllib.parse import quote
18+
import urllib3
1419

1520
# ANSI color codes
1621
CYAN = "\033[36m"
@@ -20,7 +25,7 @@
2025
DIM = "\033[2m"
2126
RESET = "\033[0m"
2227

23-
SCRIPTS_DIR = os.path.dirname(__file__)
28+
SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..")
2429

2530

2631
def read_prompt(filename):
@@ -30,37 +35,50 @@ def read_prompt(filename):
3035
return f.read()
3136

3237

33-
def invoke_harness_streaming(harness_arn, session_id, system_prompt, messages, model_id, region):
34-
"""Call invoke_harness via boto3 and return the event stream."""
35-
client = boto3.client("bedrock-agentcore", region_name=region)
36-
response = client.invoke_harness(
37-
harnessArn=harness_arn,
38-
runtimeSessionId=session_id,
39-
systemPrompt=[{"text": system_prompt}],
40-
messages=messages,
41-
model={"bedrockModelConfig": {"modelId": model_id}},
38+
def invoke_harness(harness_arn, body, region):
39+
"""Send a SigV4-signed request to the harness invoke endpoint. Returns a streaming response.
40+
41+
InvokeHarness is not in standard boto3, so we call the REST API directly.
42+
boto3 is only used to resolve AWS credentials (from env vars, OIDC, etc.)
43+
and sign the request with SigV4. The response is an AWS binary event stream.
44+
"""
45+
session = boto3.Session(region_name=region)
46+
credentials = session.get_credentials().get_frozen_credentials()
47+
url = f"https://bedrock-agentcore.{region}.amazonaws.com/harnesses/invoke?harnessArn={quote(harness_arn, safe='')}"
48+
request = AWSRequest(method="POST", url=url, data=body, headers={
49+
"Content-Type": "application/json",
50+
"Accept": "application/vnd.amazon.eventstream",
51+
})
52+
SigV4Auth(credentials, "bedrock-agentcore", region).add_auth(request)
53+
return urllib3.PoolManager().urlopen(
54+
"POST", url, body=body,
55+
headers=dict(request.headers),
56+
preload_content=False,
57+
timeout=urllib3.Timeout(connect=10, read=600),
4258
)
43-
return response["stream"]
44-
45-
46-
def parse_events(event_stream):
47-
"""Yield (event_type, payload) tuples from the boto3 event stream."""
48-
for event in event_stream:
49-
if "contentBlockStart" in event:
50-
yield "contentBlockStart", event["contentBlockStart"]
51-
elif "contentBlockDelta" in event:
52-
yield "contentBlockDelta", event["contentBlockDelta"]
53-
elif "contentBlockStop" in event:
54-
yield "contentBlockStop", event["contentBlockStop"]
55-
elif "messageStop" in event:
56-
yield "messageStop", event["messageStop"]
57-
elif "internalServerException" in event:
58-
yield "internalServerException", event["internalServerException"]
59-
elif "runtimeClientError" in event:
60-
yield "runtimeClientError", event["runtimeClientError"]
61-
62-
63-
def print_stream(event_stream):
59+
60+
61+
def parse_events(http_response):
62+
"""Yield (event_type, payload) tuples from the harness binary event stream.
63+
64+
The response arrives as raw bytes in AWS binary event stream format.
65+
EventStreamBuffer reassembles complete events from the 4KB chunks,
66+
and we decode each event's JSON payload before yielding it.
67+
"""
68+
event_buffer = EventStreamBuffer()
69+
for chunk in http_response.stream(4096):
70+
event_buffer.add_data(chunk)
71+
for event in event_buffer:
72+
if event.headers.get(":message-type") == "exception":
73+
payload = json.loads(event.payload.decode("utf-8"))
74+
print(f"\n{RED}ERROR: {payload}{RESET}", file=sys.stderr)
75+
sys.exit(1)
76+
event_type = event.headers.get(":event-type", "")
77+
if event.payload:
78+
yield event_type, json.loads(event.payload.decode("utf-8"))
79+
80+
81+
def print_stream(http_response):
6482
"""Display harness events with GitHub Actions log groups.
6583
6684
The harness streams events as the agent works:
@@ -94,7 +112,7 @@ def flush_text():
94112
print(f"{DIM}{line}{RESET}", flush=True)
95113
text_buffer = ""
96114

97-
for event_type, payload in parse_events(event_stream):
115+
for event_type, payload in parse_events(http_response):
98116

99117
if event_type == "contentBlockStart":
100118
start = payload.get("start", {})
@@ -153,11 +171,6 @@ def flush_text():
153171
print(f"\n{RED}ERROR: {payload}{RESET}", file=sys.stderr)
154172
sys.exit(1)
155173

156-
elif event_type == "runtimeClientError":
157-
close_group()
158-
print(f"\n{RED}ERROR: {payload.get('message', payload)}{RESET}", file=sys.stderr)
159-
sys.exit(1)
160-
161174
close_group()
162175
total = time.time() - start_time
163176
print(f"\n{GREEN}Review complete.{RESET} {DIM}({iteration} tool calls, {int(total)}s total){RESET}")
@@ -187,10 +200,18 @@ def flush_text():
187200
SYSTEM_PROMPT = read_prompt("system.md")
188201
REVIEW_PROMPT = read_prompt("review.md").format(pr_url=PR_URL)
189202

190-
messages = [{"role": "user", "content": [{"text": REVIEW_PROMPT}]}]
203+
request_body = json.dumps({
204+
"runtimeSessionId": SESSION_ID,
205+
"systemPrompt": [{"text": SYSTEM_PROMPT}],
206+
"messages": [{"role": "user", "content": [{"text": REVIEW_PROMPT}]}],
207+
"model": {"bedrockModelConfig": {"modelId": MODEL_ID}},
208+
})
209+
210+
http_response = invoke_harness(HARNESS_ARN, request_body, REGION)
191211

192-
event_stream = invoke_harness_streaming(
193-
HARNESS_ARN, SESSION_ID, SYSTEM_PROMPT, messages, MODEL_ID, REGION
194-
)
212+
if http_response.status != 200:
213+
error = http_response.read().decode("utf-8")
214+
print(f"{RED}ERROR: HTTP {http_response.status}: {error}{RESET}", file=sys.stderr)
215+
sys.exit(1)
195216

196-
print_stream(event_stream)
217+
print_stream(http_response)

.github/workflows/pr-ai-review.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ jobs:
139139
env:
140140
PR_URL: ${{ steps.pr-url.outputs.url }}
141141
HARNESS_ARN: ${{ secrets.HARNESS_ARN }}
142-
run: python .github/harness/harness_review.py
142+
run: python .github/scripts/python/harness_review.py
143143

144144
- name: Remove agentcore-harness-reviewing label
145145
if: always()

.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
CHANGELOG.md
22
src/assets/**/*.md
3-
.github/harness/prompts/
3+
.github/scripts/prompts/

0 commit comments

Comments
 (0)