Agent 接入
Pi Coding Agent 接入
Pi 是一个终端编程 Agent,内置读取、写入、编辑和 Shell 工具。本指南把 Lumoswitch 注册为自定义 OpenAI Responses 提供方,同时不修改默认的 ~/.pi/agent 配置。
先安装 Pi:
npm install -g --ignore-scripts @earendil-works/pi-coding-agent然后创建一个输出协议为 Responses 的 Lumoswitch API 配置。
连接参数
| 占位符 | 值 |
|---|---|
{{api_base_url}} | 下游 API URL,包含 /v1 |
{{access_key}} | Lumoswitch API 配置选定的 Access Key |
{{model}} | 面向客户端的模型名称 |
{{pi_models_json}} | 根据有效模型和推理能力生成的 Pi 提供方目录 |
{{pi_settings_json}} | 为所选配置生成的 Pi 默认值和限定模型列表 |
单次启动
在需要使用 Pi 的项目目录中运行:
(
set -e
LUMOSWITCH_PI_DIR="$(mktemp -d "${TMPDIR:-/tmp}/lumoswitch-pi.XXXXXX")"
lumoswitch_cleanup() {
rm -rf "$LUMOSWITCH_PI_DIR"
}
trap lumoswitch_cleanup EXIT HUP INT TERM
umask 077
printf '%s\n' '{{pi_models_json}}' > "$LUMOSWITCH_PI_DIR/models.json"
printf '%s\n' '{{pi_settings_json}}' > "$LUMOSWITCH_PI_DIR/settings.json"
env \
PI_CODING_AGENT_DIR="$LUMOSWITCH_PI_DIR" \
LUMOSWITCH_PI_API_KEY="{{access_key}}" \
pi --provider lumoswitch --model "{{model}}" --no-session
)Pi 退出后会删除临时 Agent 目录和临时会话,不会修改常规 Pi 配置、凭证、扩展或会话。
永久使用
此命令会写入隔离的 Pi Agent 目录,把 Access Key 保存到仅当前用户可读的环境文件,添加一个小型 Shell 配置钩子,并创建可选维护启动器。Pi 会话单独保存在 ~/.local/share/lumoswitch/agents/pi/sessions,移除接入时不会删除。
set -e
LUMOSWITCH_ROOT="$HOME/.config/lumoswitch/agents/pi"
LUMOSWITCH_AGENT_DIR="$LUMOSWITCH_ROOT/agent"
LUMOSWITCH_SESSION_DIR="$HOME/.local/share/lumoswitch/agents/pi/sessions"
LUMOSWITCH_ENV="$LUMOSWITCH_ROOT/env.sh"
LUMOSWITCH_LAUNCHER="$HOME/.local/bin/lumoswitch-pi"
mkdir -p "$LUMOSWITCH_AGENT_DIR" "$LUMOSWITCH_SESSION_DIR" "$(dirname "$LUMOSWITCH_LAUNCHER")"
umask 077
if [ -f "$LUMOSWITCH_LAUNCHER" ]; then
LUMOSWITCH_BACKUP="$LUMOSWITCH_LAUNCHER.backup.$(date +%Y%m%d-%H%M%S)-$$"
cp "$LUMOSWITCH_LAUNCHER" "$LUMOSWITCH_BACKUP"
chmod 600 "$LUMOSWITCH_BACKUP"
fi
printf '%s\n' '{{pi_models_json}}' > "$LUMOSWITCH_AGENT_DIR/models.json"
printf '%s\n' '{{pi_settings_json}}' > "$LUMOSWITCH_AGENT_DIR/settings.json"
cat > "$LUMOSWITCH_ENV" <<'LUMOSWITCH_ENV_EOF'
export PI_CODING_AGENT_DIR="$HOME/.config/lumoswitch/agents/pi/agent"
export PI_CODING_AGENT_SESSION_DIR="$HOME/.local/share/lumoswitch/agents/pi/sessions"
export LUMOSWITCH_PI_API_KEY="{{access_key}}"
LUMOSWITCH_ENV_EOF
case "$SHELL" in
*/zsh) LUMOSWITCH_PROFILE="$HOME/.zshrc" ;;
*/bash) LUMOSWITCH_PROFILE="$HOME/.bashrc" ;;
*) LUMOSWITCH_PROFILE="$HOME/.profile" ;;
esac
touch "$LUMOSWITCH_PROFILE"
LUMOSWITCH_PROFILE_MARKER='# Lumoswitch Agent: pi'
LUMOSWITCH_SOURCE_LINE='[ -f "$HOME/.config/lumoswitch/agents/pi/env.sh" ] && . "$HOME/.config/lumoswitch/agents/pi/env.sh"'
if ! grep -Fqx "$LUMOSWITCH_SOURCE_LINE" "$LUMOSWITCH_PROFILE"; then
printf '\n%s\n%s\n' "$LUMOSWITCH_PROFILE_MARKER" "$LUMOSWITCH_SOURCE_LINE" >> "$LUMOSWITCH_PROFILE"
fi
printf '%s\n' "$LUMOSWITCH_PROFILE" > "$LUMOSWITCH_ROOT/profile-path"
cat > "$LUMOSWITCH_LAUNCHER" <<'LUMOSWITCH_LAUNCHER_EOF'
#!/usr/bin/env bash
set -e
LUMOSWITCH_ROOT="$HOME/.config/lumoswitch/agents/pi"
LUMOSWITCH_ENV="$LUMOSWITCH_ROOT/env.sh"
LUMOSWITCH_LAUNCHER="$HOME/.local/bin/lumoswitch-pi"
if [ "$1" = "--lumoswitch-clean" ]; then
if [ -f "$LUMOSWITCH_ROOT/profile-path" ]; then
LUMOSWITCH_PROFILE="$(cat "$LUMOSWITCH_ROOT/profile-path")"
if [ -f "$LUMOSWITCH_PROFILE" ]; then
LUMOSWITCH_PROFILE_MARKER='# Lumoswitch Agent: pi'
LUMOSWITCH_SOURCE_LINE='[ -f "$HOME/.config/lumoswitch/agents/pi/env.sh" ] && . "$HOME/.config/lumoswitch/agents/pi/env.sh"'
LUMOSWITCH_PROFILE_TMP="$LUMOSWITCH_PROFILE.lumoswitch.$$"
awk -v marker="$LUMOSWITCH_PROFILE_MARKER" -v source="$LUMOSWITCH_SOURCE_LINE" '$0 != marker && $0 != source' "$LUMOSWITCH_PROFILE" > "$LUMOSWITCH_PROFILE_TMP"
mv "$LUMOSWITCH_PROFILE_TMP" "$LUMOSWITCH_PROFILE"
fi
fi
if [ -d "$LUMOSWITCH_ROOT" ]; then
find "$LUMOSWITCH_ROOT" -depth \( -type f -o -type l \) -delete
find "$LUMOSWITCH_ROOT" -depth -type d -exec rmdir {} \; 2>/dev/null || true
fi
find "$(dirname "$LUMOSWITCH_LAUNCHER")" -maxdepth 1 -type f -name 'lumoswitch-pi.backup.*' -delete
rm -f "$LUMOSWITCH_LAUNCHER"
printf '%s\n' 'Lumoswitch Pi setup removed. Saved Pi sessions were preserved.'
exit 0
fi
if [ -f "$LUMOSWITCH_ENV" ]; then . "$LUMOSWITCH_ENV"; fi
exec pi "$@"
LUMOSWITCH_LAUNCHER_EOF
chmod 600 "$LUMOSWITCH_ENV"
find "$LUMOSWITCH_ROOT" -type f -exec chmod 600 {} \;
chmod 700 "$LUMOSWITCH_LAUNCHER"
. "$LUMOSWITCH_ENV"
printf '%s\n' 'Native Lumoswitch setup saved. Future command: pi'
printf '%s\n' 'Optional maintenance launcher: ~/.local/bin/lumoswitch-pi'
pi配置完成后打开新终端。日常使用直接运行 pi。
Windows 原生 PowerShell 导入
以下命令使用 Pi 的 Windows 原生配置覆盖,不需要 WSL。
Windows 单次使用
& {
$ErrorActionPreference = 'Stop'
$lumoswitchPiDir = Join-Path ([IO.Path]::GetTempPath()) ('lumoswitch-pi-' + [Guid]::NewGuid().ToString('N'))
[IO.Directory]::CreateDirectory($lumoswitchPiDir) | Out-Null
$lumoswitchEnvironmentNames = @('PI_CODING_AGENT_DIR', 'LUMOSWITCH_PI_API_KEY')
$lumoswitchPreviousEnvironment = @{}
foreach ($lumoswitchName in $lumoswitchEnvironmentNames) {
$lumoswitchPreviousEnvironment[$lumoswitchName] = [Environment]::GetEnvironmentVariable($lumoswitchName, 'Process')
}
$lumoswitchExitCode = 0
try {
[IO.File]::WriteAllText((Join-Path $lumoswitchPiDir 'models.json'), '{{pi_models_json}}', [Text.UTF8Encoding]::new($false))
[IO.File]::WriteAllText((Join-Path $lumoswitchPiDir 'settings.json'), '{{pi_settings_json}}', [Text.UTF8Encoding]::new($false))
[Environment]::SetEnvironmentVariable('PI_CODING_AGENT_DIR', $lumoswitchPiDir, 'Process')
[Environment]::SetEnvironmentVariable('LUMOSWITCH_PI_API_KEY', '{{access_key}}', 'Process')
$lumoswitchExecutable = Get-Command 'pi.cmd' -CommandType Application -ErrorAction SilentlyContinue
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi.exe' -CommandType Application -ErrorAction SilentlyContinue }
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi' -CommandType Application -ErrorAction Stop }
& $lumoswitchExecutable.Path '--provider' 'lumoswitch' '--model' '{{model}}' '--no-session'
if ($null -ne $LASTEXITCODE) { $lumoswitchExitCode = $LASTEXITCODE }
} finally {
foreach ($lumoswitchName in $lumoswitchEnvironmentNames) {
[Environment]::SetEnvironmentVariable($lumoswitchName, $lumoswitchPreviousEnvironment[$lumoswitchName], 'Process')
}
Remove-Item -LiteralPath $lumoswitchPiDir -Recurse -Force -ErrorAction SilentlyContinue
}
if ($lumoswitchExitCode -ne 0) { throw 'pi exited with code ' + $lumoswitchExitCode }
}Windows 永久使用
& {
$ErrorActionPreference = 'Stop'
$lumoswitchRoot = Join-Path $env:LOCALAPPDATA 'Lumoswitch\agents\pi'
$lumoswitchAgentDir = Join-Path $lumoswitchRoot 'agent'
$lumoswitchSessionDir = Join-Path $env:LOCALAPPDATA 'Lumoswitch\sessions\pi'
$lumoswitchLauncher = Join-Path $env:LOCALAPPDATA 'Lumoswitch\bin\lumoswitch-pi.ps1'
$lumoswitchBackupSuffix = (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + $PID
function Protect-LumoswitchFile([string] $Path) {
$lumoswitchSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
& icacls.exe $Path '/inheritance:r' '/grant:r' ('*' + $lumoswitchSid + ':(F)') '*S-1-5-18:(F)' '*S-1-5-32-544:(F)' | Out-Null
if ($LASTEXITCODE -ne 0) { throw "Could not restrict access to $Path" }
}
function Install-LumoswitchFile([string] $Path, [string] $Content) {
[IO.Directory]::CreateDirectory((Split-Path -Parent $Path)) | Out-Null
if (Test-Path -LiteralPath $Path) {
$lumoswitchItem = Get-Item -LiteralPath $Path -Force
if ($lumoswitchItem.PSIsContainer -or ($lumoswitchItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw "Lumoswitch refused to replace a directory or link: $Path"
}
$lumoswitchBackup = $Path + '.backup.' + $lumoswitchBackupSuffix
Copy-Item -LiteralPath $Path -Destination $lumoswitchBackup
Protect-LumoswitchFile $lumoswitchBackup
}
$lumoswitchTemporary = $Path + '.' + [Guid]::NewGuid().ToString('N') + '.tmp'
try {
[IO.File]::WriteAllText($lumoswitchTemporary, $Content, [Text.UTF8Encoding]::new($false))
Move-Item -LiteralPath $lumoswitchTemporary -Destination $Path -Force
Protect-LumoswitchFile $Path
} finally {
Remove-Item -LiteralPath $lumoswitchTemporary -Force -ErrorAction SilentlyContinue
}
}
function Preserve-LumoswitchOriginal([string] $Name) {
$lumoswitchOriginalRoot = Join-Path $lumoswitchRoot 'original'
$lumoswitchState = Join-Path $lumoswitchOriginalRoot ($Name + '.state')
if (Test-Path -LiteralPath $lumoswitchState -PathType Leaf) { return }
[IO.Directory]::CreateDirectory($lumoswitchOriginalRoot) | Out-Null
$lumoswitchValue = [Environment]::GetEnvironmentVariable($Name, 'User')
if ($null -eq $lumoswitchValue) {
Install-LumoswitchFile $lumoswitchState 'absent'
} else {
Install-LumoswitchFile (Join-Path $lumoswitchOriginalRoot ($Name + '.value')) $lumoswitchValue
Install-LumoswitchFile $lumoswitchState 'present'
}
}
[IO.Directory]::CreateDirectory($lumoswitchAgentDir) | Out-Null
[IO.Directory]::CreateDirectory($lumoswitchSessionDir) | Out-Null
Install-LumoswitchFile (Join-Path $lumoswitchAgentDir 'models.json') '{{pi_models_json}}'
Install-LumoswitchFile (Join-Path $lumoswitchAgentDir 'settings.json') '{{pi_settings_json}}'
foreach ($lumoswitchName in @('PI_CODING_AGENT_DIR', 'PI_CODING_AGENT_SESSION_DIR', 'LUMOSWITCH_PI_API_KEY')) {
Preserve-LumoswitchOriginal $lumoswitchName
}
$lumoswitchEnvironment = [ordered]@{
PI_CODING_AGENT_DIR = $lumoswitchAgentDir
PI_CODING_AGENT_SESSION_DIR = $lumoswitchSessionDir
LUMOSWITCH_PI_API_KEY = '{{access_key}}'
}
foreach ($lumoswitchEntry in $lumoswitchEnvironment.GetEnumerator()) {
[Environment]::SetEnvironmentVariable($lumoswitchEntry.Key, $lumoswitchEntry.Value, 'User')
[Environment]::SetEnvironmentVariable($lumoswitchEntry.Key, $lumoswitchEntry.Value, 'Process')
}
$lumoswitchLauncherContent = @'
param([Parameter(ValueFromRemainingArguments = $true)][string[]] $AgentArgs)
$ErrorActionPreference = 'Stop'
$lumoswitchRoot = Join-Path $env:LOCALAPPDATA 'Lumoswitch\agents\pi'
$lumoswitchLauncher = Join-Path $env:LOCALAPPDATA 'Lumoswitch\bin\lumoswitch-pi.ps1'
function Remove-LumoswitchFile([string] $Path) {
if (-not (Test-Path -LiteralPath $Path)) { return }
$lumoswitchItem = Get-Item -LiteralPath $Path -Force
if ($lumoswitchItem.PSIsContainer -or ($lumoswitchItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw "Lumoswitch refused to remove a directory or link: $Path"
}
Remove-Item -LiteralPath $Path -Force
}
function Restore-LumoswitchOriginal([string] $Name) {
$lumoswitchOriginalRoot = Join-Path $lumoswitchRoot 'original'
$lumoswitchState = Join-Path $lumoswitchOriginalRoot ($Name + '.state')
if (-not (Test-Path -LiteralPath $lumoswitchState -PathType Leaf)) { return }
$lumoswitchStatus = (Get-Content -LiteralPath $lumoswitchState -Raw).Trim()
$lumoswitchValue = if ($lumoswitchStatus -eq 'present') {
Get-Content -LiteralPath (Join-Path $lumoswitchOriginalRoot ($Name + '.value')) -Raw
} else { $null }
[Environment]::SetEnvironmentVariable($Name, $lumoswitchValue, 'User')
[Environment]::SetEnvironmentVariable($Name, $lumoswitchValue, 'Process')
}
if ($AgentArgs.Count -gt 0 -and $AgentArgs[0] -eq '--lumoswitch-clean') {
foreach ($lumoswitchName in @('PI_CODING_AGENT_DIR', 'PI_CODING_AGENT_SESSION_DIR', 'LUMOSWITCH_PI_API_KEY')) {
Restore-LumoswitchOriginal $lumoswitchName
}
if (Test-Path -LiteralPath $lumoswitchRoot -PathType Container) {
$lumoswitchRootItem = Get-Item -LiteralPath $lumoswitchRoot -Force
if ($lumoswitchRootItem.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'Lumoswitch refused to clean through a linked directory.' }
foreach ($lumoswitchFile in @(Get-ChildItem -LiteralPath $lumoswitchRoot -File -Recurse -Force)) { Remove-LumoswitchFile $lumoswitchFile.FullName }
foreach ($lumoswitchDirectory in @(Get-ChildItem -LiteralPath $lumoswitchRoot -Directory -Recurse -Force | Sort-Object FullName -Descending)) {
if ($null -eq (Get-ChildItem -LiteralPath $lumoswitchDirectory.FullName -Force | Select-Object -First 1)) { Remove-Item -LiteralPath $lumoswitchDirectory.FullName -Force }
}
if ($null -eq (Get-ChildItem -LiteralPath $lumoswitchRoot -Force | Select-Object -First 1)) { Remove-Item -LiteralPath $lumoswitchRoot -Force }
}
$lumoswitchBin = Split-Path -Parent $lumoswitchLauncher
if (Test-Path -LiteralPath $lumoswitchBin -PathType Container) {
foreach ($lumoswitchBackup in @(Get-ChildItem -LiteralPath $lumoswitchBin -Filter 'lumoswitch-pi.ps1.backup.*' -File -Force)) { Remove-LumoswitchFile $lumoswitchBackup.FullName }
}
Remove-LumoswitchFile $lumoswitchLauncher
Write-Host 'Lumoswitch Pi setup removed. Saved Pi sessions were preserved.'
exit 0
}
$lumoswitchAgentDir = Join-Path $env:LOCALAPPDATA 'Lumoswitch\agents\pi\agent'
$lumoswitchSessionDir = Join-Path $env:LOCALAPPDATA 'Lumoswitch\sessions\pi'
$lumoswitchApiKey = [Environment]::GetEnvironmentVariable('LUMOSWITCH_PI_API_KEY', 'User')
[Environment]::SetEnvironmentVariable('PI_CODING_AGENT_DIR', $lumoswitchAgentDir, 'Process')
[Environment]::SetEnvironmentVariable('PI_CODING_AGENT_SESSION_DIR', $lumoswitchSessionDir, 'Process')
[Environment]::SetEnvironmentVariable('LUMOSWITCH_PI_API_KEY', $lumoswitchApiKey, 'Process')
$lumoswitchExecutable = Get-Command 'pi.cmd' -CommandType Application -ErrorAction SilentlyContinue
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi.exe' -CommandType Application -ErrorAction SilentlyContinue }
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi' -CommandType Application -ErrorAction Stop }
& $lumoswitchExecutable.Path @AgentArgs
if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE }
'@
Install-LumoswitchFile $lumoswitchLauncher $lumoswitchLauncherContent
Write-Host 'Native Lumoswitch setup saved. Future command: pi'
Write-Host 'Optional maintenance launcher:' $lumoswitchLauncher
$lumoswitchExecutable = Get-Command 'pi.cmd' -CommandType Application -ErrorAction SilentlyContinue
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi.exe' -CommandType Application -ErrorAction SilentlyContinue }
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi' -CommandType Application -ErrorAction Stop }
& $lumoswitchExecutable.Path
if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) { throw 'pi exited with code ' + $LASTEXITCODE }
}Windows 命令会在安装隔离配置前保存已有的用户级 Pi 环境变量;清理时恢复原值,并保留独立的会话目录。
移除 Lumoswitch 接入
Windows PowerShell
& {
$lumoswitchLauncher = Join-Path $env:LOCALAPPDATA 'Lumoswitch\bin\lumoswitch-pi.ps1'
if (-not (Test-Path -LiteralPath $lumoswitchLauncher -PathType Leaf)) {
throw 'The optional Lumoswitch maintenance launcher is missing. Run persistent import again before cleanup.'
}
& powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $lumoswitchLauncher --lumoswitch-clean
}macOS / Linux
"$HOME/.local/bin/lumoswitch-pi" --lumoswitch-clean清理会移除整个 Lumoswitch 隔离 Agent 目录(包括安装在其中的扩展或软件包),以及 Shell 配置钩子、保存的凭证环境和维护启动器;独立 Pi 会话目录会保留,~/.pi/agent 永远不会被修改。
验证与排查
- 运行
/model,确认提供方为lumoswitch,且只显示已配置的 Lumoswitch 模型。 - 使用 Shift+Tab 切换推理档位;列表只包含所选模型及其有效路由共同支持的档位。
- 模板会关闭兼容网关常拒绝的可选 OpenAI 字段,同时保留 Pi 的常规文件和 Shell 工具。
- 项目级
.pi/settings.json仍会覆盖隔离的全局设置;如果 Pi 启动后模型不同,请检查该文件。 - 如果找不到
pi,请重新安装当前官方包并打开新终端。
参考:Pi Coding Agent、自定义模型、设置和环境变量。
维护者发布字段
- 平台 ID:
pi - 展示名称:
Pi Coding Agent - 下游协议:
responses - macOS/Linux 与 Windows 临时模式:启用
- macOS/Linux 与 Windows 永久策略:
native - 两个平台的后续命令:
pi - 展示顺序:
25 - 最后结构验证:
2026-08-27