Prepare dependency generations before selecting them. Keep shipped tool bytes separate from writable additions, and store facts beside their entries. Validate proposed plugin sets before config publication. Restore the previous config if the facts write fails. Consolidate duplicate updater, backup, setup, and voice helpers. Repair launcher selection, dependency consumers, download ownership, update feeds, and native Windows process and file handling. Verification: 206 changed/prior-failing Python files reported 4630 passed, one failed, and 330 skipped. Fix the remaining Hindsight fixture boundary. The final targeted rerun reported 234 passed and two skipped. The store review regression batch reported 83 passed and one skipped. Desktop TypeScript checks, 56 selected Electron tests, 24 release tests, and the removed-import/compatibility guards passed. This is an integration checkpoint, not full audit acceptance. The complete Python suite has not run on this fixed tree. Crash-atomic plugin publication, generation cleanup, receipt correlation, and packaged lifecycle acceptance remain open in docs/pm-audit-status.md.
89 lines
3.8 KiB
Python
89 lines
3.8 KiB
Python
"""An external-process (ACP) provider can ship from outside this tree.
|
|
|
|
The known-provider gate, the binary, the argv and the env var names used to be
|
|
spelled out for one vendor, so a profile registered from a plugin died with
|
|
"Unknown provider" before any client was built. This registers a provider the
|
|
way a standalone package does — before importing ``hermes_cli`` — and walks the
|
|
real resolution path, asserting ``copilot-acp`` is unchanged alongside it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import stat
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from providers import register_provider
|
|
from providers.base import ProviderProfile
|
|
|
|
|
|
class _AcmeACPProfile(ProviderProfile):
|
|
def create_client(self, **kwargs):
|
|
return ("acme-client", kwargs)
|
|
|
|
def fetch_models(self, **kwargs):
|
|
return None
|
|
|
|
|
|
register_provider(
|
|
_AcmeACPProfile(
|
|
name="acme-acp",
|
|
aliases=("acme",),
|
|
display_name="Acme ACP",
|
|
base_url="acp://acme",
|
|
auth_type="external_process",
|
|
process_command="acme-cli",
|
|
process_args=("--acp",),
|
|
process_command_env_vars=("ACME_CLI_PATH",),
|
|
process_args_env_var="ACME_ACP_ARGS",
|
|
)
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def fake_cli(tmp_path, monkeypatch):
|
|
bindir = tmp_path / "bin"
|
|
bindir.mkdir()
|
|
for name in ("acme-cli", "copilot", "custom-acme"):
|
|
exe = bindir / (name + ".exe" if os.name == "nt" else name)
|
|
exe.write_text("#!/bin/sh\nexit 0\n")
|
|
exe.chmod(exe.stat().st_mode | stat.S_IEXEC)
|
|
monkeypatch.setenv("PATH", f"{bindir}{os.pathsep}{os.environ.get('PATH', '')}")
|
|
return bindir
|
|
|
|
|
|
def test_an_out_of_tree_external_process_provider_resolves_end_to_end(fake_cli, monkeypatch):
|
|
from hermes_cli.auth import PROVIDER_REGISTRY, resolve_external_process_provider_credentials, resolve_provider
|
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
|
|
assert PROVIDER_REGISTRY["acme"] is PROVIDER_REGISTRY["acme-acp"]
|
|
assert PROVIDER_REGISTRY["acme-acp"].auth_type == "external_process"
|
|
assert resolve_provider("acme") == "acme-acp"
|
|
|
|
creds = resolve_external_process_provider_credentials("acme-acp")
|
|
assert Path(creds["command"]) == fake_cli / ("acme-cli.exe" if os.name == "nt" else "acme-cli")
|
|
assert (creds["args"], creds["api_key"]) == (["--acp"], "acme-acp")
|
|
|
|
monkeypatch.setenv("ACME_CLI_PATH", str(fake_cli / ("custom-acme.exe" if os.name == "nt" else "custom-acme")))
|
|
monkeypatch.setenv("ACME_ACP_ARGS", "--acp=true --verbose")
|
|
creds = resolve_external_process_provider_credentials("acme-acp")
|
|
assert (creds["command"], creds["args"]) == (str(fake_cli / ("custom-acme.exe" if os.name == "nt" else "custom-acme")), ["--acp=true", "--verbose"])
|
|
|
|
runtime = resolve_runtime_provider(requested="acme", target_model="acme")
|
|
assert (runtime["provider"], runtime["base_url"], runtime["source"]) == ("acme-acp", "acp://acme", "process")
|
|
|
|
|
|
def test_copilot_acp_launch_details_are_unchanged(fake_cli, monkeypatch):
|
|
from hermes_cli.auth import resolve_external_process_provider_credentials
|
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
|
|
creds = resolve_external_process_provider_credentials("copilot-acp")
|
|
assert Path(creds["command"]) == fake_cli / ("copilot.exe" if os.name == "nt" else "copilot")
|
|
assert (creds["args"], creds["api_key"], creds["base_url"]) == (["--acp", "--stdio"], "copilot-acp", "acp://copilot")
|
|
|
|
monkeypatch.setenv("COPILOT_CLI_PATH", str(fake_cli / ("custom-acme.exe" if os.name == "nt" else "custom-acme")))
|
|
assert resolve_external_process_provider_credentials("copilot-acp")["command"] == str(fake_cli / ("custom-acme.exe" if os.name == "nt" else "custom-acme"))
|
|
assert resolve_runtime_provider(requested="copilot-acp", target_model="x")["base_url"] == "acp://copilot"
|