Skip to content

feat(providers): add custom profile registry#1170

Open
johntmyers wants to merge 8 commits intomainfrom
feat/1081-provider-profile-registry/johntmyers
Open

feat(providers): add custom profile registry#1170
johntmyers wants to merge 8 commits intomainfrom
feat/1081-provider-profile-registry/johntmyers

Conversation

@johntmyers
Copy link
Copy Markdown
Collaborator

@johntmyers johntmyers commented May 5, 2026

Summary

Adds a custom provider profile registry on top of the built-in YAML profile catalog so profiles can be exported, linted, imported, listed, retrieved, deleted, and used when creating provider records incrementally.

Closes #1081

UX Changes

  • openshell provider list-profiles now supports -o table|yaml|json; table remains the default for browsing.
  • New openshell provider profile export <id> -o yaml|json; YAML is the default authoring/export format.
  • New openshell provider profile import -f <file> for single-profile import.
  • New openshell provider profile import --from <directory> for non-recursive bulk import of *.yaml, *.yml, and *.json profile files.
  • New openshell provider profile lint -f <file> and openshell provider profile lint --from <directory> to validate profile files without registering them.
  • New openshell provider profile delete <id> for deleting registered custom profiles.
  • openshell provider create --type <id> now accepts imported custom provider profile ids in addition to built-in provider types.
  • Custom profile ids must be canonical lowercase kebab-case using only a-z, 0-9, and -; import/lint rejects whitespace, uppercase, underscores, empty segments, and leading/trailing dashes.
  • Custom profile ids cannot reuse legacy provider type ids or aliases such as generic, github, or gh; those ids remain reserved for provider records.
  • Built-in profiles remain read-only: imports cannot overwrite them, and delete returns a clear error.
  • Deleting a custom profile fails while any sandbox attaches a provider whose Provider.type matches that profile id.
  • Import/lint reports an aggregated diagnostic summary instead of failing one issue at a time.
  • Existing provider runtime credential injection is unchanged by this PR.

UX Example

Export the built-in GitHub profile as YAML:

openshell provider profile export github -o yaml > github-readonly.yaml

Built-in profiles and legacy provider type ids are reserved, so the edited profile uses a new custom lowercase kebab-case id. The profile YAML can also use the full network policy endpoint shape, including L7 allow/deny rules:

id: github-readonly
display_name: GitHub Read-only
description: GitHub API with restricted repo access
category: source_control
credentials:
  - name: api_token
    description: GitHub token
    env_vars: [GITHUB_TOKEN, GH_TOKEN]
    required: true
    auth_style: bearer
    header_name: authorization
endpoints:
  - host: api.github.com
    port: 443
    protocol: rest
    enforcement: enforce
    rules:
      - allow:
          method: GET
          path: /repos/**
    deny_rules:
      - method: POST
        path: /repos/**
  - host: github.com
    port: 443
    protocol: rest
    access: read-only
    enforcement: enforce
binaries:
  - /usr/bin/gh
  - /usr/local/bin/gh
  - /usr/bin/git
  - /usr/local/bin/git

Lint and import the custom profile:

openshell provider profile lint -f github-readonly.yaml
openshell provider profile import -f github-readonly.yaml
openshell provider list-profiles

Notional list-profiles output after import:

Available Provider Profiles:

  AGENT
    claude       Claude Code                                endpoints: 1 
    codex        Codex CLI                                  endpoints: 2 
    opencode     OpenCode                                   endpoints: 1 

  INFERENCE
    anthropic    Anthropic                                  endpoints: 1  inference
    nvidia       NVIDIA NIM                                 endpoints: 2  inference
    openai       OpenAI                                     endpoints: 1  inference

  SOURCE CONTROL
    github       GitHub                                     endpoints: 2 
    github-readonly GitHub Read-only                        endpoints: 2 
    gitlab       GitLab                                     endpoints: 1 

Create a provider record that points at the imported profile id:

openshell provider create \
  --name work-github \
  --type github-readonly \
  --credential GITHUB_TOKEN

Enable provider-profile policy composition at the gateway:

openshell settings set --global --key providers_v2_enabled --value true --yes

Launching a sandbox with work-github composes the profile policy layer JIT:

openshell sandbox create --name gh-demo --provider work-github -- bash
openshell sandbox get gh-demo --policy-only

The effective policy includes the sandbox's base policy plus a generated provider layer. Relevant composed network-policy fragment:

network_policies:
  _provider_work_github:
    name: _provider_work_github
    endpoints:
      - host: api.github.com
        port: 443
        protocol: rest
        enforcement: enforce
        rules:
          - allow:
              method: GET
              path: /repos/**
        deny_rules:
          - method: POST
            path: /repos/**
      - host: github.com
        port: 443
        protocol: rest
        access: read-only
        enforcement: enforce
    binaries:
      - path: /usr/bin/gh
      - path: /usr/local/bin/gh
      - path: /usr/bin/git
      - path: /usr/local/bin/git

Changes

  • Added provider profile import/lint/delete gRPC APIs and StoredProviderProfile object metadata support.
  • Stores custom profiles in the existing objects table with object_type = "provider_profile"; no new tables or migrations.
  • Merges built-in and custom profiles in list/get, while rejecting built-in overwrite/delete.
  • Enforces canonical lowercase kebab-case profile ids and normalizes get/delete/provider-use lookup paths through the same helper.
  • Rejects custom profile ids that collide with legacy provider type ids or aliases, closing the generic no-built-in-profile fallback path.
  • Blocks custom profile deletion when any sandbox attaches a provider whose Provider.type matches the profile id.
  • Adds CLI UX for openshell provider list-profiles -o table|yaml|json and openshell provider profile export|import|lint|delete.
  • Allows openshell provider create --type <id> to create provider records backed by imported custom profiles.
  • Adds YAML/JSON profile DTO round-tripping plus aggregated semantic validation diagnostics.
  • Extends provider v2 JIT policy composition to resolve custom profile layers from the registry.
  • Published docs/* updates remain out of scope for this issue.

Testing

  • RUSTC_WRAPPER= cargo check -p openshell-cli -p openshell-server -p openshell-providers
  • RUSTC_WRAPPER= cargo test -p openshell-providers profile
  • RUSTC_WRAPPER= cargo test -p openshell-providers profile_id
  • RUSTC_WRAPPER= cargo test -p openshell-server grpc::provider::tests::
  • RUSTC_WRAPPER= cargo test -p openshell-server provider_profile
  • RUSTC_WRAPPER= cargo test -p openshell-server provider_policy_layers_include_custom_provider_profiles
  • RUSTC_WRAPPER= cargo test -p openshell-server import_provider_profile_rejects_legacy_provider_type_ids
  • RUSTC_WRAPPER= cargo test -p openshell-server import_provider_profile_rejects_noncanonical_ids
  • RUSTC_WRAPPER= cargo test -p openshell-server provider_profile_get_and_delete_normalize_request_ids
  • RUSTC_WRAPPER= cargo test -p openshell-server provider_policy_layers_skip_custom_profile_for_legacy_provider_type
  • RUSTC_WRAPPER= cargo test -p openshell-server provider_policy_layers_normalize_custom_provider_type_ids
  • RUSTC_WRAPPER= cargo test -p openshell-cli provider_create_accepts_custom_profile_type_ids
  • RUSTC_WRAPPER= cargo test -p openshell-cli provider_profile_cli_run_functions_support_custom_profiles
  • RUSTC_WRAPPER= cargo test -p openshell-cli provider_
  • mise run pre-commit

Checklist

  • Tests added/updated
  • Published docs intentionally deferred
  • No new provider-profile storage table added

@johntmyers johntmyers added the test:e2e Requires end-to-end coverage label May 5, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Label test:e2e applied for b551b1f. Open the existing run and click Re-run all jobs to execute with the label set. The E2E Gate check on this PR will flip green automatically once the run finishes.

@johntmyers johntmyers force-pushed the feat/1081-provider-profile-registry/johntmyers branch from fd335cd to 71e7a17 Compare May 6, 2026 16:56
Comment thread crates/openshell-cli/tests/provider_commands_integration.rs
@zredlined zredlined self-requested a review May 6, 2026 23:51
Copy link
Copy Markdown
Collaborator

@drew drew left a comment

Choose a reason for hiding this comment

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

Looks good. Mostly focused on the CLI contracts.

Comment on lines +636 to +651
#[derive(Clone, Debug, ValueEnum)]
enum ProviderProfileOutput {
Table,
Yaml,
Json,
}

impl ProviderProfileOutput {
fn as_str(&self) -> &'static str {
match self {
Self::Table => "table",
Self::Yaml => "yaml",
Self::Json => "json",
}
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is probably useful as a common type for all cli entities

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:e2e Requires end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support custom provider profile import and export

4 participants