@openhands/extensions 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +6 -0
- package/.codex-plugin/marketplace.json +6 -0
- package/.plugin/marketplace.json +6 -0
- package/.release-please-manifest.json +1 -1
- package/README.md +3 -2
- package/automations/bundle-index.js +11 -7
- package/automations/catalog/github-pr-reviewer/manifest.json +19 -8
- package/automations/catalog/github-stale-ci-pr-closer/manifest.json +71 -0
- package/automations/catalog-index.js +2 -0
- package/marketplaces/openhands-extensions.json +6 -0
- package/package.json +1 -1
- package/plugins/openhands/skills/openhands-sdk/SKILL.md +1 -0
- package/plugins/pr-review/skills/github-pr-review/SKILL.md +12 -2
- package/plugins/qa-changes/skills/github-pr-review/SKILL.md +12 -2
- package/plugins/vulnerability-remediation/skills/github/scripts/github_client.py +116 -0
- package/pyproject.toml +1 -1
- package/python/openhands_extensions/_version.py +1 -1
- package/skills/github/scripts/github_client.py +116 -0
- package/skills/github-agents-md-maintainer/scripts/github_client.py +116 -0
- package/skills/github-delivery-watchdog/scripts/github_client.py +116 -0
- package/skills/github-issue-to-pr/scripts/github_client.py +116 -0
- package/skills/github-issue-triage/scripts/github_client.py +116 -0
- package/skills/github-pr-review/SKILL.md +12 -2
- package/skills/github-pr-reviewer/README.md +35 -0
- package/skills/github-pr-reviewer/SKILL.md +156 -4
- package/skills/github-pr-reviewer/references/state-schema.md +15 -0
- package/skills/github-pr-reviewer/scripts/github_client.py +116 -0
- package/skills/github-pr-reviewer/scripts/main.py +17 -1
- package/skills/github-pr-reviewer/scripts/worker.py +820 -58
- package/skills/github-repo-monitor/scripts/github_client.py +116 -0
- package/skills/github-repo-monitor/scripts/main.py +32 -1
- package/skills/github-stale-ci-pr-closer/.claude-plugin/plugin.json +9 -0
- package/skills/github-stale-ci-pr-closer/.codex-plugin/plugin.json +9 -0
- package/skills/github-stale-ci-pr-closer/.plugin/plugin.json +9 -0
- package/skills/github-stale-ci-pr-closer/README.md +9 -0
- package/skills/github-stale-ci-pr-closer/SKILL.md +24 -0
- package/skills/github-stale-ci-pr-closer/commands/github-stale-ci-pr-closer.md +8 -0
- package/skills/github-stale-ci-pr-closer/scripts/worker.py +335 -0
- package/skills/github-stale-ci-pr-closer/tests/test_worker.py +213 -0
- package/skills/index.js +12 -3
- package/skills/openhands-sdk/SKILL.md +1 -0
- package/tests/fixtures/automations/github-pr-reviewer.json +39 -24
- package/tests/test_github_automation_foundation.py +158 -0
- package/tests/test_github_repo_monitor.py +107 -0
- package/tests/test_github_reviewer_delivery.py +2108 -3
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
],
|
|
21
21
|
"githubTokenSecret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
22
22
|
"maintainers": "",
|
|
23
|
-
"triggerReviewer": "all-hands-bot"
|
|
23
|
+
"triggerReviewer": "all-hands-bot",
|
|
24
|
+
"maxNewPerRun": 2
|
|
24
25
|
},
|
|
25
26
|
"upload": {
|
|
26
27
|
"request": {
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
"timeout": 300,
|
|
68
69
|
"template": {
|
|
69
70
|
"id": "github-pr-reviewer",
|
|
70
|
-
"version": "1.
|
|
71
|
+
"version": "1.8.0",
|
|
71
72
|
"config": {
|
|
72
73
|
"repos": [
|
|
73
74
|
"OpenHands/agent-server-gui"
|
|
@@ -76,7 +77,8 @@
|
|
|
76
77
|
"review_tone": "thorough",
|
|
77
78
|
"github_token_secret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
78
79
|
"maintainers": "",
|
|
79
|
-
"trigger_reviewer": "all-hands-bot"
|
|
80
|
+
"trigger_reviewer": "all-hands-bot",
|
|
81
|
+
"max_new_per_run": 2
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
}
|
|
@@ -106,7 +108,7 @@
|
|
|
106
108
|
"timeout": 300,
|
|
107
109
|
"template": {
|
|
108
110
|
"id": "github-pr-reviewer",
|
|
109
|
-
"version": "1.
|
|
111
|
+
"version": "1.8.0",
|
|
110
112
|
"config": {
|
|
111
113
|
"repos": [
|
|
112
114
|
"OpenHands/agent-server-gui"
|
|
@@ -115,7 +117,8 @@
|
|
|
115
117
|
"review_tone": "thorough",
|
|
116
118
|
"github_token_secret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
117
119
|
"maintainers": "",
|
|
118
|
-
"trigger_reviewer": "all-hands-bot"
|
|
120
|
+
"trigger_reviewer": "all-hands-bot",
|
|
121
|
+
"max_new_per_run": 2
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
}
|
|
@@ -162,7 +165,8 @@
|
|
|
162
165
|
],
|
|
163
166
|
"githubTokenSecret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
164
167
|
"maintainers": "",
|
|
165
|
-
"triggerReviewer": "all-hands-bot"
|
|
168
|
+
"triggerReviewer": "all-hands-bot",
|
|
169
|
+
"maxNewPerRun": 2
|
|
166
170
|
},
|
|
167
171
|
"upload": {
|
|
168
172
|
"request": {
|
|
@@ -209,7 +213,7 @@
|
|
|
209
213
|
"timeout": 300,
|
|
210
214
|
"template": {
|
|
211
215
|
"id": "github-pr-reviewer",
|
|
212
|
-
"version": "1.
|
|
216
|
+
"version": "1.8.0",
|
|
213
217
|
"config": {
|
|
214
218
|
"repos": [
|
|
215
219
|
"OpenHands/agent-server-gui",
|
|
@@ -219,7 +223,8 @@
|
|
|
219
223
|
"review_tone": "thorough",
|
|
220
224
|
"github_token_secret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
221
225
|
"maintainers": "",
|
|
222
|
-
"trigger_reviewer": "all-hands-bot"
|
|
226
|
+
"trigger_reviewer": "all-hands-bot",
|
|
227
|
+
"max_new_per_run": 2
|
|
223
228
|
}
|
|
224
229
|
}
|
|
225
230
|
}
|
|
@@ -249,7 +254,7 @@
|
|
|
249
254
|
"timeout": 300,
|
|
250
255
|
"template": {
|
|
251
256
|
"id": "github-pr-reviewer",
|
|
252
|
-
"version": "1.
|
|
257
|
+
"version": "1.8.0",
|
|
253
258
|
"config": {
|
|
254
259
|
"repos": [
|
|
255
260
|
"OpenHands/agent-server-gui",
|
|
@@ -259,7 +264,8 @@
|
|
|
259
264
|
"review_tone": "thorough",
|
|
260
265
|
"github_token_secret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
261
266
|
"maintainers": "",
|
|
262
|
-
"trigger_reviewer": "all-hands-bot"
|
|
267
|
+
"trigger_reviewer": "all-hands-bot",
|
|
268
|
+
"max_new_per_run": 2
|
|
263
269
|
}
|
|
264
270
|
}
|
|
265
271
|
}
|
|
@@ -290,7 +296,7 @@
|
|
|
290
296
|
"preset_metadata": {
|
|
291
297
|
"template": {
|
|
292
298
|
"id": "github-pr-reviewer",
|
|
293
|
-
"version": "1.
|
|
299
|
+
"version": "1.8.0",
|
|
294
300
|
"config": {
|
|
295
301
|
"repos": [
|
|
296
302
|
"OpenHands/agent-server-gui",
|
|
@@ -300,7 +306,8 @@
|
|
|
300
306
|
"review_tone": "thorough",
|
|
301
307
|
"github_token_secret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
302
308
|
"maintainers": "",
|
|
303
|
-
"trigger_reviewer": "all-hands-bot"
|
|
309
|
+
"trigger_reviewer": "all-hands-bot",
|
|
310
|
+
"max_new_per_run": 2
|
|
304
311
|
}
|
|
305
312
|
}
|
|
306
313
|
}
|
|
@@ -322,7 +329,8 @@
|
|
|
322
329
|
],
|
|
323
330
|
"githubTokenSecret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
324
331
|
"maintainers": "",
|
|
325
|
-
"triggerReviewer": "all-hands-bot"
|
|
332
|
+
"triggerReviewer": "all-hands-bot",
|
|
333
|
+
"maxNewPerRun": 2
|
|
326
334
|
},
|
|
327
335
|
"preflight": {
|
|
328
336
|
"request": {
|
|
@@ -343,7 +351,7 @@
|
|
|
343
351
|
"timeout": 300,
|
|
344
352
|
"template": {
|
|
345
353
|
"id": "github-pr-reviewer",
|
|
346
|
-
"version": "1.
|
|
354
|
+
"version": "1.8.0",
|
|
347
355
|
"config": {
|
|
348
356
|
"repos": [
|
|
349
357
|
"OpenHands/agent-server-gui"
|
|
@@ -352,7 +360,8 @@
|
|
|
352
360
|
"review_tone": "concise",
|
|
353
361
|
"github_token_secret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
354
362
|
"maintainers": "",
|
|
355
|
-
"trigger_reviewer": "all-hands-bot"
|
|
363
|
+
"trigger_reviewer": "all-hands-bot",
|
|
364
|
+
"max_new_per_run": 2
|
|
356
365
|
}
|
|
357
366
|
}
|
|
358
367
|
}
|
|
@@ -390,7 +399,8 @@
|
|
|
390
399
|
],
|
|
391
400
|
"githubTokenSecret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
392
401
|
"maintainers": "",
|
|
393
|
-
"triggerReviewer": "all-hands-bot"
|
|
402
|
+
"triggerReviewer": "all-hands-bot",
|
|
403
|
+
"maxNewPerRun": 2
|
|
394
404
|
},
|
|
395
405
|
"upload": {
|
|
396
406
|
"request": {
|
|
@@ -434,7 +444,7 @@
|
|
|
434
444
|
"timeout": 300,
|
|
435
445
|
"template": {
|
|
436
446
|
"id": "github-pr-reviewer",
|
|
437
|
-
"version": "1.
|
|
447
|
+
"version": "1.8.0",
|
|
438
448
|
"config": {
|
|
439
449
|
"repos": [
|
|
440
450
|
"OpenHands/agent-server-gui"
|
|
@@ -443,7 +453,8 @@
|
|
|
443
453
|
"review_tone": "concise",
|
|
444
454
|
"github_token_secret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
445
455
|
"maintainers": "",
|
|
446
|
-
"trigger_reviewer": "all-hands-bot"
|
|
456
|
+
"trigger_reviewer": "all-hands-bot",
|
|
457
|
+
"max_new_per_run": 2
|
|
447
458
|
}
|
|
448
459
|
}
|
|
449
460
|
}
|
|
@@ -484,7 +495,8 @@
|
|
|
484
495
|
],
|
|
485
496
|
"githubTokenSecret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
486
497
|
"maintainers": "",
|
|
487
|
-
"triggerReviewer": "all-hands-bot"
|
|
498
|
+
"triggerReviewer": "all-hands-bot",
|
|
499
|
+
"maxNewPerRun": 2
|
|
488
500
|
},
|
|
489
501
|
"upload": {
|
|
490
502
|
"request": {
|
|
@@ -528,7 +540,7 @@
|
|
|
528
540
|
"timeout": 300,
|
|
529
541
|
"template": {
|
|
530
542
|
"id": "github-pr-reviewer",
|
|
531
|
-
"version": "1.
|
|
543
|
+
"version": "1.8.0",
|
|
532
544
|
"config": {
|
|
533
545
|
"repos": [
|
|
534
546
|
"OpenHands/agent-server-gui"
|
|
@@ -537,7 +549,8 @@
|
|
|
537
549
|
"review_tone": "concise",
|
|
538
550
|
"github_token_secret": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
539
551
|
"maintainers": "",
|
|
540
|
-
"trigger_reviewer": "all-hands-bot"
|
|
552
|
+
"trigger_reviewer": "all-hands-bot",
|
|
553
|
+
"max_new_per_run": 2
|
|
541
554
|
}
|
|
542
555
|
},
|
|
543
556
|
"repos": [
|
|
@@ -580,7 +593,8 @@
|
|
|
580
593
|
"OpenHands/automation"
|
|
581
594
|
],
|
|
582
595
|
"githubTokenSecret": "GITHUB_TOKEN_REVIEW",
|
|
583
|
-
"maintainers": "neubig,malhotra5"
|
|
596
|
+
"maintainers": "neubig,malhotra5",
|
|
597
|
+
"maxNewPerRun": 2
|
|
584
598
|
},
|
|
585
599
|
"preflight": {
|
|
586
600
|
"request": {
|
|
@@ -602,7 +616,7 @@
|
|
|
602
616
|
"timeout": 300,
|
|
603
617
|
"template": {
|
|
604
618
|
"id": "github-pr-reviewer",
|
|
605
|
-
"version": "1.
|
|
619
|
+
"version": "1.8.0",
|
|
606
620
|
"config": {
|
|
607
621
|
"repos": [
|
|
608
622
|
"OpenHands/automation"
|
|
@@ -611,7 +625,8 @@
|
|
|
611
625
|
"trigger_reviewer": "all-hands-bot",
|
|
612
626
|
"review_tone": "concise",
|
|
613
627
|
"maintainers": "neubig,malhotra5",
|
|
614
|
-
"github_token_secret": "GITHUB_TOKEN_REVIEW"
|
|
628
|
+
"github_token_secret": "GITHUB_TOKEN_REVIEW",
|
|
629
|
+
"max_new_per_run": 2
|
|
615
630
|
}
|
|
616
631
|
}
|
|
617
632
|
}
|
|
@@ -71,3 +71,161 @@ def test_dependency_permission_errors_fail_closed():
|
|
|
71
71
|
repository.gh = denied
|
|
72
72
|
with pytest.raises(HTTPError):
|
|
73
73
|
repository.dependencies_complete({"body": "Depends on: #12"})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_check_runs_reads_every_page_of_the_object_response():
|
|
77
|
+
"""The endpoint answers with an object, not a list, so paging is manual."""
|
|
78
|
+
repository = object.__new__(github_client.GitHubRepository)
|
|
79
|
+
pages = {
|
|
80
|
+
1: {"total_count": 3, "check_runs": [{"name": "a"}, {"name": "b"}]},
|
|
81
|
+
2: {"total_count": 3, "check_runs": [{"name": "c"}]},
|
|
82
|
+
}
|
|
83
|
+
requests = []
|
|
84
|
+
|
|
85
|
+
def gh(method, path, body=None):
|
|
86
|
+
assert method == "GET"
|
|
87
|
+
page = int(path.rsplit("page=", 1)[1])
|
|
88
|
+
requests.append(page)
|
|
89
|
+
return pages[page]
|
|
90
|
+
|
|
91
|
+
repository.gh = gh
|
|
92
|
+
|
|
93
|
+
assert [run["name"] for run in repository.check_runs("abc")] == ["a", "b", "c"]
|
|
94
|
+
assert requests == [1, 2]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_check_runs_stops_on_an_empty_first_page():
|
|
98
|
+
repository = object.__new__(github_client.GitHubRepository)
|
|
99
|
+
repository.gh = lambda *args, **kwargs: {"total_count": 0, "check_runs": []}
|
|
100
|
+
|
|
101
|
+
assert repository.check_runs("abc") == []
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_workflow_runs_reads_every_page_of_the_object_response():
|
|
105
|
+
"""The runs endpoint answers with an object too, so paging is manual."""
|
|
106
|
+
repository = object.__new__(github_client.GitHubRepository)
|
|
107
|
+
pages = {
|
|
108
|
+
1: {"total_count": 3, "workflow_runs": [{"name": "a"}, {"name": "b"}]},
|
|
109
|
+
2: {"total_count": 3, "workflow_runs": [{"name": "c"}]},
|
|
110
|
+
}
|
|
111
|
+
requests = []
|
|
112
|
+
|
|
113
|
+
def gh(method, path, body=None):
|
|
114
|
+
assert method == "GET"
|
|
115
|
+
assert "head_sha=abc" in path
|
|
116
|
+
page = int(path.rsplit("page=", 1)[1])
|
|
117
|
+
requests.append(page)
|
|
118
|
+
return pages[page]
|
|
119
|
+
|
|
120
|
+
repository.gh = gh
|
|
121
|
+
|
|
122
|
+
assert [run["name"] for run in repository.workflow_runs("abc")] == [
|
|
123
|
+
"a",
|
|
124
|
+
"b",
|
|
125
|
+
"c",
|
|
126
|
+
]
|
|
127
|
+
assert requests == [1, 2]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_required_check_contexts_keeps_only_required_nodes():
|
|
131
|
+
"""`isRequired` is the merge-policy signal; optional contexts are dropped."""
|
|
132
|
+
repository = object.__new__(github_client.GitHubRepository)
|
|
133
|
+
repository.repository = "owner/repo"
|
|
134
|
+
calls = []
|
|
135
|
+
|
|
136
|
+
def api(method, path, params=None, body=None):
|
|
137
|
+
calls.append((method, path, body))
|
|
138
|
+
return {
|
|
139
|
+
"data": {
|
|
140
|
+
"repository": {
|
|
141
|
+
"pullRequest": {
|
|
142
|
+
"commits": {
|
|
143
|
+
"nodes": [
|
|
144
|
+
{
|
|
145
|
+
"commit": {
|
|
146
|
+
"statusCheckRollup": {
|
|
147
|
+
"contexts": {
|
|
148
|
+
"pageInfo": {"hasNextPage": False},
|
|
149
|
+
"nodes": [
|
|
150
|
+
{
|
|
151
|
+
"__typename": "CheckRun",
|
|
152
|
+
"name": "test-and-build (ubuntu)",
|
|
153
|
+
"isRequired": True,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"__typename": "CheckRun",
|
|
157
|
+
"name": "release ready",
|
|
158
|
+
"isRequired": False,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"__typename": "StatusContext",
|
|
162
|
+
"context": "ci/required",
|
|
163
|
+
"isRequired": True,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
repository.api = api
|
|
178
|
+
|
|
179
|
+
contexts = repository.required_check_contexts(17200)
|
|
180
|
+
|
|
181
|
+
assert contexts == [
|
|
182
|
+
{"name": "test-and-build (ubuntu)", "kind": "CheckRun"},
|
|
183
|
+
{"name": "ci/required", "kind": "StatusContext"},
|
|
184
|
+
]
|
|
185
|
+
method, path, body = calls[0]
|
|
186
|
+
assert (method, path) == ("POST", "/graphql")
|
|
187
|
+
assert body["variables"] == {"owner": "owner", "name": "repo", "number": 17200}
|
|
188
|
+
assert "isRequired(pullRequestNumber:$number)" in body["query"]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_required_check_contexts_rejects_an_over_long_rollup():
|
|
192
|
+
"""A truncated rollup must fail loudly instead of reporting a partial set."""
|
|
193
|
+
repository = object.__new__(github_client.GitHubRepository)
|
|
194
|
+
repository.repository = "owner/repo"
|
|
195
|
+
repository.api = lambda *args, **kwargs: {
|
|
196
|
+
"data": {
|
|
197
|
+
"repository": {
|
|
198
|
+
"pullRequest": {
|
|
199
|
+
"commits": {
|
|
200
|
+
"nodes": [
|
|
201
|
+
{
|
|
202
|
+
"commit": {
|
|
203
|
+
"statusCheckRollup": {
|
|
204
|
+
"contexts": {
|
|
205
|
+
"pageInfo": {"hasNextPage": True},
|
|
206
|
+
"nodes": [],
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
with pytest.raises(RuntimeError, match="one page"):
|
|
219
|
+
repository.required_check_contexts(2)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_required_check_contexts_raises_on_graphql_errors():
|
|
223
|
+
"""A GraphQL error must surface, so the caller can fail closed."""
|
|
224
|
+
repository = object.__new__(github_client.GitHubRepository)
|
|
225
|
+
repository.repository = "owner/repo"
|
|
226
|
+
repository.api = lambda *args, **kwargs: {
|
|
227
|
+
"errors": [{"message": "A pull request ID or pull request number is required."}]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
with pytest.raises(RuntimeError, match="required-check query failed"):
|
|
231
|
+
repository.required_check_contexts(2)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import json
|
|
3
|
+
import urllib.error
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
SCRIPT_PATH = (
|
|
8
|
+
Path(__file__).parent.parent
|
|
9
|
+
/ "skills"
|
|
10
|
+
/ "github-repo-monitor"
|
|
11
|
+
/ "scripts"
|
|
12
|
+
/ "main.py"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_repo_monitor_helpers():
|
|
17
|
+
source = SCRIPT_PATH.read_text(encoding="utf-8")
|
|
18
|
+
namespace: dict = {"__name__": "repo_monitor_main"}
|
|
19
|
+
exec(compile(source, str(SCRIPT_PATH), "exec"), namespace)
|
|
20
|
+
return namespace
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _FakeResponse:
|
|
24
|
+
def __init__(self, payload: dict):
|
|
25
|
+
self._payload = payload
|
|
26
|
+
|
|
27
|
+
def __enter__(self):
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def __exit__(self, *exc_info):
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
def read(self):
|
|
34
|
+
return json.dumps(self._payload).encode()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _http_error(url: str, code: int) -> urllib.error.HTTPError:
|
|
38
|
+
return urllib.error.HTTPError(url, code, "error", {}, io.BytesIO(b"{}"))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _patch_urlopen(monkeypatch, helpers, handler):
|
|
42
|
+
monkeypatch.setattr(helpers["urllib"].request, "urlopen", handler)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
ACTIVE_LLM = {"model": "openai/gpt-5.5", "api_key": "sk-active"}
|
|
46
|
+
PROFILE_LLM = {"model": "openai/gpt-5.6-sol", "api_key": "sk-profile"}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_automation_model_profile_resolves(monkeypatch):
|
|
50
|
+
helpers = load_repo_monitor_helpers()
|
|
51
|
+
monkeypatch.setenv("AUTOMATION_MODEL", "gpt-5.6-sol")
|
|
52
|
+
monkeypatch.setitem(
|
|
53
|
+
helpers, "_fetch_settings", lambda url, key: {"agent_settings": {"llm": ACTIVE_LLM}}
|
|
54
|
+
)
|
|
55
|
+
seen: dict = {}
|
|
56
|
+
|
|
57
|
+
def fake_urlopen(req, *args, **kwargs):
|
|
58
|
+
seen["url"] = req.full_url
|
|
59
|
+
seen["headers"] = {k.lower(): v for k, v in req.header_items()}
|
|
60
|
+
return _FakeResponse({"config": dict(PROFILE_LLM)})
|
|
61
|
+
|
|
62
|
+
_patch_urlopen(monkeypatch, helpers, fake_urlopen)
|
|
63
|
+
|
|
64
|
+
agent = helpers["_get_agent_dict"]("http://agent:8000", "key")
|
|
65
|
+
|
|
66
|
+
assert seen["url"] == "http://agent:8000/api/profiles/gpt-5.6-sol"
|
|
67
|
+
assert seen["headers"]["x-expose-secrets"] == "plaintext"
|
|
68
|
+
assert agent["llm"]["model"] == "openai/gpt-5.6-sol"
|
|
69
|
+
assert agent["llm"]["api_key"] == "sk-profile"
|
|
70
|
+
assert agent["llm"]["usage_id"] == "profile:gpt-5.6-sol"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_missing_profile_falls_back_to_active(monkeypatch):
|
|
74
|
+
helpers = load_repo_monitor_helpers()
|
|
75
|
+
monkeypatch.setenv("AUTOMATION_MODEL", "renamed-profile")
|
|
76
|
+
monkeypatch.setitem(
|
|
77
|
+
helpers, "_fetch_settings", lambda url, key: {"agent_settings": {"llm": ACTIVE_LLM}}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def fake_urlopen(req, *args, **kwargs):
|
|
81
|
+
raise _http_error(req.full_url, 404)
|
|
82
|
+
|
|
83
|
+
_patch_urlopen(monkeypatch, helpers, fake_urlopen)
|
|
84
|
+
|
|
85
|
+
agent = helpers["_get_agent_dict"]("http://agent:8000", "key")
|
|
86
|
+
|
|
87
|
+
assert agent["llm"] == ACTIVE_LLM
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_no_automation_model_uses_active_profile(monkeypatch):
|
|
91
|
+
helpers = load_repo_monitor_helpers()
|
|
92
|
+
monkeypatch.delenv("AUTOMATION_MODEL", raising=False)
|
|
93
|
+
monkeypatch.setitem(
|
|
94
|
+
helpers, "_fetch_settings", lambda url, key: {"agent_settings": {"llm": ACTIVE_LLM}}
|
|
95
|
+
)
|
|
96
|
+
calls: list = []
|
|
97
|
+
|
|
98
|
+
def fake_urlopen(req, *args, **kwargs):
|
|
99
|
+
calls.append(req.full_url)
|
|
100
|
+
raise AssertionError("no profile request expected")
|
|
101
|
+
|
|
102
|
+
_patch_urlopen(monkeypatch, helpers, fake_urlopen)
|
|
103
|
+
|
|
104
|
+
agent = helpers["_get_agent_dict"]("http://agent:8000", "key")
|
|
105
|
+
|
|
106
|
+
assert agent["llm"] == ACTIVE_LLM
|
|
107
|
+
assert calls == []
|