Skip to main content

agent_profile/adapter/
pi.rs

1// Copyright (c) 2026 Costas Kirgoussios
2//
3// This software is licensed under the PolyForm Noncommercial License 1.0.0.
4// You may obtain a copy of the License at:
5// https://polyformproject.org/licenses/noncommercial/1.0.0/
6
7//! Pi: `PI_CODING_AGENT_DIR` per profile (SP4 design §8 — provider keys bypass, unless `auth.json` is
8//! present).
9
10use 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/// The Pi adapter.
75#[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        // `PI_CODING_AGENT_DIR` names the config directory itself: the probe's files landed in the
85        // target with no `.pi/agent` prefix, unlike the default `$PROBE_AGENT_HOME/.pi/agent`. The
86        // profile directory IS the config directory, so nothing else is declared.
87        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}