Files
dev/sdk/python/tests/test_client_rpc_methods.py
zeekayandHanzo Dev 6cbbea2d03 merge: just-every/code v0.6.169 (e4fe007150) — keep Hanzo identity
Our history is just-every's with author emails rewritten, so the graphs are
disjoint and git finds no merge base. Supplied one by hand: d1f1f51a41, the
just-every commit matching our Oct-2025 fork point (22 files apart). 9618
upstream commits, 7004 files, +1.81M/-159k, 37 conflicts.

Resolution:
- Upstream code where our side was the Oct-2025 prototype of a feature they
  have since reimplemented 3-5x larger (agent_runs, browser_sessions,
  history_cell/{agent,browser}, the vt100 harness and its snapshots).
- Upstream's config.rs module split (builder/defaults/sources/validation)
  kept; zen5-coder and the provider-follows-model rule re-applied on top.
- Ours where the file IS the identity: README, package.json, postinstall,
  release notes pipeline, integration-zed.
- Hybrid where upstream improved a file we had branded: updates.rs (their
  release-repo migration, our hanzoai/dev), release.yml (their fail-fast npm
  auth check, our package name), chatwidget.rs (their turn handling, our
  HANZO_USER_KEY hint), exec human output (their 237 lines, our banner).

Superseded and dropped deliberately: our resume_command_name() coder-alias
hack — upstream's derive_resume_command_name() reads the invocation basename
generically, which yields "dev" for us.

LLM.md now records the real topology and the sync point; AGENTS.md and
CLAUDE.md are symlinks to it again. Single parent on purpose: adopting
jecode/main as a second parent would re-import 13,742 commits carrying the
upstream author emails a prior rewrite deliberately removed.

Version 0.2.188 -> 0.2.189.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 23:09:12 -07:00

219 lines
7.3 KiB
Python

from __future__ import annotations
from pathlib import Path
from typing import Any
from codex_app_server.client import AppServerClient, _params_dict
from codex_app_server.generated.v2_all import (
ApprovalsReviewer,
GetAccountResponse,
PlanType,
ReasoningEffort,
ReasoningEffortOption,
ThreadForkParams,
ThreadListParams,
ThreadResumeResponse,
ThreadStartParams,
ThreadTokenUsageUpdatedNotification,
)
from codex_app_server.models import UnknownNotification
ROOT = Path(__file__).resolve().parents[1]
def test_thread_set_name_and_compact_use_current_rpc_methods() -> None:
client = AppServerClient()
calls: list[tuple[str, dict[str, Any] | None]] = []
def fake_request(method: str, params, *, response_model): # type: ignore[no-untyped-def]
calls.append((method, params))
return response_model.model_validate({})
client.request = fake_request # type: ignore[method-assign]
client.thread_set_name("thread-1", "sdk-name")
client.thread_compact("thread-1")
assert calls[0][0] == "thread/name/set"
assert calls[1][0] == "thread/compact/start"
def test_generated_params_models_are_snake_case_and_dump_by_alias() -> None:
params = ThreadListParams(search_term="needle", limit=5)
assert "search_term" in ThreadListParams.model_fields
dumped = _params_dict(params)
assert dumped == {"searchTerm": "needle", "limit": 5}
def test_generated_v2_bundle_has_single_shared_plan_type_definition() -> None:
source = (ROOT / "src" / "codex_app_server" / "generated" / "v2_all.py").read_text()
assert source.count("class PlanType(") == 1
def test_plan_type_accepts_business_prolite_from_newer_runtime() -> None:
"""New runtime plan values should remain typed when using a codex_bin override."""
plan_type = "self_serve_business_prolite"
response = GetAccountResponse.model_validate(
{
"account": {
"type": "chatgpt",
"email": "[email protected]",
"planType": plan_type,
},
"requiresOpenaiAuth": True,
}
)
assert response.account is not None
assert response.account.root.plan_type.value == plan_type
client = CodexClient()
account_updated = client._coerce_notification(
"account/updated",
{"authMode": "chatgpt", "planType": plan_type},
)
assert isinstance(account_updated.payload, AccountUpdatedNotification)
assert account_updated.payload.plan_type == PlanType(plan_type)
rate_limits_updated = client._coerce_notification(
"account/rateLimits/updated",
{"rateLimits": {"planType": plan_type}},
)
assert isinstance(rate_limits_updated.payload, AccountRateLimitsUpdatedNotification)
assert rate_limits_updated.payload.rate_limits.plan_type == PlanType(plan_type)
def test_reasoning_effort_preserves_enum_constants_and_accepts_future_values() -> None:
"""Known effort members and new runtime values should share the enum-style API."""
known_option = ReasoningEffortOption.model_validate(
{"description": "Balanced", "reasoningEffort": "medium"}
)
future_option = ReasoningEffortOption.model_validate(
{"description": "Future", "reasoningEffort": "ultra"}
)
turn_params = TurnStartParams(
thread_id="thread-1",
input=[],
effort=ReasoningEffort.medium,
)
assert {
"known_member": ReasoningEffort.medium.value,
"known_option": known_option.reasoning_effort.value,
"future_option": future_option.reasoning_effort.value,
"turn_effort": _params_dict(turn_params)["effort"],
} == {
"known_member": "medium",
"known_option": "medium",
"future_option": "ultra",
"turn_effort": "medium",
}
def test_thread_source_preserves_enum_constants_and_accepts_future_values() -> None:
"""Known thread sources and new runtime values should share the enum-style API."""
start_params = ThreadStartParams(thread_source=ThreadSource.user)
fork_params = ThreadForkParams(
thread_id="thread-1",
thread_source=ThreadSource("future_source"),
)
assert {
"known_member": ThreadSource.user.value,
"subagent_member": ThreadSource.subagent.value,
"memory_member": ThreadSource.memory_consolidation.value,
"start_source": _params_dict(start_params)["threadSource"],
"fork_source": _params_dict(fork_params)["threadSource"],
} == {
"known_member": "user",
"subagent_member": "subagent",
"memory_member": "memory_consolidation",
"start_source": "user",
"fork_source": "future_source",
}
def test_thread_resume_response_accepts_auto_review_reviewer() -> None:
response = ThreadResumeResponse.model_validate(
{
"approvalPolicy": "on-request",
"approvalsReviewer": "auto_review",
"cwd": "/tmp",
"model": "gpt-5",
"modelProvider": "openai",
"sandbox": {"type": "dangerFullAccess"},
"thread": {
"cliVersion": "1.0.0",
"createdAt": 1,
"cwd": "/tmp",
"ephemeral": False,
"id": "thread-1",
"modelProvider": "openai",
"preview": "",
"source": "cli",
"status": {"type": "idle"},
"turns": [],
"updatedAt": 1,
},
}
)
assert response.approvals_reviewer is ApprovalsReviewer.auto_review
def test_notifications_are_typed_with_canonical_v2_methods() -> None:
client = AppServerClient()
event = client._coerce_notification(
"thread/tokenUsage/updated",
{
"threadId": "thread-1",
"turnId": "turn-1",
"tokenUsage": {
"last": {
"cachedInputTokens": 0,
"inputTokens": 1,
"outputTokens": 2,
"reasoningOutputTokens": 0,
"totalTokens": 3,
},
"total": {
"cachedInputTokens": 0,
"inputTokens": 1,
"outputTokens": 2,
"reasoningOutputTokens": 0,
"totalTokens": 3,
},
},
},
)
assert event.method == "thread/tokenUsage/updated"
assert isinstance(event.payload, ThreadTokenUsageUpdatedNotification)
assert event.payload.turn_id == "turn-1"
def test_unknown_notifications_fall_back_to_unknown_payloads() -> None:
client = AppServerClient()
event = client._coerce_notification(
"unknown/notification",
{
"id": "evt-1",
"conversationId": "thread-1",
"msg": {"type": "turn_aborted"},
},
)
assert event.method == "unknown/notification"
assert isinstance(event.payload, UnknownNotification)
assert event.payload.params["msg"] == {"type": "turn_aborted"}
def test_invalid_notification_payload_falls_back_to_unknown() -> None:
client = AppServerClient()
event = client._coerce_notification(
"thread/tokenUsage/updated", {"threadId": "missing"}
)
assert event.method == "thread/tokenUsage/updated"
assert isinstance(event.payload, UnknownNotification)