Skip to content

Commit d67cea2

Browse files
yaozheng-fangclaude
andcommitted
feat(invoke harness): default --protocol to run_sse, mint random session
run_sse is now the default transport for `agentkit invoke harness`; pass --protocol invoke for the /harness/invoke path. When --session-id is unset the run_sse path mints a random s-<id> (creating it is idempotent). Existing invoke-path tests pin --protocol invoke via a _run_invoke helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ad9f7e8 commit d67cea2

2 files changed

Lines changed: 65 additions & 22 deletions

File tree

agentkit/toolkit/cli/cli_invoke.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -667,10 +667,10 @@ def _harness_run_sse(
667667
) -> Any:
668668
"""Invoke a deployed harness via the ADK ``/run_sse`` endpoint (streaming).
669669
670-
app_name is the fixed ``"harness"``; user_id is a freshly generated random id
671-
(a temporary placeholder until real identity wiring); session_id is the
672-
caller's. When ``overrides`` is non-empty it is sent as the ``harness`` field
673-
so the runtime streams a spawned (overridden) agent; otherwise the base agent.
670+
app_name is the fixed ``"harness"``; user_id is the JWT ``sub`` when the token
671+
is an OIDC id_token, else a random id; session_id is the caller's. When
672+
``overrides`` is non-empty it is sent as the ``harness`` field so the runtime
673+
streams a spawned (overridden) agent; otherwise the base agent.
674674
"""
675675
import requests
676676

@@ -780,7 +780,9 @@ def harness_command(
780780
"agentkit_user", "--user-id", help="user_id for the run."
781781
),
782782
session_id: str = typer.Option(
783-
"agentkit_sample_session", "--session-id", help="session_id for the run."
783+
None,
784+
"--session-id",
785+
help="session_id for the run (run_sse: random if unset).",
784786
),
785787
max_llm_calls: int = typer.Option(
786788
None,
@@ -818,9 +820,9 @@ def harness_command(
818820
False, "--raw", help="Print the raw response (InvokeHarnessResponse / SSE)."
819821
),
820822
protocol: str = typer.Option(
821-
"invoke",
823+
"run_sse",
822824
"--protocol",
823-
help="Transport: 'invoke' (POST /harness/invoke) or 'run_sse' (ADK /run_sse).",
825+
help="Transport: 'run_sse' (ADK /run_sse, default) or 'invoke' (POST /harness/invoke).",
824826
),
825827
) -> Any:
826828
"""Invoke a deployed harness by name (resolved via the harness.json registry).
@@ -908,13 +910,15 @@ def harness_command(
908910
base_url=base_url,
909911
token=token,
910912
prompt=message,
911-
session_id=session_id,
913+
# No session given → mint a random one; creating it is idempotent.
914+
session_id=session_id or f"s-{uuid.uuid4().hex[:12]}",
912915
overrides=build_harness_overrides(
913916
system_prompt, model_name, tools, skills, runtime
914917
),
915918
raw=raw,
916919
)
917920

921+
session_id = session_id or "agentkit_sample_session"
918922
invoke_url = base_url + "/harness/invoke"
919923
req_headers = {"Content-Type": "application/json"}
920924
if token:

tests/toolkit/cli/test_cli_invoke_harness.py

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ def _run_harness(args):
4444
return runner.invoke(app, ["invoke", "harness", *args])
4545

4646

47+
def _run_invoke(args):
48+
"""Run the harness subcommand pinned to the ``invoke`` transport.
49+
50+
``--protocol`` now defaults to ``run_sse``; these tests exercise the
51+
``/harness/invoke`` path, so they request it explicitly.
52+
"""
53+
return _run_harness([*args, "--protocol", "invoke"])
54+
55+
4756
class _FakeResponse:
4857
def __init__(self, payload, status_code=200):
4958
self._payload = payload
@@ -96,13 +105,13 @@ def test_build_harness_overrides_empty_when_unset():
96105

97106
def test_unknown_harness_fails(tmp_path):
98107
_write_registry(tmp_path, {"other": {"url": "https://x", "key": "k"}})
99-
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
108+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path)])
100109
assert result.exit_code == 1
101110
assert "not found in registry" in result.output
102111

103112

104113
def test_no_registry_fails(tmp_path):
105-
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
114+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path)])
106115
assert result.exit_code == 1
107116
assert "not found in registry" in result.output
108117

@@ -122,7 +131,7 @@ def test_harness_invoke_posts_correct_request(tmp_path, monkeypatch):
122131
"output": "PINEAPPLE",
123132
})
124133

125-
result = _run_harness(
134+
result = _run_invoke(
126135
[
127136
"first",
128137
"What should you reply?",
@@ -155,7 +164,7 @@ def test_harness_invoke_no_overrides_omits_harness_key(tmp_path, monkeypatch):
155164
captured = {}
156165
_patch_post(monkeypatch, captured)
157166

158-
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
167+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path)])
159168

160169
assert result.exit_code == 0, result.output
161170
assert "harness" not in captured["json"]
@@ -176,7 +185,7 @@ def test_harness_error_field_is_surfaced(tmp_path, monkeypatch):
176185
},
177186
)
178187

179-
result = _run_harness(
188+
result = _run_invoke(
180189
["first", "hi", "--directory", str(tmp_path), "--tools", "bogus"]
181190
)
182191
assert result.exit_code == 1
@@ -189,7 +198,7 @@ def test_harness_invoke_http_error_fails(tmp_path, monkeypatch):
189198
captured = {}
190199
_patch_post(monkeypatch, captured, payload={"detail": "boom"}, status_code=500)
191200

192-
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
201+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path)])
193202
assert result.exit_code == 1
194203
assert "HTTP 500" in result.output
195204

@@ -199,7 +208,7 @@ def test_apikey_overrides_registry_key(tmp_path, monkeypatch):
199208
captured = {}
200209
_patch_post(monkeypatch, captured)
201210

202-
result = _run_harness(
211+
result = _run_invoke(
203212
["first", "hi", "--directory", str(tmp_path), "--apikey", "jwt-token"]
204213
)
205214
assert result.exit_code == 0, result.output
@@ -282,7 +291,7 @@ def test_harness_invoke_uses_login_id_token_as_bearer(monkeypatch, tmp_path):
282291
_write_registry(tmp_path, {"first": {"url": "https://h.example.com"}}) # no static key
283292
captured = {}
284293
_patch_post(monkeypatch, captured)
285-
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
294+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path)])
286295
assert result.exit_code == 0, result.output
287296
assert captured["headers"].get("Authorization") == f"Bearer {tok}"
288297

@@ -292,7 +301,7 @@ def test_harness_invoke_apikey_overrides_login_id_token(monkeypatch, tmp_path):
292301
_write_registry(tmp_path, {"first": {"url": "https://h.example.com"}})
293302
captured = {}
294303
_patch_post(monkeypatch, captured)
295-
result = _run_harness(["first", "hi", "--directory", str(tmp_path), "--apikey", "explicit-key"])
304+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path), "--apikey", "explicit-key"])
296305
assert result.exit_code == 0, result.output
297306
assert captured["headers"].get("Authorization") == "Bearer explicit-key"
298307

@@ -317,7 +326,7 @@ def fake_post(url, json=None, headers=None, timeout=None):
317326
return _FakeResponse({"output": "ok"}, 401 if len(calls) == 1 else 200)
318327

319328
monkeypatch.setattr("requests.post", fake_post)
320-
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
329+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path)])
321330
assert result.exit_code == 0, result.output
322331
assert calls == [f"Bearer {tok1}", f"Bearer {tok2}"] # one refresh + one retry
323332

@@ -334,7 +343,7 @@ def boom(self, rt):
334343
raise AuthError("token endpoint rejected the request")
335344

336345
monkeypatch.setattr(sess_mod.OAuthClient, "refresh", boom)
337-
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
346+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path)])
338347
assert result.exit_code == 1
339348
assert "login" in result.output.lower()
340349

@@ -346,7 +355,7 @@ def test_harness_invoke_keyauth_uses_key_even_when_logged_in(monkeypatch, tmp_pa
346355
_write_registry(tmp_path, {"first": {"url": "https://h.example.com", "key": "ak", "runtime_id": "r-1"}})
347356
captured = {}
348357
_patch_post(monkeypatch, captured)
349-
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
358+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path)])
350359
assert result.exit_code == 0, result.output
351360
assert captured["headers"].get("Authorization") == "Bearer ak"
352361

@@ -360,7 +369,7 @@ def test_harness_invoke_custom_jwt_entry_uses_login_id_token(monkeypatch, tmp_pa
360369
}})
361370
captured = {}
362371
_patch_post(monkeypatch, captured)
363-
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
372+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path)])
364373
assert result.exit_code == 0, result.output
365374
assert captured["headers"].get("Authorization") == f"Bearer {tok}"
366375

@@ -382,7 +391,7 @@ def fake_post(url, json=None, headers=None, timeout=None):
382391
return _FakeResponse({"detail": "denied"}, 401)
383392

384393
monkeypatch.setattr("requests.post", fake_post)
385-
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
394+
result = _run_invoke(["first", "hi", "--directory", str(tmp_path)])
386395
assert result.exit_code == 1
387396
assert len(calls) == 2 # original + exactly one refresh-retry, no third
388397
assert "HTTP 401" in result.output
@@ -544,6 +553,36 @@ def test_user_id_from_token_helper():
544553
assert cli_invoke._user_id_from_token("") is None
545554

546555

556+
def test_default_protocol_is_run_sse_with_random_session(tmp_path, monkeypatch):
557+
"""No --protocol and no --session-id → run_sse with a freshly minted session."""
558+
_write_registry(tmp_path, {"first": {"url": "https://x", "key": "ak"}})
559+
calls = []
560+
sse = ['data: {"content":{"parts":[{"text":"OK"}]},"partial":true}']
561+
562+
class _SSEResp:
563+
status_code = 200
564+
text = ""
565+
566+
def iter_lines(self, decode_unicode=False):
567+
return iter(sse)
568+
569+
def fake_post(url, json=None, headers=None, timeout=None, stream=False):
570+
calls.append({"url": url, "json": json})
571+
return _SSEResp() if url.endswith("/run_sse") else _FakeResponse({}, 200)
572+
573+
monkeypatch.setattr("requests.post", fake_post)
574+
575+
result = _run_harness(["first", "hi", "--directory", str(tmp_path)])
576+
assert result.exit_code == 0, result.output
577+
# Default transport is run_sse (not /harness/invoke).
578+
run_call = next(c for c in calls if c["url"].endswith("/run_sse"))
579+
sid = run_call["json"]["session_id"]
580+
assert sid.startswith("s-") # random, not the invoke-path default
581+
# The session is created (idempotently) before the run, under that id.
582+
sess_call = next(c for c in calls if "/sessions/" in c["url"])
583+
assert sess_call["url"].endswith(f"/sessions/{sid}")
584+
585+
547586
def test_harness_invalid_protocol_fails(tmp_path):
548587
_write_registry(tmp_path, {"first": {"url": "https://x", "key": "ak"}})
549588
result = _run_harness(

0 commit comments

Comments
 (0)