refactor(cli): host guardrail, intake and experiments in their owning packages - #2001
Draft
maxdubrinsky wants to merge 15 commits into
Draft
refactor(cli): host guardrail, intake and experiments in their owning packages#2001maxdubrinsky wants to merge 15 commits into
maxdubrinsky wants to merge 15 commits into
Conversation
Adds what hand-written CLI commands need from nemo_platform_plugin: - InferenceGatewayClient (provider/model/openai proxy routes, provider_ready, raw SSE streams, OpenAI model listing) with endpoint and wire tests. - client_from_platform accepts a NemoClient or AsyncNemoClient and derives the typed client with from_client, so callers no longer need to know which platform handle they hold; PlatformClient is the runtime-checkable structural type for that parameter and platform_default_headers reads identity headers off either shape. - models.refs holds the pure model-reference helpers; packages/models re-exports them so the CLI does not import the Stainless-bound package. - filesets.transfer holds SDK-free upload/download/list/delete; FilesResource delegates to it and filesets no longer imports .resources eagerly. - Bearer tokens are resolved on every HTTP attempt rather than baked into the PreparedRequest, so retries and later pages never replay a stale token. - A 409 on a create sent with exist_ok is no longer retried before send() resolves it by fetching the existing entity. - Query-param TypedDicts and request models for files, iam, virtual models and workspaces gain the fields the CLI exposes; GuardrailConfig.data is optional to match the server entity. Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
…ands Additive plumbing so command groups can be rewritten on the typed clients one PR at a time while the generated commands keep working: - client/bootstrap.py resolves config, OIDC discovery and token providers without the generated SDK and builds NemoClient/AsyncNemoClient with the same retry policy, TLS verification and 5 s connect cap the SDK used; factory.py layers the NeMoPlatform constructors on top of it. - CLIContext.typed_client(XClient) / async_typed_client derive a service client that shares the platform client's transport and auth; get_workspace exposes the configured default. - pagination gains collect_offset_pages / collect_cursor_pages for typed paginated responses, carrying the server's envelope fields (sort, filter, grouped_by) into JSON output; fetch_all_pages stays for generated commands. - errors maps typed-client and pydantic errors alongside the SDK ones (validation and unknown --input-data keys exit 2), and recognises every unresolved-workspace message. - stdin_utils.build_request_body validates --input-data into a request model and rejects unknown keys instead of dropping them. - code_generator renders typed-client snippets (request models as constructor calls, SecretStr masked, RootModel positional); generated commands are routed to legacy_code_generator until they are replaced. - waiters accept either platform handle, poll with a monotonic clock and normalise str-enum statuses; formatters unwrap NemoResponse; the version flag reads distribution metadata; command groups no longer advertise shell completion (the root app owns it). Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
…p calls it The new test endpoint stubs raise NotImplementedError like their siblings instead of an ellipsis body, which ty rejects as an implicit None return. The auth-idp CLI refresh contract test patches discover_nmp_config on client.bootstrap, where the CLI now resolves OIDC settings. Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
AllPagesResponse and OffsetPageResponse emit the server envelope fields, but callers that build them without an envelope (fetch_all_pages behind the generated list commands, and nemo jobs list --all-pages) lost the sort key that list output has always carried. sort is now always present, null when the server did not echo one, and a real envelope keeps its own field order. Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
- Narrow the platform handle from object to PlatformClient in client_from_platform and platform_default_headers (adapter.py) and the nemo.sdk resource-factory owner in NemoPluginSDKResources (sdk.py), so the typed-client boundary stops erasing the type where plugins are built. - build_request_body reports each field's accepted input alias in the unknown-input hint (schema, not the internal schema_) so the advertised key is one pydantic actually accepts. - with_options now clears a clone's cached nemo.sdk plugin resources, which were built against the original client's transport, so typed clients accessed on the clone bind to its headers/retry/timeout. - Drop the always-false _PLATFORM_JOB_LIFECYCLE watch exclusions from the legacy code generator's lifecycle helpers. Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
Brings in the auth-off-Stainless SDK removal, customization typed-client migration, and sandbox forward merges. Resolves conflicts in adapter.py and client.py by combining both sides: keep main's _owns_http lifecycle (owns_http_client=False) and _platform_default_headers header resolution, and keep the PlatformClient narrowing plus the with_options cached-resource clearing added for review. Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
This was referenced Sep 11, 2026
The main merge kept the typed resource factories on PlatformClient but took main's __all__ which no longer re-exports NeMoPlatform/AsyncNeMoPlatform, so the import became dead and tripped the ruff pre-commit hook (lint-python-style). Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
Plugins (nemo-evaluator among others) import the generated NeMoPlatform / AsyncNeMoPlatform classes from nemo_platform_plugin.sdk. My earlier drop of that import broke them with an ImportError. Re-export lazily through a module __getattr__ so plugin discovery still imports this module without requiring the generated SDK at load time. Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
Brings in guardrails typed-client (#1966) and other main changes. Resolved guardrail/types.py to main's landed GuardrailConfig.data shape (RailsConfig, typed) per the layered-merge rule, and took main's add/add tests/guardrail/test_endpoints.py. Also fixes the sdk.py re-export regression in the same push. Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
maxdubrinsky
force-pushed
the
cli-plugin-hosted-groups/mdubrinsky
branch
from
September 11, 2026 18:37
27a5e57 to
7681b85
Compare
Contributor
maxdubrinsky
changed the base branch from
cli-typed-client-plumbing/mdubrinsky
to
main
September 11, 2026 18:50
Contributor
|
…bing-merge Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com> # Conflicts: # packages/nemo_platform_ext/src/nemo_platform_ext/cli/core/errors.py
maxdubrinsky
force-pushed
the
cli-plugin-hosted-groups/mdubrinsky
branch
from
September 11, 2026 20:15
7681b85 to
9fc5dc7
Compare
…bing-merge Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
maxdubrinsky
force-pushed
the
cli-plugin-hosted-groups/mdubrinsky
branch
from
September 11, 2026 20:37
9fc5dc7 to
d2d2bf3
Compare
…bing-merge Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
…bing-merge Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com> # Conflicts: # packages/nemo_platform_plugin/tests/client/test_adapter.py
maxdubrinsky
force-pushed
the
cli-plugin-hosted-groups/mdubrinsky
branch
from
September 11, 2026 21:04
d2d2bf3 to
fb5d8ef
Compare
…bing-merge Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
… packages The three functional groups move out of the generated command tree and become nemo.cli entry points on the packages that own the services: guardrail in nemo-guardrails-plugin (GuardrailCLI), intake and experiments in nmp-intake (IntakeCLI, ExperimentsCLI). They appear only when that package is installed, the same way every other plugin group does, and are written on GuardrailClient and IntakeClient via state.typed_client(). Command names, flags, defaults and columns are unchanged; the group help line becomes the standard "Plugin commands for <name>." row. The intake typed client gains the read and experiment endpoints the CLI needs. The nmp-intake bundle inherits nemo.* entry points so the nemo-platform wheel actually carries the groups (the generated entry-point table is regenerated with make vendor), and a new test asserts every bundled package's nemo.* entry points are exposed by the wrapper or SDK pyproject so a missing inherit fails in CI rather than in the shipped wheel. The generator skips the three resources. GuardrailConfig.data is Optional again after #1966 typed it as a bare RailsConfig: the guardrails entity stores it as Optional and the API returns "data": null for configs created without a body, which the CLI integration tests here create. The middleware already handled None. Rebased over #1965, which added its own experiment endpoints and read models: main's ExperimentCreateRequest/UpdateRequest/Response (dict-typed pareto and column_layout) and RetrieveTraceQueryParams are used; the endpoint stubs keep the create exist_ok / list / delete variants the CLI needs, and span groups go back through collect_offset_pages now that list_span_groups returns Paginated[SpanGroup] on main. Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
maxdubrinsky
force-pushed
the
cli-plugin-hosted-groups/mdubrinsky
branch
from
September 11, 2026 21:49
fb5d8ef to
633afbf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Moves the
guardrail,intakeandexperimentscommand groups out of the generated tree into the packages that own the APIs (plugins/nemo-guardrailsandservices/intake), exposed to thenemoCLI throughnemo.clientry points. Command surface is unchanged except the group help line, which becomes "Plugin commands for ." like every other plugin-hosted group.Changes
plugins/nemo-guardrails/.../cli.py(GuardrailCLI): configs CRUD andcheckonGuardrailsClient;nemo.clientry point in the plugin'spyproject.toml.services/intake/.../cli_commands/{intake,experiments}.py: telemetry upload/query, span groups, OTLP logs and the Experiments leaderboard commands onIntakeClient. Rebased over refactor(insights): migrate plugin off stainless clients #1964: main'sAnnotation/SpanGroupsPageshapes are used;--all-pagesfor span groups pages manually (_collect_span_group_pages); filter params accept the CLI's JSON string as well as a dict; ATIF TypedDicts keepextra="allow"so custom top-level ATIF keys still reach the server (a test covers this).packages/nemo_platform/pyproject.toml: thenmp-intakebundle inheritsnemo.*entry points,make vendorregenerates the wrapper'snemo.clitable;tools/nemo-platform-sdk-tools/tests/sdk/vendor/test_wrapper_entry_points.pyasserts every bundled package'snemo.*entry points reach the wrapper.manifest_registry.py:PLUGIN_GROUPSincludes guardrail;RUNTIME_COMMANDSextended.cli_config.yamlskip: true;cli/commands/api/{guardrail,intake,experiments}deleted.ExperimentCreateRequest/UpdateRequest/Response(dict-typedpareto/column_layout) andRetrieveTraceQueryParamsare used; the endpoint stubs keep thecreate_experiment(exist_ok=...),list_experimentsanddelete_experimentvariants the CLI needs; span groups go back throughcollect_offset_pagesnow thatlist_span_groupsreturnsPaginated[SpanGroup]on main.GuardrailConfig.dataisRailsConfig | Noneagain: refactor(guardrails): migrate plugin to typed clients #1966 typed it as a bareRailsConfig, but the guardrails entity stores it as Optional and the API returns"data": nullfor configs created without a body, which the CLI integration tests here create (the middleware already handledNone). Pinned bytests/guardrail/test_types.py::test_guardrail_config_accepts_null_data.docs/cli/reference.mdxregenerated (three help-line changes).Stack
Part of the AIRCORE-893 series that moves the
nemoCLI off the generated Stainless SDK onto the typed clients innemo_platform_plugin(blocking AIRCORE-827). Stacked on #1986 (plumbing) but targeted atmainso CI runs (theCIworkflow only triggers for PRs againstmain/release/*). Until #1986 merges, the diff includes its commits; this PR's own change isrefactor(cli): host guardrail, intake and experiments in their owning packages. Review that commit, or compare againstcli-typed-client-plumbing/mdubrinsky.get_client()'s Stainless path, deletecli/commands/api/*and the CLI generatorStatus: draft. Being maintained by an agent on behalf of @mdubrinsky; it re-bases on #1986 as that moves and on @ironcommit's landing plugin migrations (#1971, #1973), and picks up CI and review feedback. It will be re-cut where those PRs overlap (noted per PR below).
Type of Change
Quality Gates
Verification
Signed-off-by:traileruv run pre-commit run -apasses, or any blocked checks are identified belowTargeted validation (on the current head, based on #1986 at
377fb4d0a):uv run --frozen pytest packages/nemo_platform_ext/tests packages/nemo_platform_plugin/tests services/intake/tests/cli plugins/nemo-guardrails/tests tools/nemo-platform-sdk-tools/tests -q --deselect packages/nemo_platform_ext/tests/cli/commands/test_services.py --deselect packages/nemo_platform_ext/tests/local/test_services.py(the two deselected files bind ports 8080/9000, which are occupied on the host): 5522 passed (ext, plugin, intake CLI, guardrails, experimentalist, sdk-tools).make vendorandmake generate-cli-reference-docsleave no diff (thelint-sdk-vendoredCI gate).uv run ruff checkanduv run ruff format --check: clean.uv run pre-commit run -a: all hooks pass exceptuv-lock(host uv 0.9.30 vs pinned 0.9.14);uvx --from uv==0.9.14 uv lock --checkpasses.helm-docsandyqare not installed on the host; both hooks are CI-covered.NMP_CONFIG_FILE=/tmp/nope.yaml uv run _nemo <group> --helpdiffed againstmainfor every rewritten group: identical, except a trailing period on group help lines.