agent_profile/adapter/
pi.rs1use std::path::PathBuf;
11
12use super::{
13 Adapter, AdapterEvidence, AdapterMetadata, Capability, CapabilityClaim, CapabilityState,
14 EnvOverride, Mechanism, PathKind, PlanContext, PlannedLaunch, SupportLevel, profile_dir,
15};
16use crate::config::AppRoot;
17use crate::error::Result;
18use crate::name::ProfileName;
19
20const VAR: &str = "PI_CODING_AGENT_DIR";
21
22static METADATA: AdapterMetadata = AdapterMetadata {
23 id: "pi",
24 executable: "pi",
25 mechanism: Mechanism::Env(VAR),
26 support: SupportLevel::Proven,
27 evidence: AdapterEvidence {
28 mechanism_id: "pi-coding-agent-dir-v1",
29 verified_at: "2026-11-09",
30 upstream_version: "1.5.0",
31 source_url: "measured",
32 notes: "the baseline held auth.json and models-store.json at the default .pi/agent and the \
33 same files moved under PI_CODING_AGENT_DIR with nothing left behind — the variable \
34 names the config directory itself; the behaviour step's exit 1 is the probe's \
35 PROBE_EXPECT (`pi -p hello` initialises before failing without a key); \
36 ANTHROPIC_API_KEY, OPENAI_API_KEY and PI_CODING_AGENT_DIR are strings measured, \
37 and the help lists PI_CODING_AGENT_SESSION_DIR, PI_PACKAGE_DIR, PI_OFFLINE, \
38 PI_TELEMETRY and PI_SHARE_VIEWER_URL",
39 },
40 capabilities: &[
41 CapabilityClaim {
42 capability: Capability::ConfigIsolation,
43 state: CapabilityState::Supported,
44 basis: "measured: `+ f /home/agent/probe-target/auth.json` and \
45 `+ f /home/agent/probe-target/models-store.json` — the config directory's \
46 files moved **without** a `.pi/agent` prefix, so `PI_CODING_AGENT_DIR` names the \
47 config directory itself (the delta at the watched default `.pi` was empty)",
48 },
49 CapabilityClaim {
50 capability: Capability::CredentialIsolation,
51 state: CapabilityState::NotSupported,
52 basis: "measured: `+ f /home/agent/probe-target/auth.json` under the variable against \
53 baseline `f /home/agent/.pi/agent/auth.json` at the default — pi stores \
54 credentials in `auth.json` inside its own config directory, which is exactly \
55 what `PI_CODING_AGENT_DIR` moves (§6's wider exception: the probe established \
56 where credentials are stored), so they travel with the config directory and no \
57 separate credential store exists to point anywhere else; env keys \
58 (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, …) bypass the file entirely (help)",
59 },
60 CapabilityClaim {
61 capability: Capability::StateIsolation,
62 state: CapabilityState::Supported,
63 basis: "measured: `+ d /home/agent/probe-target/sessions` and \
64 `+ f /home/agent/probe-target/models-store.json` — the session and \
65 models-store partial-measure cells appeared under the mechanism, and pi wrote \
66 plenty before it needed the network",
67 },
68 ],
69 env: &[EnvOverride { name: VAR, sensitive: false }],
70 conflicts: &[],
71 credential_env: &["ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
72};
73
74#[derive(Debug, Clone, Copy)]
76pub struct Pi;
77
78impl Adapter for Pi {
79 fn metadata(&self) -> &'static AdapterMetadata {
80 &METADATA
81 }
82
83 fn paths(&self, root: &AppRoot, profile: &ProfileName) -> Vec<(PathBuf, PathKind)> {
84 vec![(profile_dir(root, profile, METADATA.id), PathKind::Dir)]
88 }
89
90 fn plan(&self, ctx: &PlanContext<'_>) -> Result<PlannedLaunch> {
91 METADATA.mechanism.plan(self, ctx)
92 }
93}