pi-coding-agent: add module

This commit is contained in:
Nikhil Singh
2026-06-04 08:11:26 +05:30
committed by Austin Horstman
parent 5fadbec07a
commit 37e8eef933
15 changed files with 428 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
{ config, ... }:
{
time = "2026-06-02T12:00:00+00:00";
condition = config.programs.pi-coding-agent.enable;
message = ''
A new module is available: `programs.pi-coding-agent`.
The module supports declarative configuration of Pi Coding Agent
including settings, keybindings, models, and global context
(AGENTS.md).
For MCP server support, use the existing `programs.mcp` module
which writes to `~/.config/mcp/mcp.json`.
'';
}

View File

@@ -0,0 +1,223 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
literalExpression
mkEnableOption
mkIf
mkOption
mkPackageOption
;
cfg = config.programs.pi-coding-agent;
jsonFormat = pkgs.formats.json { };
isPathLike =
content:
lib.isPath content
|| (builtins.isString content && lib.hasPrefix "${builtins.storeDir}/" content)
|| lib.isDerivation content;
upstreamConfigDir = "${config.home.homeDirectory}/.pi/agent";
packageWithExtraPackages =
if cfg.package != null && cfg.extraPackages != [ ] then
pkgs.symlinkJoin {
inherit (cfg.package) meta;
name = "${lib.getName cfg.package}-wrapped-${lib.getVersion cfg.package}";
paths = [ cfg.package ];
preferLocalBuild = true;
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
wrapProgram $out/bin/pi \
--suffix PATH : ${lib.makeBinPath cfg.extraPackages}
'';
}
else
cfg.package;
in
{
meta.maintainers = with lib.hm.maintainers; [ semi710 ];
options.programs.pi-coding-agent = {
enable = mkEnableOption "pi-coding-agent";
package = mkPackageOption pkgs "pi-coding-agent" { nullable = true; };
extraPackages = mkOption {
type = with lib.types; listOf package;
default = [ ];
example = literalExpression "[ pkgs.nodejs pkgs.bun ]";
description = ''
Extra packages available to Pi Coding Agent.
These are added to the PATH of the wrapped pi binary.
Needed for packages installed by pi (e.g.
{command}`npm:@termdraw/pi` requires {command}`npm` and
{command}`bun`).
'';
};
configDir = mkOption {
type = lib.types.str;
default = upstreamConfigDir;
defaultText = literalExpression ''"''${config.home.homeDirectory}/.pi/agent"'';
example = literalExpression ''"''${config.xdg.configHome}/pi/agent"'';
description = ''
Directory holding Pi Coding Agent's configuration files.
Defaults to {file}`~/.pi/agent`, matching the upstream
{command}`pi` CLI default. The {env}`PI_CODING_AGENT_DIR`
environment variable is exported automatically whenever the
directory differs from this default so the CLI reads
configuration from the same location.
'';
};
settings = mkOption {
inherit (jsonFormat) type;
default = { };
example = {
defaultProvider = "anthropic";
defaultModel = "claude-sonnet-4-20250514";
defaultThinkingLevel = "medium";
theme = "dark";
packages = [
"npm:@termdraw/pi"
"npm:pi-mcp-adapter"
];
compaction = {
enabled = true;
reserveTokens = 16384;
keepRecentTokens = 20000;
};
retry = {
enabled = true;
maxRetries = 3;
};
enabledModels = [
"claude-*"
"gpt-4o"
];
};
description = ''
Configuration written to
{file}`~/.pi/agent/settings.json`.
See <https://pi.dev/docs/latest/settings> for the
documentation.
'';
};
keybindings = mkOption {
inherit (jsonFormat) type;
default = { };
example = {
"tui.editor.cursorUp" = [
"up"
"ctrl+p"
];
"tui.editor.cursorDown" = [
"down"
"ctrl+n"
];
"tui.editor.deleteWordBackward" = [
"ctrl+w"
"alt+backspace"
];
};
description = ''
Keybindings configuration written to
{file}`~/.pi/agent/keybindings.json`.
See <https://pi.dev/docs/latest/keybindings> for the
documentation.
'';
};
models = mkOption {
inherit (jsonFormat) type;
default = { };
example = {
providers = {
ollama = {
baseUrl = "http://localhost:11434/v1";
api = "openai-completions";
apiKey = "ollama";
models = [ { id = "llama3.1:8b"; } ];
};
};
};
description = ''
Custom model providers written to
{file}`~/.pi/agent/models.json`.
Each provider entry may contain `baseUrl`,
`api`, `apiKey`, `compat`, and a `models`
list with `id`, `name`, `reasoning`, etc.
See <https://pi.dev/docs/latest/models> for the
documentation.
'';
};
context = mkOption {
type = lib.types.either lib.types.lines lib.types.path;
default = "";
description = ''
Global context for Pi Coding Agent.
The value is either:
- Inline content as a string
- A path to a file containing the content
The configured content is written to
{file}`AGENTS.md` inside
{option}`programs.pi-coding-agent.configDir`
(default {file}`~/.pi/agent/AGENTS.md`).
'';
example = literalExpression "./pi-context.md";
};
};
config = mkIf cfg.enable {
home = {
packages = mkIf (packageWithExtraPackages != null) [
packageWithExtraPackages
];
sessionVariables = lib.mkIf (cfg.configDir != upstreamConfigDir) {
PI_CODING_AGENT_DIR = cfg.configDir;
};
file = lib.mkMerge [
(mkIf (cfg.settings != { }) {
"${cfg.configDir}/settings.json".source =
jsonFormat.generate "pi-coding-agent-settings.json" cfg.settings;
})
(mkIf (cfg.keybindings != { }) {
"${cfg.configDir}/keybindings.json".source =
jsonFormat.generate "pi-coding-agent-keybindings.json" cfg.keybindings;
})
(mkIf (cfg.models != { }) {
"${cfg.configDir}/models.json".source =
jsonFormat.generate "pi-coding-agent-models.json" cfg.models;
})
(
if isPathLike cfg.context then
{ "${cfg.configDir}/AGENTS.md".source = cfg.context; }
else
(mkIf (cfg.context != "") {
"${cfg.configDir}/AGENTS.md".text = cfg.context;
})
)
];
};
};
}

View File

@@ -0,0 +1,9 @@
{
programs.pi-coding-agent = {
enable = true;
context = "";
};
nmt.script = ''
assertPathNotExists home-files/.pi/agent/AGENTS.md
'';
}

View File

@@ -0,0 +1,4 @@
# Global Pi Context
Always use TypeScript strict mode.
Follow the project's existing code style.

View File

@@ -0,0 +1,16 @@
{
programs.pi-coding-agent = {
enable = true;
context = ''
# Global Pi Context
Always use TypeScript strict mode.
Follow the project's existing code style.
'';
};
nmt.script = ''
assertFileExists home-files/.pi/agent/AGENTS.md
assertFileContent home-files/.pi/agent/AGENTS.md \
${./context-inline.md}
'';
}

View File

@@ -0,0 +1,11 @@
{
programs.pi-coding-agent = {
enable = true;
context = ./context-inline.md;
};
nmt.script = ''
assertFileExists home-files/.pi/agent/AGENTS.md
assertFileContent home-files/.pi/agent/AGENTS.md \
${./context-inline.md}
'';
}

View File

@@ -0,0 +1,11 @@
{
programs.pi-coding-agent = {
enable = true;
configDir = "/home/testuser/.config/pi/agent";
};
nmt.script = ''
# Verify env var is set for non-default configDir
assertFileRegex home-path/etc/profile.d/hm-session-vars.sh \
'PI_CODING_AGENT_DIR'
'';
}

View File

@@ -0,0 +1,10 @@
{
pi-coding-agent-settings = ./settings.nix;
pi-coding-agent-empty-settings = ./empty-settings.nix;
pi-coding-agent-keybindings = ./keybindings.nix;
pi-coding-agent-context-inline = ./context-inline.nix;
pi-coding-agent-context-path = ./context-path.nix;
pi-coding-agent-context-empty = ./context-empty.nix;
pi-coding-agent-custom-config-dir = ./custom-config-dir.nix;
pi-coding-agent-models = ./models.nix;
}

View File

@@ -0,0 +1,9 @@
{
programs.pi-coding-agent = {
enable = true;
settings = { };
};
nmt.script = ''
assertPathNotExists home-files/.pi/agent/settings.json
'';
}

View File

@@ -0,0 +1,14 @@
{
"tui.editor.cursorDown": [
"down",
"ctrl+n"
],
"tui.editor.cursorUp": [
"up",
"ctrl+p"
],
"tui.editor.deleteWordBackward": [
"ctrl+w",
"alt+backspace"
]
}

View File

@@ -0,0 +1,24 @@
{
programs.pi-coding-agent = {
enable = true;
keybindings = {
"tui.editor.cursorUp" = [
"up"
"ctrl+p"
];
"tui.editor.cursorDown" = [
"down"
"ctrl+n"
];
"tui.editor.deleteWordBackward" = [
"ctrl+w"
"alt+backspace"
];
};
};
nmt.script = ''
assertFileExists home-files/.pi/agent/keybindings.json
assertFileContent home-files/.pi/agent/keybindings.json \
${./keybindings.json}
'';
}

View File

@@ -0,0 +1,14 @@
{
"providers": {
"litellm": {
"api": "openai-completions",
"apiKey": "ollama",
"baseUrl": "http://localhost:11434/v1",
"models": [
{
"id": "llama3.1:8b"
}
]
}
}
}

View File

@@ -0,0 +1,20 @@
{
programs.pi-coding-agent = {
enable = true;
models = {
providers = {
litellm = {
baseUrl = "http://localhost:11434/v1";
api = "openai-completions";
apiKey = "ollama";
models = [ { id = "llama3.1:8b"; } ];
};
};
};
};
nmt.script = ''
assertFileExists home-files/.pi/agent/models.json
assertFileContent home-files/.pi/agent/models.json \
${./models.json}
'';
}

View File

@@ -0,0 +1,19 @@
{
"compaction": {
"enabled": true,
"keepRecentTokens": 20000,
"reserveTokens": 16384
},
"defaultModel": "claude-sonnet-4-20250514",
"defaultProvider": "anthropic",
"defaultThinkingLevel": "medium",
"enabledModels": [
"claude-*",
"gpt-4o"
],
"retry": {
"enabled": true,
"maxRetries": 3
},
"theme": "dark"
}

View File

@@ -0,0 +1,29 @@
{
programs.pi-coding-agent = {
enable = true;
settings = {
defaultProvider = "anthropic";
defaultModel = "claude-sonnet-4-20250514";
defaultThinkingLevel = "medium";
theme = "dark";
compaction = {
enabled = true;
reserveTokens = 16384;
keepRecentTokens = 20000;
};
retry = {
enabled = true;
maxRetries = 3;
};
enabledModels = [
"claude-*"
"gpt-4o"
];
};
};
nmt.script = ''
assertFileExists home-files/.pi/agent/settings.json
assertFileContent home-files/.pi/agent/settings.json \
${./settings.json}
'';
}