"""Bounded, display-safe provider command inventories. Provider discovery returns filesystem paths and other provider-owned metadata. This module keeps that material on AgentsServer and projects only the small allowlist needed by clients. A client selects an opaque id plus the inventory revision; it never sends a path or provider command name back to the server. """ from __future__ import annotations import hashlib import hmac import json import os import re import stat import time import unicodedata from dataclasses import dataclass from pathlib import Path from typing import Any, Iterable MAX_PROVIDER_COMMANDS = 512 MAX_PROVIDER_COMMAND_NAME_CHARS = 128 MAX_PROVIDER_COMMAND_LABEL_CHARS = 160 MAX_PROVIDER_COMMAND_DESCRIPTION_CHARS = 800 MAX_PROVIDER_COMMAND_PATH_CHARS = 4096 # OpenCode skills are discovered directly from its documented local defaults. # These ceilings make opening the slash palette a bounded filesystem read even # when a workspace contains a deliberately hostile directory tree. MAX_OPENCODE_SKILL_ANCESTORS = 64 MAX_OPENCODE_SKILL_ROOTS = 3 * MAX_OPENCODE_SKILL_ANCESTORS + 3 MAX_OPENCODE_SKILL_SCAN_DEPTH = 8 MAX_OPENCODE_SKILL_SCAN_DIRS = 4096 MAX_OPENCODE_SKILL_SCAN_ENTRIES = 16384 MAX_OPENCODE_SKILL_FILE_BYTES = 1024 * 1024 MAX_OPENCODE_SKILL_TOTAL_BYTES = 16 * 1024 * 1024 MAX_OPENCODE_SKILL_FRONTMATTER_LINES = 256 OPENCODE_SKILL_SCAN_TIMEOUT_SECONDS = 2.0 _COMMAND_NAME_RE = re.compile( rf"[A-Za-z0-9_][A-Za-z0-9_.:-]{{0,{MAX_PROVIDER_COMMAND_NAME_CHARS - 1}}}" ) _OPENCODE_SKILL_NAME_RE = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*") _FRONTMATTER_KEY_RE = re.compile(r"([A-Za-z][A-Za-z0-9_-]*):(?:[ \t]*(.*))?") _LOCATION_RE = re.compile( # Provider metadata is optional display copy, so redact the entire suffix # after a URL/path marker. Local paths may contain spaces and punctuation; # trying to guess their endpoint can expose the unredacted tail. r"(?P[A-Za-z][A-Za-z0-9+.-]*://[\s\S]*)" r"|(?P(? list[dict[str, Any]]: return [dict(record.public) for record in self.records] def resolve(self, command_id: str) -> ProviderCommandRecord | None: for record in self.records: if record.public["id"] == command_id: return record return None class ProviderCommandDiscoveryError(RuntimeError): """A bounded local provider inventory could not be read safely.""" @dataclass class _OpenCodeSkillScanBudget: deadline: float directories: int = 0 entries: int = 0 files: int = 0 total_bytes: int = 0 def check(self) -> None: if time.monotonic() > self.deadline: raise ProviderCommandDiscoveryError( "OpenCode skill discovery exceeded its time limit" ) def count_directory(self) -> None: self.check() self.directories += 1 if self.directories > MAX_OPENCODE_SKILL_SCAN_DIRS: raise ProviderCommandDiscoveryError( "OpenCode skill discovery exceeded its directory limit" ) def count_file(self, size: int) -> None: self.check() self.files += 1 if self.files > MAX_PROVIDER_COMMANDS: raise ProviderCommandDiscoveryError( "OpenCode skill discovery exceeded its file limit" ) if size < 0 or size > MAX_OPENCODE_SKILL_FILE_BYTES: raise ProviderCommandDiscoveryError( "an OpenCode skill exceeds the per-file byte limit" ) self.total_bytes += size if self.total_bytes > MAX_OPENCODE_SKILL_TOTAL_BYTES: raise ProviderCommandDiscoveryError( "OpenCode skill discovery exceeded its aggregate byte limit" ) def count_entry(self) -> None: self.check() self.entries += 1 if self.entries > MAX_OPENCODE_SKILL_SCAN_ENTRIES: raise ProviderCommandDiscoveryError( "OpenCode skill discovery exceeded its entry limit" ) def canonical_provider_command_name(value: Any) -> str | None: if not isinstance(value, str): return None candidate = unicodedata.normalize("NFC", value) if candidate != value or candidate != candidate.strip(): return None if _COMMAND_NAME_RE.fullmatch(candidate) is None: return None return candidate def _replace_local_path(match: re.Match[str]) -> str: url = match.group("url") if url is not None: # Even HTTP(S) user info, hosts, paths and query parameters can contain # credentials or personally identifying local infrastructure data. return "" return "" def sanitize_provider_command_text(value: Any, max_chars: int) -> str: if not isinstance(value, str): return "" normalized = unicodedata.normalize("NFC", value) safe = "".join( character for character in normalized if character not in _BIDI_CONTROLS and ( character in {"\n", "\r", "\t"} or unicodedata.category(character) not in _UNSAFE_CATEGORIES ) ) safe = _EMAIL_RE.sub("", safe) safe = _LOCATION_RE.sub(_replace_local_path, safe) safe = " ".join(safe.split()) if len(safe) <= max_chars: return safe return safe[: max(0, max_chars - 1)].rstrip() + "…" def _plain_frontmatter_scalar(value: str) -> str | None: """Parse the conservative scalar subset used by the local skill index. OpenCode uses a complete YAML parser. AgentsServer intentionally accepts a smaller, deterministic subset for the two display fields it needs rather than adding executable tags, aliases, or another deployment dependency. Files using richer YAML remain usable in OpenCode itself but are omitted from this bounded palette. """ candidate = value.strip() if not candidate: return "" if candidate.startswith('"'): try: decoded = json.loads(candidate) except (TypeError, json.JSONDecodeError): return None return decoded if isinstance(decoded, str) else None if candidate.startswith("'"): if len(candidate) < 2 or not candidate.endswith("'"): return None return candidate[1:-1].replace("''", "'") if candidate[0] in "[{&*!|>": return None # A hash starts a YAML comment only when separated from the scalar. candidate = re.split(r"[ \t]+#", candidate, maxsplit=1)[0].rstrip() if re.search(r":[ \t]", candidate): return None if candidate.lower() in {"null", "true", "false", "~"}: return None return candidate def _opencode_skill_frontmatter(data: bytes) -> tuple[str, str, str] | None: try: text = data.decode("utf-8", errors="strict") except UnicodeDecodeError: return None if text.startswith("\ufeff"): return None lines = text.splitlines() if not lines or lines[0].strip() != "---": return None closing = None for index, line in enumerate( lines[1 : MAX_OPENCODE_SKILL_FRONTMATTER_LINES + 1], start=1, ): if line.strip() == "---": closing = index break if closing is None: return None values: dict[str, str] = {} body_lines = lines[1:closing] index = 0 while index < len(body_lines): line = body_lines[index] index += 1 if not line.strip() or line.lstrip().startswith("#"): continue # Nested metadata and other unknown structured fields are irrelevant. # A recognized field must be a top-level, single-line scalar. if line[:1].isspace(): continue match = _FRONTMATTER_KEY_RE.fullmatch(line) if match is None: continue key, raw_value = match.groups() if key not in {"name", "description"}: continue if key in values or raw_value is None: return None if key == "description" and raw_value.strip() in { "|", "|-", "|+", ">", ">-", ">+", }: block: list[str] = [] while index < len(body_lines): block_line = body_lines[index] if block_line and not block_line[:1].isspace(): break index += 1 block.append(block_line) nonblank = [value for value in block if value.strip()] if not nonblank or any(value.startswith("\t") for value in nonblank): return None indentation = min(len(value) - len(value.lstrip(" ")) for value in nonblank) if indentation <= 0: return None value = "\n".join( item[indentation:] if item.strip() else "" for item in block ).strip("\n") values[key] = value continue value = _plain_frontmatter_scalar(raw_value) if value is None: return None values[key] = value name = values.get("name", "") description = values.get("description", "") if ( not 1 <= len(name) <= 64 or _OPENCODE_SKILL_NAME_RE.fullmatch(name) is None or not 1 <= len(description) <= 1024 ): return None # OpenCode's native Skill loader passes gray-matter's trimmed Markdown # content to the model, not the YAML frontmatter itself. content = "\n".join(lines[closing + 1:]).strip() return name, description, content def _path_has_symlink(path: Path) -> bool: """Return true when any existing component is a symbolic link.""" absolute = Path(os.path.abspath(os.path.normpath(str(path)))) parts = absolute.parts if not parts: return True current = Path(parts[0]) for part in parts[1:]: current /= part try: if stat.S_ISLNK(current.lstat().st_mode): return True except OSError: return True return False def _read_regular_file_without_symlinks( path: Path, budget: _OpenCodeSkillScanBudget, ) -> bytes: if _path_has_symlink(path): raise ProviderCommandDiscoveryError( "an OpenCode skill path contains a symbolic link" ) flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) try: descriptor = os.open(path, flags) except OSError as exc: raise ProviderCommandDiscoveryError( "an OpenCode skill could not be opened safely" ) from exc try: before = os.fstat(descriptor) if not stat.S_ISREG(before.st_mode): raise ProviderCommandDiscoveryError( "an OpenCode skill is not a regular file" ) budget.count_file(before.st_size) chunks: list[bytes] = [] remaining = before.st_size while remaining: budget.check() chunk = os.read(descriptor, min(64 * 1024, remaining)) if not chunk: raise ProviderCommandDiscoveryError( "an OpenCode skill changed while being read" ) chunks.append(chunk) remaining -= len(chunk) data = b"".join(chunks) after = os.fstat(descriptor) if ( before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns, ) != ( after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns, ): raise ProviderCommandDiscoveryError( "an OpenCode skill changed while being read" ) return data finally: os.close(descriptor) def _bounded_skill_files( root: Path, budget: _OpenCodeSkillScanBudget, ) -> list[Path]: if _path_has_symlink(root): return [] try: root_stat = root.lstat() except FileNotFoundError: return [] except OSError as exc: raise ProviderCommandDiscoveryError( "an OpenCode skill root could not be inspected" ) from exc if not stat.S_ISDIR(root_stat.st_mode): return [] matches: list[Path] = [] pending: list[tuple[Path, int]] = [(root, 0)] while pending: directory, depth = pending.pop() budget.count_directory() try: with os.scandir(directory) as iterator: entries = [] for entry in iterator: budget.count_entry() entries.append(entry) entries.sort(key=lambda item: item.name) except OSError as exc: raise ProviderCommandDiscoveryError( "an OpenCode skill directory could not be read" ) from exc child_dirs: list[Path] = [] for entry in entries: budget.check() try: entry_stat = entry.stat(follow_symlinks=False) except OSError as exc: raise ProviderCommandDiscoveryError( "an OpenCode skill entry could not be inspected" ) from exc if stat.S_ISLNK(entry_stat.st_mode): continue candidate = directory / entry.name if stat.S_ISDIR(entry_stat.st_mode): if depth < MAX_OPENCODE_SKILL_SCAN_DEPTH: child_dirs.append(candidate) continue if stat.S_ISREG(entry_stat.st_mode) and entry.name == "SKILL.md": matches.append(candidate) # Stack reversal preserves lexical traversal while remaining iterative. pending.extend((child, depth + 1) for child in reversed(child_dirs)) return matches def _opencode_project_ancestors(cwd: Path) -> list[Path]: """Walk nearest-first through the git worktree, or only cwd outside git.""" ancestors: list[Path] = [] current = cwd worktree_index: int | None = None for _ in range(MAX_OPENCODE_SKILL_ANCESTORS): ancestors.append(current) git_marker = current / ".git" try: marker_mode = git_marker.lstat().st_mode except FileNotFoundError: marker_mode = 0 except OSError as exc: raise ProviderCommandDiscoveryError( "the OpenCode worktree boundary could not be inspected" ) from exc if marker_mode and not stat.S_ISLNK(marker_mode): worktree_index = len(ancestors) - 1 break parent = current.parent if parent == current: break current = parent else: raise ProviderCommandDiscoveryError( "the OpenCode worktree exceeds the ancestor limit" ) return ancestors[: worktree_index + 1] if worktree_index is not None else [cwd] def opencode_provider_skill_inventory( *, cwd: str, home: str, xdg_config_home: str | None, selector_secret: str, binding_context: str, limit: int = MAX_PROVIDER_COMMANDS, timeout_seconds: float = OPENCODE_SKILL_SCAN_TIMEOUT_SECONDS, ) -> ProviderCommandInventory: """Index the documented default local OpenCode skill roots only. This deliberately does not evaluate OpenCode configuration or start its CLI. Configured paths/URLs, plugins, commands, agents, MCP prompts, the undocumented ``~/.opencode`` compatibility root, and built-in skills are outside this side-effect-free v1 contract. """ try: canonical_cwd = Path( os.path.abspath(os.path.normpath(os.path.expanduser(cwd))) ).resolve(strict=True) canonical_home = Path( os.path.abspath(os.path.normpath(os.path.expanduser(home))) ).resolve(strict=True) except (OSError, RuntimeError, ValueError) as exc: raise ProviderCommandDiscoveryError( "OpenCode skill discovery boundaries are unavailable" ) from exc if not canonical_cwd.is_dir() or not canonical_home.is_dir(): raise ProviderCommandDiscoveryError( "OpenCode skill discovery boundaries are not directories" ) if xdg_config_home: expanded_xdg = Path(os.path.expanduser(xdg_config_home)) config_home = ( expanded_xdg if expanded_xdg.is_absolute() else canonical_home / ".config" ) else: config_home = canonical_home / ".config" try: if config_home.exists(): config_home = config_home.resolve(strict=True) except (OSError, RuntimeError) as exc: raise ProviderCommandDiscoveryError( "the OpenCode configuration boundary could not be resolved" ) from exc project_roots: list[tuple[Path, str]] = [] for ancestor in _opencode_project_ancestors(canonical_cwd): project_roots.extend(( (ancestor / ".opencode" / "skills", "project"), (ancestor / ".claude" / "skills", "project"), (ancestor / ".agents" / "skills", "project"), )) roots = [ *project_roots, (config_home / "opencode" / "skills", "user"), (canonical_home / ".claude" / "skills", "user"), (canonical_home / ".agents" / "skills", "user"), ] if len(roots) > MAX_OPENCODE_SKILL_ROOTS: raise ProviderCommandDiscoveryError( "OpenCode skill discovery exceeded its root limit" ) budget = _OpenCodeSkillScanBudget( deadline=time.monotonic() + max(0.01, float(timeout_seconds)) ) discovered: dict[str, list[tuple[Path, bytes, str, str]]] = {} seen_paths: set[str] = set() for root, scope in roots: budget.check() for path in _bounded_skill_files(root, budget): canonical_path = os.path.abspath(os.path.normpath(str(path))) if len(canonical_path) > MAX_PROVIDER_COMMAND_PATH_CHARS: raise ProviderCommandDiscoveryError( "an OpenCode skill path exceeds the path limit" ) if canonical_path in seen_paths: continue seen_paths.add(canonical_path) data = _read_regular_file_without_symlinks(path, budget) parsed = _opencode_skill_frontmatter(data) if parsed is None: continue name, description, _content = parsed if path.parent.name != name: continue discovered.setdefault(name, []).append((path, data, scope, description)) bounded_limit = max(1, min(int(limit), MAX_PROVIDER_COMMANDS)) unique = [ (name, entries[0]) for name, entries in sorted(discovered.items()) if len(entries) == 1 ] if len(unique) > bounded_limit: raise ProviderCommandDiscoveryError( "OpenCode skill discovery exceeded its inventory limit" ) records: list[ProviderCommandRecord] = [] for name, (path, data, scope, description) in unique: canonical_path = os.path.abspath(os.path.normpath(str(path))) digest = hashlib.sha256(data).hexdigest() native_identity = json.dumps( [canonical_path, digest], ensure_ascii=False, separators=(",", ":"), ) command_id = _opaque_command_id( selector_secret, binding_context, "opencode", str(canonical_cwd), name, native_identity, ) safe_description = sanitize_provider_command_text( description, MAX_PROVIDER_COMMAND_DESCRIPTION_CHARS, ) records.append(ProviderCommandRecord( public={ "id": command_id, "name": name, "label": name, "description": safe_description, "scope": scope, "source": "opencode", "kind": "skill", "invocation": f"/{name}", }, native={ "name": name, "path": canonical_path, "directory": os.path.dirname(canonical_path), "content_sha256": digest, }, )) return _inventory( selector_secret, binding_context, "opencode", str(canonical_cwd), records, truncated=False, ) def validate_opencode_provider_skill_record( record: ProviderCommandRecord, ) -> tuple[str, str, str]: """Revalidate one private skill identity immediately before launch.""" name = str(record.native.get("name") or "") raw_path = str(record.native.get("path") or "") expected_directory = str(record.native.get("directory") or "") expected_digest = str(record.native.get("content_sha256") or "") if ( record.public.get("source") != "opencode" or record.public.get("kind") != "skill" or record.public.get("name") != name or record.public.get("invocation") != f"/{name}" or _OPENCODE_SKILL_NAME_RE.fullmatch(name) is None or not raw_path or not os.path.isabs(raw_path) or len(raw_path) > MAX_PROVIDER_COMMAND_PATH_CHARS or not expected_directory or not hmac.compare_digest( expected_digest, expected_digest.lower(), ) or re.fullmatch(r"[0-9a-f]{64}", expected_digest) is None ): raise ProviderCommandDiscoveryError( "the selected OpenCode skill identity is invalid" ) path = Path(os.path.abspath(os.path.normpath(raw_path))) if _path_has_symlink(path): raise ProviderCommandDiscoveryError( "the selected OpenCode skill path is no longer safe" ) budget = _OpenCodeSkillScanBudget( deadline=time.monotonic() + OPENCODE_SKILL_SCAN_TIMEOUT_SECONDS ) data = _read_regular_file_without_symlinks(path, budget) parsed = _opencode_skill_frontmatter(data) canonical_directory = os.path.dirname(str(path)) if ( parsed is None or parsed[0] != name or path.parent.name != name or canonical_directory != expected_directory or not hmac.compare_digest( hashlib.sha256(data).hexdigest(), expected_digest, ) ): raise ProviderCommandDiscoveryError( "the selected OpenCode skill changed before launch" ) return name, canonical_directory, parsed[2] def _opaque_command_id( selector_secret: str, binding_context: str, backend: str, cwd: str, name: str, native_identity: str, ) -> str: payload = json.dumps( [ "provider-command-v1", binding_context, backend, cwd, name, native_identity, ], ensure_ascii=False, separators=(",", ":"), ).encode("utf-8") return "pcmd_" + hmac.new( selector_secret.encode("utf-8"), payload, hashlib.sha256, ).hexdigest()[:32] def _inventory_revision( selector_secret: str, binding_context: str, backend: str, cwd: str, records: Iterable[ProviderCommandRecord], *, truncated: bool, ) -> str: payload = { "version": 1, "binding_context": binding_context, "backend": backend, "cwd": cwd, "truncated": truncated, # Provider order is presentation-only and may change across otherwise # identical cold connects. A semantic inventory revision must not # invalidate a queued selection solely because that order changed. "commands": sorted( (record.public for record in records), key=lambda item: str(item.get("id") or ""), ), } encoded = json.dumps( payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), ).encode("utf-8") return "pcmdrev_" + hmac.new( selector_secret.encode("utf-8"), encoded, hashlib.sha256, ).hexdigest()[:32] def _inventory( selector_secret: str, binding_context: str, backend: str, cwd: str, records: list[ProviderCommandRecord], *, truncated: bool, ) -> ProviderCommandInventory: frozen = tuple(records) return ProviderCommandInventory( backend=backend, revision=_inventory_revision( selector_secret, binding_context, backend, cwd, frozen, truncated=truncated, ), records=frozen, truncated=truncated, ) def codex_provider_command_inventory( value: Any, *, cwd: str, selector_secret: str, binding_context: str, limit: int = MAX_PROVIDER_COMMANDS, ) -> ProviderCommandInventory: """Project one cwd entry from Codex ``skills/list``. The provider-resolved SKILL.md path remains only in ``record.native`` and is never part of ``record.public``. """ raw_entries = value.get("data") if isinstance(value, dict) else None matching: dict[str, Any] | None = None if isinstance(raw_entries, list): normalized_cwd = str(Path(cwd).resolve()) for entry in raw_entries[:32]: if not isinstance(entry, dict): continue raw_cwd = entry.get("cwd") if not isinstance(raw_cwd, str): continue try: candidate_cwd = str(Path(raw_cwd).resolve()) except (OSError, RuntimeError): continue if candidate_cwd == normalized_cwd: matching = entry break raw_skills = matching.get("skills") if isinstance(matching, dict) else None if not isinstance(raw_skills, list): raw_skills = [] bounded_limit = max(1, min(int(limit), MAX_PROVIDER_COMMANDS)) records: list[ProviderCommandRecord] = [] seen_ids: set[str] = set() scanned = 0 for raw in raw_skills: if scanned >= bounded_limit: break scanned += 1 if not isinstance(raw, dict) or raw.get("enabled") is False: continue name = canonical_provider_command_name(raw.get("name")) raw_path = raw.get("path") if ( name is None or not isinstance(raw_path, str) or not raw_path or len(raw_path) > MAX_PROVIDER_COMMAND_PATH_CHARS or "\x00" in raw_path or not Path(raw_path).is_absolute() ): continue interface = raw.get("interface") if not isinstance(interface, dict): interface = {} label = sanitize_provider_command_text( interface.get("displayName") or name, MAX_PROVIDER_COMMAND_LABEL_CHARS, ) or name description = sanitize_provider_command_text( interface.get("shortDescription") or raw.get("description"), MAX_PROVIDER_COMMAND_DESCRIPTION_CHARS, ) raw_scope = str(raw.get("scope") or "").strip().lower() scope = raw_scope if raw_scope in {"project", "user", "admin", "system"} else None command_id = _opaque_command_id( selector_secret, binding_context, "codex", cwd, name, raw_path, ) if command_id in seen_ids: continue seen_ids.add(command_id) public = { "id": command_id, "name": name, "label": label, "description": description, "scope": scope, "source": "plugin" if raw.get("pluginId") else "codex", "kind": "skill", # AgentsDock presents one slash-command palette for every provider. # The server converts this slash token into Codex's native text + # structured skill input only after validating the opaque id. "invocation": f"/{name}", } records.append( ProviderCommandRecord( public=public, native={"name": name, "path": raw_path}, ) ) return _inventory( selector_secret, binding_context, "codex", cwd, records, truncated=len(raw_skills) > bounded_limit, ) def claude_provider_command_inventory( value: Any, *, cwd: str, selector_secret: str, binding_context: str, control_generation: str | None = None, limit: int = MAX_PROVIDER_COMMANDS, ) -> ProviderCommandInventory: """Project only Claude's provider-reported command metadata. ``get_server_info`` also contains account data. Callers should already project it inside the SDK actor, and this second allowlist prevents an accidental raw top-level return from reaching the API. """ raw_commands = value.get("commands") if isinstance(value, dict) else None if not isinstance(raw_commands, list): raw_commands = [] bounded_limit = max(1, min(int(limit), MAX_PROVIDER_COMMANDS)) records: list[ProviderCommandRecord] = [] seen_names: set[str] = set() scanned = 0 for raw in raw_commands: if scanned >= bounded_limit: break scanned += 1 if not isinstance(raw, dict): continue name = canonical_provider_command_name(raw.get("name")) if name is None or name in seen_names: continue seen_names.add(name) description = sanitize_provider_command_text( raw.get("description"), MAX_PROVIDER_COMMAND_DESCRIPTION_CHARS, ) argument_hint = sanitize_provider_command_text( raw.get("argumentHint"), MAX_PROVIDER_COMMAND_LABEL_CHARS, ) if argument_hint: description = ( f"{description} {argument_hint}".strip() )[:MAX_PROVIDER_COMMAND_DESCRIPTION_CHARS] command_id = _opaque_command_id( selector_secret, binding_context, "claude", cwd, name, name, ) public = { "id": command_id, "name": name, "label": name, "description": description, "scope": None, "source": "claude", "kind": "command", "invocation": f"/{name}", } records.append( ProviderCommandRecord( public=public, native={ "name": name, **( {"control_generation": control_generation} if control_generation else {} ), }, ) ) return _inventory( selector_secret, binding_context, "claude", cwd, records, truncated=bool( isinstance(value, dict) and value.get("_agentsdock_provider_commands_truncated") ) or len(raw_commands) > bounded_limit, ) def empty_provider_command_inventory( backend: str, *, cwd: str, selector_secret: str, binding_context: str, ) -> ProviderCommandInventory: return _inventory( selector_secret, binding_context, backend, cwd, [], truncated=False, )