Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,16 @@ positional IDs for scripts.
judgment projects list

# Traces
judgment traces search --pagination '{"limit":25,"cursorSortValue":null,"cursorItemId":null}'
judgment traces search <PROJECT_ID> --pagination '{"limit":25,"cursorSortValue":null,"cursorItemId":null}'
judgment traces search --pagination '{"limit":25,"cursor":null}'
judgment traces search <PROJECT_ID> --pagination '{"limit":25,"cursor":null}'
judgment traces get <PROJECT_ID> <TRACE_ID>
judgment traces spans <PROJECT_ID> <TRACE_ID>
judgment traces tags <PROJECT_ID> <TRACE_ID>
judgment traces behaviors <PROJECT_ID> <TRACE_ID>
judgment traces span <PROJECT_ID> --spans '[{"trace_id":"...","span_id":"..."}]'

# Sessions
judgment sessions search <PROJECT_ID> --pagination '{"limit":25,"cursorSortValue":null,"cursorItemId":null}'
judgment sessions search <PROJECT_ID> --pagination '{"limit":25,"cursor":null}'
judgment sessions get <PROJECT_ID> <SESSION_ID>
judgment sessions trace-ids <PROJECT_ID> <SESSION_ID>
judgment sessions trace-behaviors <PROJECT_ID> <SESSION_ID>
Expand Down
15 changes: 6 additions & 9 deletions src/judgment_cli/generated/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,10 @@ def agent_threads_get(ctx, _args, output_format, organization_id_option, organiz
@click.option("--owner-user-id", "owner_user_id", default=None, help='Restrict project history to a specific thread owner.')
@click.option("--all-users", "all_users", default=None, type=bool, help='Deprecated alias for scope=project. Prefer the `scope` query parameter.')
@click.option("--limit", "limit", default=None, type=float, help='Maximum number of threads to return (1–100).')
@click.option("--cursor-updated-at", "cursor_updated_at", default=None, help='Pagination cursor: `updated_at` value from a previous `next_cursor`.')
@click.option("--cursor-thread-id", "cursor_thread_id", default=None, help='Pagination cursor: `thread_id` value from a previous `next_cursor`.')
@click.option("--cursor", "cursor", default=None, help='Opaque pagination cursor string from a previous `next_cursor`. Omit for the first page.')
@click.option("-o", "--output", "output_format", type=click.Choice(["table", "yaml", "json"]), default="table", help="Output format.")
@click.pass_context
def agent_threads_list(ctx, _args, output_format, organization_id_option, organization_name_option, project_id_option, project_name_option, agent_type, agent_name, judge_id, agent_config_id, scope, owner_user_id, all_users, limit, cursor_updated_at, cursor_thread_id):
def agent_threads_list(ctx, _args, output_format, organization_id_option, organization_name_option, project_id_option, project_name_option, agent_type, agent_name, judge_id, agent_config_id, scope, owner_user_id, all_users, limit, cursor):
"List agent thread conversations.\n\n\x08\nList the authenticated user's agent thread conversations in a project (global_copilot or custom_agent). Returns each thread's title, type, message count, active run status, and timestamps."
_parsed = _parse_contextual_positionals(
_args,
Expand Down Expand Up @@ -115,10 +114,8 @@ def agent_threads_list(ctx, _args, output_format, organization_id_option, organi
body["all_users"] = all_users
if limit is not None:
body["limit"] = limit
if cursor_updated_at is not None:
body["cursor_updated_at"] = cursor_updated_at
if cursor_thread_id is not None:
body["cursor_thread_id"] = cursor_thread_id
if cursor is not None:
body["cursor"] = cursor
result = _api.agent_threads_list(ctx.obj["client"], body)
_table_output(result, output_format=output_format)

Expand Down Expand Up @@ -1552,7 +1549,7 @@ def sessions_get(ctx, _args, output_format, organization_id_option, organization
@click.argument("_args", nargs=-1, metavar='[[ORG_ID] PROJECT_ID]')
@click.option("--filters", "filters", required=True, help='Filter expressions, ANDed together. Each item is `{"field":<field>,"op":<op>,"value":<value>}`. Allowed ops depend on the field\'s type.\n\n**Op groups:**\n- `STRING_OPS` = `=` | `!=` | `contains` | `does_not_contain`\n- `NUMERIC_OPS` = `=` | `!=` | `<` | `<=` | `>` | `>=`\n- `ARRAY_ANY` = `any` (matches when the row\'s array overlaps the supplied values)\n\n**String fields** (op in STRING_OPS, value is a string): `session_id`.\n\n**Numeric fields** (op in NUMERIC_OPS, value is a number): `trace_count`, `latency` (nanoseconds), `total_cost` (USD).\n\n**Array fields** (op = `any`, value is an array): `behaviors` (behavior UUIDs).')
@click.option("--time-range", "time_range", default=None, help='`{"start_time":<iso8601-string>|null,"end_time":<iso8601-string>|null}`. Either bound may be null to leave that side open. Invalid timestamps return 400.')
@click.option("--pagination", "pagination", required=True, help='`{"limit":<int 1-200>,"cursorSortValue":<string>|null,"cursorItemId":<string>|null}`.\n\nFirst page: pass null for both cursor fields. Each response returns `nextCursor:{sort_value,session_id}` (or null when `hasMore=false`); copy those into `cursorSortValue` and `cursorItemId` for the next page.')
@click.option("--pagination", "pagination", required=True, help='`{"limit":<int 1-200>,"cursor":<string>|null}`.\n\nFirst page: pass null or omit cursor. Each response returns `nextCursor` as an opaque string (or null when `hasMore=false`); pass that string as `cursor` for the next page.')
@click.option("--sort-by", "sort_by", default=None, help='`{"field":<sort_field>,"direction":"asc"|"desc"}` where `sort_field` is one of: `created_at`, `num_traces`, `latency`, `llm_cost`. Default when omitted: `{"field":"created_at","direction":"desc"}`.')
@click.option("-o", "--output", "output_format", type=click.Choice(["table", "yaml", "json"]), default="table", help="Output format.")
@click.pass_context
Expand Down Expand Up @@ -1840,7 +1837,7 @@ def traces_get(ctx, _args, output_format, organization_id_option, organization_n
@click.option("--filters", "filters", default=None, help='Filter expressions, ANDed together. Each item is `{"field":<field>,"op":<op>,"value":<value>}`. Allowed ops depend on the field\'s type.\n\n**Op groups:**\n- `STRING_OPS` = `=` | `!=` | `contains` | `does_not_contain` | `exists` | `is_absent`\n- `NUMERIC_OPS` = `=` | `!=` | `<` | `<=` | `>` | `>=`\n- `ARRAY_ANY` = `any` (matches when the row\'s array overlaps the supplied values)\n\n**String fields** (op in STRING_OPS, value is a string): `span_name`, `customer_id`, `customer_user_id`, `session_id`, `error`, `dataset_id`.\n\n**Numeric fields** (op in NUMERIC_OPS, value is a number): `duration` (nanoseconds), `llm_cost` (USD).\n\n**Array fields** (op = `any`, value is an array): `tags` (strings), `rules_invoked` (rule names from this project, strings), `behaviors` (behavior UUIDs).\n\n**Special:**\n- `full_text_search`: op = `contains`, value is a string searched across span attribute text.\n- `span_attributes_roots`: matches a single span attribute key/value: `{"field":"span_attributes_roots","key":"<attribute-name>","op":<STRING_OPS>,"value":"<string>"}`')
@click.option("--sort-by", "sort_by", default=None, help='`{"field":<sort_field>,"direction":"asc"|"desc"}` where `sort_field` is one of: `created_at`, `span_name`, `duration`, `llm_cost`. Default when omitted: `{"field":"created_at","direction":"desc"}`. Any sort other than `created_at` desc requires `time_range.start_time` and a window between `start_time` and `end_time` of at most 7 days; use `created_at` desc sorting for broader ranges.')
@click.option("--time-range", "time_range", default=None, help='`{"start_time":<iso8601-string>|null,"end_time":<iso8601-string>|null}`. Either bound may be null to leave that side open. Invalid timestamps return 400. For any sort other than `created_at` desc, `start_time` is required and the window between `start_time` and `end_time` must be at most 7 days.')
@click.option("--pagination", "pagination", required=True, help='`{"limit":<int 1-200>,"cursorSortValue":<string>|null,"cursorItemId":<string>|null}`.\n\nFirst page: pass null for both cursor fields. Each response returns `nextCursor:{sort_value,trace_id}` (or null when `hasMore=false`); copy those into `cursorSortValue` and `cursorItemId` for the next page.')
@click.option("--pagination", "pagination", required=True, help='`{"limit":<int 1-200>,"cursor":<string>|null}`.\n\nFirst page: pass null or omit cursor. Each response returns `nextCursor` as an opaque string (or null when `hasMore=false`); pass that string as `cursor` for the next page.')
@click.option("-o", "--output", "output_format", type=click.Choice(["table", "yaml", "json"]), default="table", help="Output format.")
@click.pass_context
def traces_search(ctx, _args, output_format, organization_id_option, organization_name_option, project_id_option, project_name_option, filters, sort_by, time_range, pagination):
Expand Down
17 changes: 4 additions & 13 deletions src/judgment_cli/generated/types.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Response cursor types for sessions/traces search not updated to match request migration

The AgentThreadsListResponse.next_cursor was updated from a structured type (AgentThreadsListResponseNextCursorOption1 with updated_at/thread_id) to str | None at src/judgment_cli/generated/types.py:364, consistent with the request body change. However, SessionsSearchResponse.nextCursor at line 1862 still uses structured SessionsSearchResponseNextcursorOption1 (with sort_value/session_id), and TracesSearchResponse.nextCursor at line 2047 still uses structured TracesSearchResponseNextcursorOption1 (with sort_value/trace_id). Meanwhile, their request body pagination types (SessionsSearchBodyPagination.cursor at line 1902 and TracesSearchBodyPagination.cursor at line 2146) were both changed to str | None. Since all these files are auto-generated from the OpenAPI spec, this asymmetry likely reflects the actual API state (perhaps sessions/traces endpoints still return structured cursors while accepting a string in the request). Worth confirming with the API team that the spec is accurate and the sessions/traces response cursor types don't need the same migration.

(Refers to lines 1854-1862)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -359,14 +359,9 @@ class AgentThreadsListResponseThreadsItem(AgentThreadsListResponseThreadsItemOpt
updated_at: str


class AgentThreadsListResponseNextCursorOption1(TypedDict):
updated_at: str
thread_id: str


class AgentThreadsListResponse(TypedDict):
threads: list[AgentThreadsListResponseThreadsItem]
next_cursor: AgentThreadsListResponseNextCursorOption1 | None
next_cursor: str | None


class AgentThreadsListBodyOptional(TypedDict, total=False):
Expand All @@ -376,8 +371,7 @@ class AgentThreadsListBodyOptional(TypedDict, total=False):
owner_user_id: str
all_users: bool
limit: float
cursor_updated_at: str
cursor_thread_id: str
cursor: str


class AgentThreadsListBody(AgentThreadsListBodyOptional):
Expand Down Expand Up @@ -1905,8 +1899,7 @@ class SessionsSearchBodyTimeRange(TypedDict):

class SessionsSearchBodyPagination(TypedDict):
limit: float
cursorSortValue: str | None
cursorItemId: str | None
cursor: str | None


class SessionsSearchBodySortBy(TypedDict):
Expand Down Expand Up @@ -2150,8 +2143,7 @@ class TracesSearchBodyTimeRangeOption1(TypedDict):

class TracesSearchBodyPagination(TypedDict):
limit: float
cursorSortValue: str | None
cursorItemId: str | None
cursor: str | None


class TracesSearchBodyOptional(TypedDict, total=False):
Expand Down Expand Up @@ -2272,4 +2264,3 @@ class TracesTagsBody(TypedDict):
project_id: str
trace_id: str


8 changes: 2 additions & 6 deletions tests/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,7 @@ def test_generated_command_uses_env_org_and_positional_project(
config, "credentials_path", lambda: tmp_path / "credentials.json"
)
monkeypatch.setenv("JUDGMENT_ORG_ID", "org-env")
pagination = json.dumps(
{"limit": 25, "cursorSortValue": None, "cursorItemId": None}
)
pagination = json.dumps({"limit": 25, "cursor": None})

result = CliRunner().invoke(
cli,
Expand Down Expand Up @@ -220,9 +218,7 @@ def test_generated_command_uses_saved_context(monkeypatch, tmp_path: Path) -> No
project_id="project-saved",
)
)
pagination = json.dumps(
{"limit": 25, "cursorSortValue": None, "cursorItemId": None}
)
pagination = json.dumps({"limit": 25, "cursor": None})

result = CliRunner().invoke(
cli,
Expand Down
20 changes: 5 additions & 15 deletions tests/test_generated_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,7 @@ def project_id() -> str:
if not candidates:
pytest.skip("No projects in this organization have any traces.")

pagination = json.dumps(
{"limit": 1, "cursorSortValue": None, "cursorItemId": None}
)
pagination = json.dumps({"limit": 1, "cursor": None})
for project in candidates:
pid = project.get("project_id") or project.get("id")
assert pid
Expand Down Expand Up @@ -142,9 +140,7 @@ def project_id() -> str:

@pytest.fixture(scope="session")
def trace_id(project_id: str) -> str:
pagination = json.dumps(
{"limit": 1, "cursorSortValue": None, "cursorItemId": None}
)
pagination = json.dumps({"limit": 1, "cursor": None})
payload = _run("traces", "search", project_id, "--pagination", pagination, "-o", "json")
assert isinstance(payload, dict)
traces = payload.get("data") or []
Expand All @@ -171,9 +167,7 @@ def span_pair(project_id: str, trace_id: str) -> tuple[str, str]:

@pytest.fixture(scope="session")
def session_id(project_id: str) -> str:
pagination = json.dumps(
{"limit": 1, "cursorSortValue": None, "cursorItemId": None}
)
pagination = json.dumps({"limit": 1, "cursor": None})
payload = _run(
"sessions",
"search",
Expand Down Expand Up @@ -616,9 +610,7 @@ def test_projects_create_and_favorite():

def test_sessions_search(project_id: str):
"""Covers ``sessions search``."""
pagination = json.dumps(
{"limit": 5, "cursorSortValue": None, "cursorItemId": None}
)
pagination = json.dumps({"limit": 5, "cursor": None})
payload = _run(
"sessions",
"search",
Expand Down Expand Up @@ -658,9 +650,7 @@ def test_sessions_trace_behaviors(project_id: str, session_id: str):

def test_traces_search(project_id: str):
"""Covers ``traces search``."""
pagination = json.dumps(
{"limit": 5, "cursorSortValue": None, "cursorItemId": None}
)
pagination = json.dumps({"limit": 5, "cursor": None})
payload = _run(
"traces", "search", project_id, "--pagination", pagination, "-o", "json"
)
Expand Down
Loading