feat: sync upstream pi-mono (386 commits)
从上游 badlogic/pi-mono 同步 386 个提交,手动解决所有冲突: 保留 SproutClaw 独有功能: - RPC: reload 命令、bash 流式输出、get_extensions 命令 - settings-manager: showChangelogOnStartup 设置 - agent-session: turnIndex getter - 移除 pi 版本检查通知(interactive-mode) - skills 系统、启动脚本、自定义工具脚本 直接采用上游版本: - packages/ai 模型列表及测试文件 - packages/coding-agent 文档 - 其余未冲突的全部上游代码 升级 @mistralai/mistralai 至 2.2.6(修复 promptCacheKey 类型错误) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
111
packages/coding-agent/docs/containerization.md
Normal file
111
packages/coding-agent/docs/containerization.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Containerization
|
||||
|
||||
Pi runs with all permissions by default, but in some cases, you will want to have more control over what directories Pi can write to and which accesses it has.
|
||||
|
||||
There are two general options. You can either
|
||||
1. run the whole `pi` process inside an isolated environment, or
|
||||
2. run `pi` on the host and route tool execution into an isolated environment.
|
||||
|
||||
## Choose a pattern
|
||||
|
||||
| Pattern | What is isolated | Best for | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| Gondolin extension | Built-in tools and `!` commands | Local micro-VM isolation while keeping auth on host | See [`examples/extensions/gondolin/`](../examples/extensions/gondolin/). |
|
||||
| Plain Docker | Whole `pi` process in a local container | Simple local isolation | Provider API keys enter the container. |
|
||||
| OpenShell | Whole `pi` process in a policy-controlled sandbox | Local or remote managed sandbox | Requires an OpenShell gateway |
|
||||
|
||||
Extensions run wherever the `pi` process runs. If you run host `pi` with a tool-routing extension, other custom extension tools still run on the host unless they also delegate their operations.
|
||||
|
||||
## Gondolin
|
||||
|
||||
[Gondolin](https://github.com/earendil-works/gondolin) is a local Linux micro-VM.
|
||||
Use the [example extension](../examples/extensions/gondolin) when you want `pi` on the host but all built-in tools routed into the VM.
|
||||
|
||||
Setup:
|
||||
|
||||
```bash
|
||||
cp -R packages/coding-agent/examples/extensions/gondolin ~/.pi/agent/extensions/gondolin
|
||||
cd ~/.pi/agent/extensions/gondolin
|
||||
npm install --ignore-scripts
|
||||
```
|
||||
|
||||
Run from the project you want mounted:
|
||||
|
||||
```bash
|
||||
cd /path/to/project
|
||||
pi -e ~/.pi/agent/extensions/gondolin
|
||||
```
|
||||
|
||||
The extension mounts the host cwd at `/workspace` in the VM and overrides `read`, `write`, `edit`, `bash`, `grep`, `find`, and `ls`.
|
||||
User `!` commands are routed into the VM, as well.
|
||||
File changes under `/workspace` write through to the host.
|
||||
|
||||
Requirements: Node.js >= 23.6.0 for `@earendil-works/gondolin`, plus QEMU (requires installation through your package manager).
|
||||
|
||||
## Plain Docker
|
||||
|
||||
Run the whole `pi` process in Docker when you want the simplest local container boundary.
|
||||
|
||||
`Dockerfile.pi`:
|
||||
|
||||
```dockerfile
|
||||
FROM node:24-bookworm-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends bash ca-certificates git ripgrep \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent
|
||||
|
||||
WORKDIR /workspace
|
||||
ENTRYPOINT ["pi"]
|
||||
```
|
||||
|
||||
Build and run:
|
||||
|
||||
```bash
|
||||
docker build -t pi-sandbox -f Dockerfile.pi .
|
||||
|
||||
docker run --rm -it \
|
||||
-e ANTHROPIC_API_KEY \
|
||||
-v "$PWD:/workspace" \
|
||||
-v pi-agent-home:/root/.pi/agent \
|
||||
pi-sandbox
|
||||
```
|
||||
|
||||
The `-v "$PWD:/workspace"` mounts your current directory into the container at /workspace such that reads and writes in `/workspace` inside Docker directly affect your host files, like in the Gondolin example.
|
||||
|
||||
Use a named volume for `/root/.pi/agent` if you want container-local settings and sessions. Mounting your host `~/.pi/agent` exposes host auth and session files to the container.
|
||||
|
||||
## OpenShell
|
||||
|
||||
Use [NVIDIA OpenShell](https://docs.nvidia.com/openshell/about/overview) when you want a policy-controlled sandbox with filesystem, process, network, credential, and inference controls.
|
||||
OpenShell can run sandboxes through a local gateway backed by Docker, Podman, or a VM runtime, or through a remote Kubernetes gateway.
|
||||
|
||||
Every sandbox requires an active gateway.
|
||||
Register and select one before creating a sandbox:
|
||||
|
||||
```bash
|
||||
openshell gateway add <gateway-url> --name <name>
|
||||
openshell gateway select <name>
|
||||
```
|
||||
|
||||
Launch `pi` inside an OpenShell sandbox:
|
||||
|
||||
```bash
|
||||
openshell sandbox create --name pi-sandbox --from pi -- pi
|
||||
```
|
||||
|
||||
In this pattern, the whole `pi` process runs inside the sandbox.
|
||||
Built-in tools, `!` commands, and extension tools execute inside the OpenShell boundary.
|
||||
|
||||
If the gateway is remote, project files are not bind-mounted from the host, meaning writes in the sandbox are not reflected on your machine.
|
||||
Clone the repository inside the sandbox or use OpenShell file transfer commands:
|
||||
|
||||
```bash
|
||||
openshell sandbox upload pi-sandbox ./repo /workspace
|
||||
openshell sandbox download pi-sandbox /workspace/repo ./repo-out
|
||||
```
|
||||
|
||||
OpenShell providers can keep raw model API keys outside the sandbox.
|
||||
When inference routing is configured, code inside the sandbox can call `https://inference.local`, and the gateway injects the configured provider credentials upstream.
|
||||
Configure Pi to use the corresponding OpenAI-compatible or Anthropic-compatible endpoint if you want model traffic to use this route.
|
||||
@@ -43,7 +43,7 @@ export default function (pi: ExtensionAPI) {
|
||||
pi.registerProvider("my-provider", {
|
||||
name: "My Provider",
|
||||
baseUrl: "https://api.example.com",
|
||||
apiKey: "MY_API_KEY",
|
||||
apiKey: "$MY_API_KEY",
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{
|
||||
@@ -83,7 +83,7 @@ pi.registerProvider("openai", {
|
||||
pi.registerProvider("google", {
|
||||
baseUrl: "https://ai-gateway.corp.com/google",
|
||||
headers: {
|
||||
"X-Corp-Auth": "CORP_AUTH_TOKEN" // env var or literal
|
||||
"X-Corp-Auth": "$CORP_AUTH_TOKEN" // env var or literal
|
||||
}
|
||||
});
|
||||
```
|
||||
@@ -112,7 +112,7 @@ export default async function (pi: ExtensionAPI) {
|
||||
|
||||
pi.registerProvider("local-openai", {
|
||||
baseUrl: "http://localhost:1234/v1",
|
||||
apiKey: "LOCAL_OPENAI_API_KEY",
|
||||
apiKey: "$LOCAL_OPENAI_API_KEY",
|
||||
api: "openai-completions",
|
||||
models: payload.data.map((model) => ({
|
||||
id: model.id,
|
||||
@@ -132,7 +132,7 @@ This registers the fetched models before startup finishes.
|
||||
```typescript
|
||||
pi.registerProvider("my-llm", {
|
||||
baseUrl: "https://api.my-llm.com/v1",
|
||||
apiKey: "MY_LLM_API_KEY", // env var name or literal value
|
||||
apiKey: "$MY_LLM_API_KEY", // env var reference
|
||||
api: "openai-completions", // which streaming API to use
|
||||
models: [
|
||||
{
|
||||
@@ -155,6 +155,8 @@ pi.registerProvider("my-llm", {
|
||||
|
||||
When `models` is provided, it **replaces** all existing models for that provider.
|
||||
|
||||
`apiKey` and custom header values use the same config value syntax as `models.json`: `!command` at the start executes a command for the whole value, `$ENV_VAR` and `${ENV_VAR}` interpolate environment variables, `$$` emits a literal `$`, and `$!` emits a literal `!`.
|
||||
|
||||
## Unregister Provider
|
||||
|
||||
Use `pi.unregisterProvider(name)` to remove a provider that was previously registered via `pi.registerProvider(name, ...)`:
|
||||
@@ -163,7 +165,7 @@ Use `pi.unregisterProvider(name)` to remove a provider that was previously regis
|
||||
// Register
|
||||
pi.registerProvider("my-llm", {
|
||||
baseUrl: "https://api.my-llm.com/v1",
|
||||
apiKey: "MY_LLM_API_KEY",
|
||||
apiKey: "$MY_LLM_API_KEY",
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{
|
||||
@@ -227,9 +229,11 @@ models: [{
|
||||
}]
|
||||
```
|
||||
|
||||
Use `openrouter` for OpenRouter-style `reasoning: { effort }` controls. Use `together` for Together-style `reasoning: { enabled }` controls; with `supportsReasoningEffort`, it also sends `reasoning_effort`. Use `qwen-chat-template` instead for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`.
|
||||
Use `openrouter` for OpenRouter-style `reasoning: { effort }` controls. Use `together` for Together-style `reasoning: { enabled }` controls; with `supportsReasoningEffort`, it also sends `reasoning_effort`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking` and need `preserve_thinking`.
|
||||
Use `cacheControlFormat: "anthropic"` for OpenAI-compatible providers that expose Anthropic-style prompt caching via `cache_control` on the system prompt, last tool definition, and last user/assistant text content.
|
||||
|
||||
For Anthropic-compatible providers using `api: "anthropic-messages"`, set `compat.forceAdaptiveThinking: true` on models or providers whose upstream model requires adaptive thinking (`thinking.type: "adaptive"` plus `output_config.effort`). Built-in adaptive Claude models set this automatically. Set `compat.allowEmptySignature: true` only for providers that emit empty thinking signatures and expect `signature: ""` on replay.
|
||||
|
||||
> Migration note: Mistral moved from `openai-completions` to `mistral-conversations`.
|
||||
> Use `mistral-conversations` for native Mistral models.
|
||||
> If you intentionally route Mistral-compatible/custom endpoints through `openai-completions`, set `compat` flags explicitly as needed.
|
||||
@@ -241,7 +245,7 @@ If your provider expects `Authorization: Bearer <key>` but doesn't use a standar
|
||||
```typescript
|
||||
pi.registerProvider("custom-api", {
|
||||
baseUrl: "https://api.example.com",
|
||||
apiKey: "MY_API_KEY",
|
||||
apiKey: "$MY_API_KEY",
|
||||
authHeader: true, // adds Authorization: Bearer header
|
||||
api: "openai-completions",
|
||||
models: [...]
|
||||
@@ -263,17 +267,28 @@ pi.registerProvider("corporate-ai", {
|
||||
name: "Corporate AI (SSO)",
|
||||
|
||||
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
||||
// Option 1: Browser-based OAuth
|
||||
callbacks.onAuth({ url: "https://sso.corp.com/authorize?..." });
|
||||
|
||||
// Option 2: Device code flow
|
||||
callbacks.onDeviceCode({
|
||||
userCode: "ABCD-1234",
|
||||
verificationUri: "https://sso.corp.com/device"
|
||||
const method = await callbacks.onSelect({
|
||||
message: "Select login method:",
|
||||
options: [
|
||||
{ id: "browser", label: "Browser OAuth" },
|
||||
{ id: "device", label: "Device code" }
|
||||
]
|
||||
});
|
||||
if (!method) throw new Error("Login cancelled");
|
||||
|
||||
// Option 3: Prompt for token/code
|
||||
const code = await callbacks.onPrompt({ message: "Enter SSO code:" });
|
||||
let code: string;
|
||||
if (method === "device") {
|
||||
callbacks.onDeviceCode({
|
||||
userCode: "ABCD-1234",
|
||||
verificationUri: "https://sso.corp.com/device",
|
||||
intervalSeconds: 5,
|
||||
expiresInSeconds: 900
|
||||
});
|
||||
code = await pollDeviceCodeUntilComplete();
|
||||
} else {
|
||||
callbacks.onAuth({ url: "https://sso.corp.com/authorize?..." });
|
||||
code = await callbacks.onPrompt({ message: "Enter SSO code:" });
|
||||
}
|
||||
|
||||
// Exchange for tokens (your implementation)
|
||||
const tokens = await exchangeCodeForTokens(code);
|
||||
@@ -322,10 +337,21 @@ interface OAuthLoginCallbacks {
|
||||
onAuth(params: { url: string }): void;
|
||||
|
||||
// Show device code (for device authorization flow)
|
||||
onDeviceCode(params: { userCode: string; verificationUri: string }): void;
|
||||
onDeviceCode(params: {
|
||||
userCode: string;
|
||||
verificationUri: string;
|
||||
intervalSeconds?: number;
|
||||
expiresInSeconds?: number;
|
||||
}): void;
|
||||
|
||||
// Prompt user for input (for manual token entry)
|
||||
onPrompt(params: { message: string }): Promise<string>;
|
||||
|
||||
// Show an interactive selector, e.g. to choose browser OAuth vs device code
|
||||
onSelect(params: {
|
||||
message: string;
|
||||
options: { id: string; label: string }[];
|
||||
}): Promise<string | undefined>;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -568,7 +594,7 @@ Register your stream function:
|
||||
```typescript
|
||||
pi.registerProvider("my-provider", {
|
||||
baseUrl: "https://api.example.com",
|
||||
apiKey: "MY_API_KEY",
|
||||
apiKey: "$MY_API_KEY",
|
||||
api: "my-custom-api",
|
||||
models: [...],
|
||||
streamSimple: streamMyProvider
|
||||
@@ -605,7 +631,7 @@ interface ProviderConfig {
|
||||
/** API endpoint URL. Required when defining models. */
|
||||
baseUrl?: string;
|
||||
|
||||
/** API key or environment variable name. Required when defining models (unless oauth). */
|
||||
/** API key literal, env interpolation ($ENV_VAR or ${ENV_VAR}), or !command. Required when defining models (unless oauth). */
|
||||
apiKey?: string;
|
||||
|
||||
/** API type for streaming. Required at provider or model level when defining models. */
|
||||
@@ -618,7 +644,7 @@ interface ProviderConfig {
|
||||
options?: SimpleStreamOptions
|
||||
) => AssistantMessageEventStream;
|
||||
|
||||
/** Custom headers to include in requests. Values can be env var names. */
|
||||
/** Custom headers to include in requests. Values use the same resolution syntax as apiKey. */
|
||||
headers?: Record<string, string>;
|
||||
|
||||
/** If true, adds Authorization: Bearer header with the resolved API key. */
|
||||
@@ -680,8 +706,9 @@ interface ProviderModelConfig {
|
||||
/** Custom headers for this specific model. */
|
||||
headers?: Record<string, string>;
|
||||
|
||||
/** OpenAI compatibility settings for openai-completions API. */
|
||||
/** Compatibility settings for the selected API. */
|
||||
compat?: {
|
||||
// openai-completions
|
||||
supportsStore?: boolean;
|
||||
supportsDeveloperRole?: boolean;
|
||||
supportsReasoningEffort?: boolean;
|
||||
@@ -691,11 +718,20 @@ interface ProviderModelConfig {
|
||||
requiresAssistantAfterToolResult?: boolean;
|
||||
requiresThinkingAsText?: boolean;
|
||||
requiresReasoningContentOnAssistantMessages?: boolean;
|
||||
thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "zai" | "qwen" | "qwen-chat-template";
|
||||
thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "zai" | "qwen" | "chat-template" | "qwen-chat-template" | "string-thinking" | "ant-ling";
|
||||
chatTemplateKwargs?: Record<string, string | number | boolean | null | { "$var": "thinking.enabled" | "thinking.effort"; omitWhenOff?: boolean }>;
|
||||
cacheControlFormat?: "anthropic";
|
||||
|
||||
// anthropic-messages
|
||||
supportsEagerToolInputStreaming?: boolean;
|
||||
supportsLongCacheRetention?: boolean;
|
||||
sendSessionAffinityHeaders?: boolean;
|
||||
supportsCacheControlOnTools?: boolean;
|
||||
forceAdaptiveThinking?: boolean;
|
||||
allowEmptySignature?: boolean;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
`openrouter` sends `reasoning: { effort }`. `deepseek` sends `thinking: { type: "enabled" | "disabled" }` and `reasoning_effort` when enabled. `together` sends `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` is for DashScope-style top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`.
|
||||
`openrouter` sends `reasoning: { effort }`. `deepseek` sends `thinking: { type: "enabled" | "disabled" }` and `reasoning_effort` when enabled. `together` sends `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` is for DashScope-style top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking` and need `preserve_thinking`. Use `chat-template` for configurable `chat_template_kwargs`, for example DeepSeek V3.x behind vLLM with `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }`.
|
||||
`cacheControlFormat: "anthropic"` applies Anthropic-style `cache_control` markers to the system prompt, last tool definition, and last user/assistant text content.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Development
|
||||
|
||||
See [AGENTS.md](../../../AGENTS.md) for additional guidelines.
|
||||
See [AGENTS.md](https://github.com/earendil-works/pi-mono/blob/main/AGENTS.md) for additional guidelines.
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
"title": "Providers",
|
||||
"path": "providers.md"
|
||||
},
|
||||
{
|
||||
"title": "Security",
|
||||
"path": "security.md"
|
||||
},
|
||||
{
|
||||
"title": "Containerization",
|
||||
"path": "containerization.md"
|
||||
},
|
||||
{
|
||||
"title": "Settings",
|
||||
"path": "settings.md"
|
||||
|
||||
@@ -109,7 +109,7 @@ pi -e ./my-extension.ts
|
||||
|
||||
> **Security:** Extensions run with your full system permissions and can execute arbitrary code. Only install from sources you trust.
|
||||
|
||||
Extensions are auto-discovered from:
|
||||
Extensions are auto-discovered from trusted locations. Project-local `.pi/extensions` entries load only after the project is trusted.
|
||||
|
||||
| Location | Scope |
|
||||
|----------|-------|
|
||||
@@ -199,7 +199,7 @@ export default async function (pi: ExtensionAPI) {
|
||||
|
||||
pi.registerProvider("local-openai", {
|
||||
baseUrl: "http://localhost:1234/v1",
|
||||
apiKey: "LOCAL_OPENAI_API_KEY",
|
||||
apiKey: "$LOCAL_OPENAI_API_KEY",
|
||||
api: "openai-completions",
|
||||
models: payload.data.map((model) => ({
|
||||
id: model.id,
|
||||
@@ -216,6 +216,12 @@ export default async function (pi: ExtensionAPI) {
|
||||
|
||||
This pattern makes the fetched models available during normal startup and to `pi --list-models`.
|
||||
|
||||
### Long-lived resources and shutdown
|
||||
|
||||
Extension factories may run in invocations that never start a session. Do not start background resources such as processes, sockets, file watchers, or timers from the factory.
|
||||
|
||||
Defer background resource startup until `session_start` or the command/tool/event that needs the resource. Register an idempotent `session_shutdown` handler to close any session-scoped resources you start.
|
||||
|
||||
### Extension Styles
|
||||
|
||||
**Single file** - simplest, for small extensions:
|
||||
@@ -270,6 +276,7 @@ Run `npm install` in the extension directory, then imports from `node_modules/`
|
||||
```
|
||||
pi starts
|
||||
│
|
||||
├─► project_trust (user/global and CLI extensions only, before project resources load)
|
||||
├─► session_start { reason: "startup" }
|
||||
└─► resources_discover { reason: "startup" }
|
||||
│
|
||||
@@ -334,6 +341,25 @@ exit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM)
|
||||
└─► session_shutdown
|
||||
```
|
||||
|
||||
### Startup Events
|
||||
|
||||
#### project_trust
|
||||
|
||||
Fired before pi decides whether to trust a project with dynamic configs (`.pi` or `.agents/skills`). It runs during startup and when session replacement (for example `/resume`) enters a cwd whose trust has not been resolved in the current process. Only user/global extensions and CLI `-e` extensions participate; project-local extensions are not loaded until after trust is resolved.
|
||||
|
||||
```typescript
|
||||
pi.on("project_trust", async (event, ctx) => {
|
||||
// event.cwd - current working directory
|
||||
// ctx has a limited trust context: cwd, mode, hasUI, and select/confirm/input/notify UI helpers
|
||||
if (await ctx.ui.confirm("Trust project?", event.cwd)) {
|
||||
return { trusted: "yes", remember: true };
|
||||
}
|
||||
return { trusted: "undecided" };
|
||||
});
|
||||
```
|
||||
|
||||
A `project_trust` handler must return `{ trusted: "yes" | "no" | "undecided" }`. A user/global or CLI extension that returns `"yes"` or `"no"` owns the decision; the first yes/no decision wins and suppresses the built-in trust prompt. Use `remember: true` to persist a yes/no decision; otherwise it applies only to the current process. Return `"undecided"` to let later handlers or the built-in trust flow decide. Check `ctx.hasUI` before prompting. If no handler returns yes/no, normal trust resolution continues: saved `trust.json` decisions apply first, then `defaultProjectTrust` controls whether pi asks, trusts, or declines by default.
|
||||
|
||||
### Resource Events
|
||||
|
||||
#### resources_discover
|
||||
@@ -451,7 +477,7 @@ pi.on("session_tree", async (event, ctx) => {
|
||||
|
||||
#### session_shutdown
|
||||
|
||||
Fired before an extension runtime is torn down.
|
||||
Fired before a started session runtime is torn down. Use this to clean up resources opened from `session_start` or other session-scoped hooks.
|
||||
|
||||
```typescript
|
||||
pi.on("session_shutdown", async (event, ctx) => {
|
||||
@@ -819,6 +845,9 @@ pi.on("input", async (event, ctx) => {
|
||||
// event.text - raw input (before skill/template expansion)
|
||||
// event.images - attached images, if any
|
||||
// event.source - "interactive" (typed), "rpc" (API), or "extension" (via sendUserMessage)
|
||||
// event.streamingBehavior - "steer" | "followUp" | undefined
|
||||
// undefined when idle, "steer" for mid-stream interrupts,
|
||||
// "followUp" for messages queued until the agent finishes
|
||||
|
||||
// Transform: rewrite input before expansion
|
||||
if (event.text.startsWith("?quick "))
|
||||
@@ -847,7 +876,7 @@ pi.on("input", async (event, ctx) => {
|
||||
- `transform` - modify text/images, then continue to expansion
|
||||
- `handled` - skip agent entirely (first handler to return this wins)
|
||||
|
||||
Transforms chain across handlers. See [input-transform.ts](../examples/extensions/input-transform.ts).
|
||||
Transforms chain across handlers. See [input-transform.ts](../examples/extensions/input-transform.ts) and [input-transform-streaming.ts](../examples/extensions/input-transform-streaming.ts) for `streamingBehavior`-aware routing.
|
||||
|
||||
## ExtensionContext
|
||||
|
||||
@@ -857,14 +886,38 @@ All handlers receive `ctx: ExtensionContext`.
|
||||
|
||||
UI methods for user interaction. See [Custom UI](#custom-ui) for full details.
|
||||
|
||||
### ctx.mode
|
||||
|
||||
Current run mode: `"tui"`, `"rpc"`, `"json"`, or `"print"`. Use `ctx.mode === "tui"` to guard terminal-only features such as `custom()`, component factories, terminal input, and direct TUI rendering.
|
||||
|
||||
### ctx.hasUI
|
||||
|
||||
`false` in print mode (`-p`) and JSON mode. `true` in interactive and RPC mode. In RPC mode, dialog methods (`select`, `confirm`, `input`, `editor`) work via the extension UI sub-protocol, and fire-and-forget methods (`notify`, `setStatus`, `setWidget`, `setTitle`, `setEditorText`) emit requests to the client. Some TUI-specific methods are no-ops or return defaults (see [rpc.md](rpc.md#extension-ui-protocol)).
|
||||
`true` in TUI and RPC modes. `false` in print mode (`-p`) and JSON mode. Use this to guard dialog methods (`select`, `confirm`, `input`, `editor`) and fire-and-forget methods (`notify`, `setStatus`, `setWidget`, `setTitle`, `setEditorText`) that work in both TUI and RPC modes. In RPC mode, some TUI-specific methods are no-ops or return defaults (see [rpc.md](rpc.md#extension-ui-protocol)).
|
||||
|
||||
### ctx.cwd
|
||||
|
||||
Current working directory.
|
||||
|
||||
Use `CONFIG_DIR_NAME` instead of hardcoding `.pi` when constructing project-local config paths. Rebranded distributions can use a different config directory name.
|
||||
|
||||
```typescript
|
||||
import { CONFIG_DIR_NAME, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import { join } from "node:path";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.on("session_start", (_event, ctx) => {
|
||||
const projectConfigPath = join(ctx.cwd, CONFIG_DIR_NAME, "my-extension.json");
|
||||
// ...
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### ctx.isProjectTrusted()
|
||||
|
||||
Returns whether project-local trust is active for the current session context. This includes temporary trust decisions and CLI trust overrides, not just saved decisions in the global trust store.
|
||||
|
||||
Use this before reading project-local extension configuration that should only be honored for trusted projects.
|
||||
|
||||
### ctx.sessionManager
|
||||
|
||||
Read-only access to session state. See [Session Format](session-format.md) for the full SessionManager API and entry types.
|
||||
@@ -975,6 +1028,19 @@ pi.on("before_agent_start", (event, ctx) => {
|
||||
|
||||
Command handlers receive `ExtensionCommandContext`, which extends `ExtensionContext` with session control methods. These are only available in commands because they can deadlock if called from event handlers.
|
||||
|
||||
### ctx.getSystemPromptOptions()
|
||||
|
||||
Returns the base inputs Pi currently uses to build the system prompt.
|
||||
|
||||
```typescript
|
||||
const options = ctx.getSystemPromptOptions();
|
||||
const contextPaths = options.contextFiles?.map((file) => file.path) ?? [];
|
||||
```
|
||||
|
||||
This has the same shape and mutability as `before_agent_start` `event.systemPromptOptions`: custom prompt, active tools, tool snippets, prompt guidelines, appended system prompt text, cwd, loaded context files, and loaded skills. It may include full context file contents, so treat it as sensitive extension-local data and avoid exposing it through command lists, logs, or autocomplete metadata.
|
||||
|
||||
This reports the current base prompt inputs. It does not include per-turn `before_agent_start` chained system-prompt changes, later `context` event message mutations, or `before_provider_request` payload rewrites.
|
||||
|
||||
### ctx.waitForIdle()
|
||||
|
||||
Wait for the agent to finish streaming:
|
||||
@@ -1482,24 +1548,25 @@ const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
|
||||
|
||||
### pi.getActiveTools() / pi.getAllTools() / pi.setActiveTools(names)
|
||||
|
||||
Manage active tools. This works for both built-in tools and dynamically registered tools.
|
||||
Manage active tools. This works for both built-in tools and dynamically registered tools. `pi.getActiveTools()` returns the active tool names as `string[]`; `pi.getAllTools()` returns metadata for all configured tools.
|
||||
|
||||
```typescript
|
||||
const active = pi.getActiveTools();
|
||||
const active = pi.getActiveTools(); // ["read", "bash", ...]
|
||||
const all = pi.getAllTools();
|
||||
// [{
|
||||
// all = [{
|
||||
// name: "read",
|
||||
// description: "Read file contents...",
|
||||
// parameters: ...,
|
||||
// parameters: ...,
|
||||
// promptGuidelines: ["Use read to examine files instead of cat or sed."],
|
||||
// sourceInfo: { path: "<builtin:read>", source: "builtin", scope: "temporary", origin: "top-level" }
|
||||
// }, ...]
|
||||
const names = all.map(t => t.name);
|
||||
const builtinTools = all.filter((t) => t.sourceInfo.source === "builtin");
|
||||
const extensionTools = all.filter((t) => t.sourceInfo.source !== "builtin" && t.sourceInfo.source !== "sdk");
|
||||
pi.setActiveTools([...new Set([...active, "my_custom_tool"])]); // Keep current tools and enable my_custom_tool
|
||||
pi.setActiveTools(["read", "bash"]); // Switch to read-only
|
||||
```
|
||||
|
||||
`pi.getAllTools()` returns `name`, `description`, `parameters`, and `sourceInfo`.
|
||||
`pi.getAllTools()` returns `name`, `description`, `parameters`, `promptGuidelines`, and `sourceInfo`.
|
||||
|
||||
Typical `sourceInfo.source` values:
|
||||
- `builtin` for built-in tools
|
||||
@@ -1551,7 +1618,7 @@ If you need to discover models from a remote endpoint, prefer an async extension
|
||||
pi.registerProvider("my-proxy", {
|
||||
name: "My Proxy",
|
||||
baseUrl: "https://proxy.example.com",
|
||||
apiKey: "PROXY_API_KEY", // env var name or literal
|
||||
apiKey: "$PROXY_API_KEY", // env var reference
|
||||
api: "anthropic-messages",
|
||||
models: [
|
||||
{
|
||||
@@ -1598,7 +1665,7 @@ pi.registerProvider("corporate-ai", {
|
||||
**Config options:**
|
||||
- `name` - Display name for the provider in UI such as `/login`.
|
||||
- `baseUrl` - API endpoint URL. Required when defining models.
|
||||
- `apiKey` - API key or environment variable name. Required when defining models (unless `oauth` provided).
|
||||
- `apiKey` - API key literal, environment interpolation (`$ENV_VAR` or `${ENV_VAR}`), or leading `!command`. Required when defining models (unless `oauth` provided). `$$` escapes `$`, and `$!` escapes a literal `!` without triggering command execution.
|
||||
- `api` - API type: `"anthropic-messages"`, `"openai-completions"`, `"openai-responses"`, etc.
|
||||
- `headers` - Custom headers to include in requests.
|
||||
- `authHeader` - If true, adds `Authorization: Bearer` header automatically.
|
||||
@@ -2234,6 +2301,7 @@ ctx.ui.pasteToEditor("pasted content");
|
||||
|
||||
// Stack custom autocomplete behavior on top of the built-in provider
|
||||
ctx.ui.addAutocompleteProvider((current) => ({
|
||||
triggerCharacters: ["#"],
|
||||
async getSuggestions(lines, line, col, options) {
|
||||
const beforeCursor = (lines[line] ?? "").slice(0, col);
|
||||
const match = beforeCursor.match(/(?:^|[ \t])#([^\s#]*)$/);
|
||||
@@ -2282,7 +2350,7 @@ Custom working-indicator frames are rendered verbatim. If you want colors, add t
|
||||
|
||||
### Autocomplete Providers
|
||||
|
||||
Use `ctx.ui.addAutocompleteProvider()` to stack custom autocomplete logic on top of the built-in slash-command and path provider.
|
||||
Use `ctx.ui.addAutocompleteProvider()` to stack custom autocomplete logic on top of the built-in slash-command and path provider. Set `triggerCharacters` for custom natural triggers such as `$`.
|
||||
|
||||
Typical pattern:
|
||||
|
||||
@@ -2294,6 +2362,7 @@ Typical pattern:
|
||||
```typescript
|
||||
pi.on("session_start", (_event, ctx) => {
|
||||
ctx.ui.addAutocompleteProvider((current) => ({
|
||||
triggerCharacters: ["#"],
|
||||
async getSuggestions(lines, cursorLine, cursorCol, options) {
|
||||
const line = lines[cursorLine] ?? "";
|
||||
const beforeCursor = line.slice(0, cursorCol);
|
||||
@@ -2367,7 +2436,7 @@ const result = await ctx.ui.custom<string | null>(
|
||||
);
|
||||
```
|
||||
|
||||
For advanced positioning (anchors, margins, percentages, responsive visibility), pass `overlayOptions`. Use `onHandle` to control visibility programmatically:
|
||||
For advanced positioning (anchors, margins, percentages, responsive visibility), pass `overlayOptions`. Use `onHandle` to control focus or visibility programmatically:
|
||||
|
||||
```typescript
|
||||
const result = await ctx.ui.custom<string | null>(
|
||||
@@ -2375,12 +2444,19 @@ const result = await ctx.ui.custom<string | null>(
|
||||
{
|
||||
overlay: true,
|
||||
overlayOptions: { anchor: "top-right", width: "50%", margin: 2 },
|
||||
onHandle: (handle) => { /* handle.setHidden(true/false) */ }
|
||||
onHandle: (handle) => {
|
||||
handle.focus(); // focus this overlay and bring it to the visual front
|
||||
// handle.unfocus({ target: editorComponent }); // release input to a specific component
|
||||
// handle.setHidden(true/false); // toggle visibility
|
||||
// handle.hide(); // permanently remove
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
See [tui.md](tui.md) for the full `OverlayOptions` API and [overlay-qa-tests.ts](../examples/extensions/overlay-qa-tests.ts) for examples.
|
||||
A focused visible overlay can reclaim input after temporary non-overlay custom UI closes. If you intentionally want another component to keep input while the overlay stays visible, call `handle.unfocus({ target })`. Passing `{ target: null }` releases the overlay without focusing another component.
|
||||
|
||||
See [tui.md](tui.md) for the full `OverlayOptions` and `OverlayHandle` API and [overlay-qa-tests.ts](../examples/extensions/overlay-qa-tests.ts) for examples.
|
||||
|
||||
### Custom Editor
|
||||
|
||||
@@ -2505,14 +2581,14 @@ const highlighted = highlightCode(code, lang, theme);
|
||||
|
||||
## Mode Behavior
|
||||
|
||||
| Mode | UI Methods | Notes |
|
||||
|------|-----------|-------|
|
||||
| Interactive | Full TUI | Normal operation |
|
||||
| RPC (`--mode rpc`) | JSON protocol | Host handles UI, see [rpc.md](rpc.md) |
|
||||
| JSON (`--mode json`) | No-op | Event stream to stdout, see [json.md](json.md) |
|
||||
| Print (`-p`) | No-op | Extensions run but can't prompt |
|
||||
| Mode | `ctx.mode` | `ctx.hasUI` | Notes |
|
||||
|------|------------|-------------|-------|
|
||||
| Interactive | `"tui"` | `true` | Full TUI with terminal rendering |
|
||||
| RPC (`--mode rpc`) | `"rpc"` | `true` | Dialogs and notifications via JSON protocol; `custom()` returns `undefined`. See [rpc.md](rpc.md) |
|
||||
| JSON (`--mode json`) | `"json"` | `false` | Event stream to stdout; UI methods are no-ops |
|
||||
| Print (`-p`) | `"print"` | `false` | Extensions run but can't prompt |
|
||||
|
||||
In non-interactive modes, check `ctx.hasUI` before using UI methods.
|
||||
Use `ctx.mode === "tui"` before TUI-specific features (`custom()`, component factories, terminal input). Use `ctx.hasUI` before dialog and notification methods that work in both TUI and RPC modes.
|
||||
|
||||
## Examples Reference
|
||||
|
||||
@@ -2539,10 +2615,12 @@ All examples in [examples/extensions/](../examples/extensions/).
|
||||
| `shutdown-command.ts` | Graceful shutdown command | `registerCommand`, `shutdown()` |
|
||||
| **Events & Gates** |||
|
||||
| `permission-gate.ts` | Block dangerous commands | `on("tool_call")`, `ui.confirm` |
|
||||
| `project-trust.ts` | Decide or defer project trust from a user/global or CLI extension | `on("project_trust")`, trust UI, required trust result |
|
||||
| `protected-paths.ts` | Block writes to specific paths | `on("tool_call")` |
|
||||
| `confirm-destructive.ts` | Confirm session changes | `on("session_before_switch")`, `on("session_before_fork")` |
|
||||
| `dirty-repo-guard.ts` | Warn on dirty git repo | `on("session_before_*")`, `exec` |
|
||||
| `input-transform.ts` | Transform user input | `on("input")` |
|
||||
| `input-transform-streaming.ts` | Streaming-aware input transform | `on("input")`, `streamingBehavior` |
|
||||
| `model-status.ts` | React to model changes | `on("model_select")`, `setStatus` |
|
||||
| `provider-payload.ts` | Inspect payloads and provider response headers | `on("before_provider_request")`, `on("after_provider_response")` |
|
||||
| `system-prompt-header.ts` | Display system prompt info | `on("agent_start")`, `getSystemPrompt` |
|
||||
@@ -2553,6 +2631,7 @@ All examples in [examples/extensions/](../examples/extensions/).
|
||||
| `custom-compaction.ts` | Custom compaction summary | `on("session_before_compact")` |
|
||||
| `trigger-compact.ts` | Trigger compaction manually | `compact()` |
|
||||
| `git-checkpoint.ts` | Git stash on turns | `on("turn_start")`, `on("session_before_fork")`, `exec` |
|
||||
| `git-merge-and-resolve.ts` | Fetch, merge, and resolve conflicts | `on("agent_end")`, `exec`, `sendUserMessage` |
|
||||
| `auto-commit-on-exit.ts` | Commit on shutdown | `on("session_shutdown")`, `exec` |
|
||||
| **UI Components** |||
|
||||
| `status-line.ts` | Footer status indicator | `setStatus`, session events |
|
||||
@@ -2576,6 +2655,7 @@ All examples in [examples/extensions/](../examples/extensions/).
|
||||
| `ssh.ts` | SSH remote execution | `registerFlag`, `on("user_bash")`, `on("before_agent_start")`, tool operations |
|
||||
| `interactive-shell.ts` | Persistent shell session | `on("user_bash")` |
|
||||
| `sandbox/` | Sandboxed tool execution | Tool operations |
|
||||
| `gondolin/` | Route built-in tools and `!` commands into a Gondolin micro-VM | Tool operations, built-in tool overrides, `on("user_bash")` |
|
||||
| `subagent/` | Spawn sub-agents | `registerTool`, `exec` |
|
||||
| **Games** |||
|
||||
| `snake.ts` | Snake game | `registerCommand`, `ui.custom`, keyboard handling |
|
||||
|
||||
@@ -41,6 +41,8 @@ For the full first-run flow, see [Quickstart](quickstart.md).
|
||||
- [Quickstart](quickstart.md) - install, authenticate, and run a first session.
|
||||
- [Using Pi](usage.md) - interactive mode, slash commands, context files, and CLI reference.
|
||||
- [Providers](providers.md) - subscription and API-key setup for built-in providers.
|
||||
- [Security](security.md) - project trust, sandbox boundaries, and vulnerability reporting.
|
||||
- [Containerization](containerization.md) - sandbox pi with Gondolin, Docker, or OpenShell.
|
||||
- [Settings](settings.md) - global and project settings.
|
||||
- [Keybindings](keybindings.md) - default shortcuts and custom keybindings.
|
||||
- [Sessions](sessions.md) - session management, branching, and tree navigation.
|
||||
|
||||
@@ -101,7 +101,7 @@ Use `google-generative-ai` with a `baseUrl` to add models from Google AI Studio,
|
||||
"my-google": {
|
||||
"baseUrl": "https://generativelanguage.googleapis.com/v1beta",
|
||||
"api": "google-generative-ai",
|
||||
"apiKey": "GEMINI_API_KEY",
|
||||
"apiKey": "$GEMINI_API_KEY",
|
||||
"models": [
|
||||
{
|
||||
"id": "gemma-4-31b-it",
|
||||
@@ -143,18 +143,25 @@ Set `api` at provider level (default for all models) or model level (override pe
|
||||
|
||||
### Value Resolution
|
||||
|
||||
The `apiKey` and `headers` fields support three formats:
|
||||
The `apiKey` and `headers` fields support command execution, environment interpolation, and literals:
|
||||
|
||||
- **Shell command:** `"!command"` executes and uses stdout
|
||||
- **Shell command:** `"!command"` at the start executes the whole value as a command and uses stdout
|
||||
```json
|
||||
"apiKey": "!security find-generic-password -ws 'anthropic'"
|
||||
"apiKey": "!op read 'op://vault/item/credential'"
|
||||
```
|
||||
- **Environment variable:** Uses the value of the named variable
|
||||
- **Environment interpolation:** `"$ENV_VAR"` or `"${ENV_VAR}"` uses the value of the named variable. Interpolation works inside larger literals.
|
||||
```json
|
||||
"apiKey": "MY_API_KEY"
|
||||
"apiKey": "$MY_API_KEY"
|
||||
"apiKey": "${KEY_PREFIX}_${KEY_SUFFIX}"
|
||||
```
|
||||
- **Literal value:** Used directly
|
||||
`$FOO_BAR` is the variable `FOO_BAR`; use `${FOO}_BAR` when `BAR` is literal text. Missing environment variables make the value unresolved.
|
||||
- **Escapes:** `"$$"` emits a literal `"$"`; `"$!"` emits a literal `"!"` without triggering command execution.
|
||||
```json
|
||||
"apiKey": "$$literal-dollar-prefix"
|
||||
"apiKey": "$!literal-bang-prefix"
|
||||
```
|
||||
- **Literal value:** Used directly. Plain uppercase strings such as `MY_API_KEY` are literals; use `$MY_API_KEY` for environment variables.
|
||||
```json
|
||||
"apiKey": "sk-..."
|
||||
```
|
||||
@@ -172,10 +179,10 @@ If your command is slow, expensive, rate-limited, or should keep using a previou
|
||||
"providers": {
|
||||
"custom-proxy": {
|
||||
"baseUrl": "https://proxy.example.com/v1",
|
||||
"apiKey": "MY_API_KEY",
|
||||
"apiKey": "$MY_API_KEY",
|
||||
"api": "anthropic-messages",
|
||||
"headers": {
|
||||
"x-portkey-api-key": "PORTKEY_API_KEY",
|
||||
"x-portkey-api-key": "$PORTKEY_API_KEY",
|
||||
"x-secret": "!op read 'op://vault/item/secret'"
|
||||
},
|
||||
"models": [...]
|
||||
@@ -189,7 +196,7 @@ If your command is slow, expensive, rate-limited, or should keep using a previou
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `id` | Yes | — | Model identifier (passed to the API) |
|
||||
| `name` | No | `id` | Human-readable model label. Used for matching (`--model` patterns) and shown in model details/status text. |
|
||||
| `name` | No | `id` | Human-readable model label. Used for matching (`--model` patterns) and shown as secondary model detail text. |
|
||||
| `api` | No | provider's `api` | Override provider's API for this model |
|
||||
| `reasoning` | No | `false` | Supports extended thinking |
|
||||
| `thinkingLevelMap` | No | omitted | Maps pi thinking levels to provider values and marks unsupported levels (see below) |
|
||||
@@ -200,8 +207,8 @@ If your command is slow, expensive, rate-limited, or should keep using a previou
|
||||
| `compat` | No | provider `compat` | Provider compatibility overrides. Merged with provider-level `compat` when both are set. |
|
||||
|
||||
Current behavior:
|
||||
- `/model` and `--list-models` list entries by model `id`.
|
||||
- The configured `name` is used for model matching and detail/status text.
|
||||
- `/model`, `--list-models`, and the interactive footer display entries by model `id`.
|
||||
- The configured `name` is used for model matching and secondary model detail text. It does not replace the footer/status-bar model id.
|
||||
|
||||
### Thinking Level Map
|
||||
|
||||
@@ -268,7 +275,7 @@ To merge custom models into a built-in provider, include the `models` array:
|
||||
"providers": {
|
||||
"anthropic": {
|
||||
"baseUrl": "https://my-proxy.example.com/v1",
|
||||
"apiKey": "ANTHROPIC_API_KEY",
|
||||
"apiKey": "$ANTHROPIC_API_KEY",
|
||||
"api": "anthropic-messages",
|
||||
"models": [...]
|
||||
}
|
||||
@@ -311,24 +318,31 @@ Behavior notes:
|
||||
- `modelOverrides` are applied to built-in provider models.
|
||||
- Unknown model IDs are ignored.
|
||||
- You can combine provider-level `baseUrl`/`headers` with `modelOverrides`.
|
||||
- Overriding `name` changes model matching and secondary detail text only; the footer and primary model lists continue to show the model `id`.
|
||||
- If `models` is also defined for a provider, custom models are merged after built-in overrides. A custom model with the same `id` replaces the overridden built-in model entry.
|
||||
|
||||
## Anthropic Messages Compatibility
|
||||
|
||||
For providers or proxies using `api: "anthropic-messages"`, use `compat.supportsEagerToolInputStreaming` to control Anthropic fine-grained tool streaming compatibility.
|
||||
For providers or proxies using `api: "anthropic-messages"`, use `compat` to control Anthropic-specific request compatibility.
|
||||
|
||||
By default pi sends per-tool `eager_input_streaming: true`. If a proxy or Anthropic-compatible backend rejects that field, set `supportsEagerToolInputStreaming` to `false`. Pi will omit `tools[].eager_input_streaming` and send the legacy `fine-grained-tool-streaming-2025-05-14` beta header for tool-enabled requests instead.
|
||||
|
||||
Some Anthropic models require adaptive thinking (`thinking.type: "adaptive"` plus `output_config.effort`) instead of the legacy budget-based thinking payload. Built-in models set this automatically. For custom providers or aliases that route to those models, set `forceAdaptiveThinking` to `true`.
|
||||
|
||||
Some Anthropic-compatible providers emit thinking blocks with empty signatures and still expect them on replay. Set `allowEmptySignature` to `true` only for those providers; real Anthropic rejects empty thinking signatures.
|
||||
|
||||
```json
|
||||
{
|
||||
"providers": {
|
||||
"anthropic-proxy": {
|
||||
"baseUrl": "https://proxy.example.com",
|
||||
"api": "anthropic-messages",
|
||||
"apiKey": "ANTHROPIC_PROXY_KEY",
|
||||
"apiKey": "$ANTHROPIC_PROXY_KEY",
|
||||
"compat": {
|
||||
"supportsEagerToolInputStreaming": false,
|
||||
"supportsLongCacheRetention": true
|
||||
"supportsLongCacheRetention": true,
|
||||
"forceAdaptiveThinking": true,
|
||||
"allowEmptySignature": true
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
@@ -346,6 +360,10 @@ By default pi sends per-tool `eager_input_streaming: true`. If a proxy or Anthro
|
||||
|-------|-------------|
|
||||
| `supportsEagerToolInputStreaming` | Whether the provider accepts per-tool `eager_input_streaming`. Default: `true`. Set to `false` to omit that field and use the legacy fine-grained tool streaming beta header on tool-enabled requests. |
|
||||
| `supportsLongCacheRetention` | Whether the provider accepts Anthropic long cache retention (`cache_control.ttl: "1h"`) when cache retention is `long`. Default: `true`. |
|
||||
| `sendSessionAffinityHeaders` | Whether to send `x-session-affinity` from the session id when caching is enabled. Default: auto-detected for known providers. |
|
||||
| `supportsCacheControlOnTools` | Whether the provider accepts Anthropic-style `cache_control` markers on tool definitions. Default: `true`. |
|
||||
| `forceAdaptiveThinking` | Whether to send adaptive thinking (`thinking.type: "adaptive"` plus `output_config.effort`) for this model. Built-in adaptive models set this automatically. Default: `false`. |
|
||||
| `allowEmptySignature` | Whether to replay empty thinking signatures as `signature: ""` instead of converting thinking to text. Default: `false`. |
|
||||
|
||||
## OpenAI Compatibility
|
||||
|
||||
@@ -381,14 +399,15 @@ For providers with partial OpenAI compatibility, use the `compat` field.
|
||||
| `requiresAssistantAfterToolResult` | Insert an assistant message before a user message after tool results |
|
||||
| `requiresThinkingAsText` | Convert thinking blocks to plain text |
|
||||
| `requiresReasoningContentOnAssistantMessages` | Include empty `reasoning_content` on all replayed assistant messages when reasoning is enabled |
|
||||
| `thinkingFormat` | Use `reasoning_effort`, `openrouter`, `deepseek`, `together`, `zai`, `qwen`, or `qwen-chat-template` thinking parameters |
|
||||
| `thinkingFormat` | Use `reasoning_effort`, `openrouter`, `deepseek`, `together`, `zai`, `qwen`, `chat-template`, or `qwen-chat-template` thinking parameters |
|
||||
| `chatTemplateKwargs` | `chat_template_kwargs` values for `thinkingFormat: "chat-template"`; use `{ "$var": "thinking.enabled" }` or `{ "$var": "thinking.effort" }` for pi-controlled thinking values |
|
||||
| `cacheControlFormat` | Use Anthropic-style `cache_control` markers on the system prompt, last tool definition, and last user/assistant text content. Currently only `anthropic` is supported. |
|
||||
| `supportsStrictMode` | Include the `strict` field in tool definitions |
|
||||
| `supportsLongCacheRetention` | Whether the provider accepts long cache retention when cache retention is `long`: `prompt_cache_retention: "24h"` for OpenAI prompt caching, or `cache_control.ttl: "1h"` when `cacheControlFormat` is `anthropic`. Default: `true`. |
|
||||
| `openRouterRouting` | OpenRouter provider routing preferences. This object is sent as-is in the `provider` field of the [OpenRouter API request](https://openrouter.ai/docs/guides/routing/provider-selection). |
|
||||
| `vercelGatewayRouting` | Vercel AI Gateway routing config for provider selection (`only`, `order`) |
|
||||
|
||||
`openrouter` uses `reasoning: { effort }`. `together` uses `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking`.
|
||||
`openrouter` uses `reasoning: { effort }`. `together` uses `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking` and `preserve_thinking`. Use `chat-template` for vLLM/Hugging Face chat templates that need configurable `chat_template_kwargs`, such as `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }` for DeepSeek V3.x templates.
|
||||
|
||||
`cacheControlFormat: "anthropic"` is for OpenAI-compatible providers that expose Anthropic-style prompt caching through `cache_control` markers on text content and tool definitions.
|
||||
|
||||
@@ -399,7 +418,7 @@ Example:
|
||||
"providers": {
|
||||
"openrouter": {
|
||||
"baseUrl": "https://openrouter.ai/api/v1",
|
||||
"apiKey": "OPENROUTER_API_KEY",
|
||||
"apiKey": "$OPENROUTER_API_KEY",
|
||||
"api": "openai-completions",
|
||||
"models": [
|
||||
{
|
||||
@@ -449,7 +468,7 @@ Vercel AI Gateway example:
|
||||
"providers": {
|
||||
"vercel-ai-gateway": {
|
||||
"baseUrl": "https://ai-gateway.vercel.sh/v1",
|
||||
"apiKey": "AI_GATEWAY_API_KEY",
|
||||
"apiKey": "$AI_GATEWAY_API_KEY",
|
||||
"api": "openai-completions",
|
||||
"models": [
|
||||
{
|
||||
|
||||
@@ -28,17 +28,18 @@ pi install ./relative/path/to/package
|
||||
|
||||
pi remove npm:@foo/bar
|
||||
pi list # show installed packages from settings
|
||||
pi update # update pi and all non-pinned packages
|
||||
pi update --extensions # update all non-pinned packages only
|
||||
pi update # update pi only
|
||||
pi update --all # update pi, update packages, and reconcile pinned git refs
|
||||
pi update --extensions # update packages and reconcile pinned git refs only
|
||||
pi update --self # update pi only
|
||||
pi update --self --force # reinstall pi even if current
|
||||
pi update npm:@foo/bar # update one package
|
||||
pi update --extension npm:@foo/bar
|
||||
```
|
||||
|
||||
These commands manage pi packages, not the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall).
|
||||
These commands manage pi packages and `pi update` can update the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall).
|
||||
|
||||
By default, `install` and `remove` write to user settings (`~/.pi/agent/settings.json`). Use `-l` to write to project settings (`.pi/settings.json`) instead. Project settings can be shared with your team, and pi installs any missing packages automatically on startup.
|
||||
By default, `install` and `remove` write to user settings (`~/.pi/agent/settings.json`). Use `-l` to write to project settings (`.pi/settings.json`) instead. Project settings can be shared with your team, and pi installs any missing packages automatically on startup after the project is trusted.
|
||||
|
||||
To try a package without installing it, use `--extension` or `-e`. This installs to a temporary directory for the current run only:
|
||||
|
||||
@@ -58,7 +59,7 @@ npm:@scope/pkg@1.2.3
|
||||
npm:pkg
|
||||
```
|
||||
|
||||
- Versioned specs are pinned and skipped by package updates (`pi update`, `pi update --extensions`).
|
||||
- Versioned specs are pinned and skipped by package updates (`pi update --extensions`, `pi update --all`).
|
||||
- User installs go under `~/.pi/agent/npm/`.
|
||||
- Project installs go under `.pi/npm/`.
|
||||
- Set `npmCommand` in `settings.json` to pin npm package lookup and install operations to a specific wrapper command such as `mise` or `asdf`.
|
||||
@@ -85,9 +86,10 @@ ssh://git@github.com/user/repo@v1
|
||||
- HTTPS and SSH URLs are both supported.
|
||||
- SSH URLs use your configured SSH keys automatically (respects `~/.ssh/config`).
|
||||
- For non-interactive runs (for example CI), you can set `GIT_TERMINAL_PROMPT=0` to disable credential prompts and set `GIT_SSH_COMMAND` (for example `ssh -o BatchMode=yes -o ConnectTimeout=5`) to fail fast.
|
||||
- Refs are pinned tags or commits and skip package updates (`pi update`, `pi update --extensions`). Use `pi install git:host/user/repo@new-ref` to move an existing package to a new pinned ref.
|
||||
- Refs are pinned tags or commits. `pi update --extensions` and `pi update --all` do not move them to newer refs, but they do reconcile an existing clone to the configured ref.
|
||||
- Use `pi install git:host/user/repo@new-ref` to update settings and move an existing package to a new pinned ref.
|
||||
- Cloned to `~/.pi/agent/git/<host>/<path>` (global) or `.pi/git/<host>/<path>` (project).
|
||||
- Runs `npm install` after clone, pull, or pinned ref change if `package.json` exists.
|
||||
- When reconciliation changes the checkout, pi resets and cleans the clone, then runs `npm install` if `package.json` exists.
|
||||
|
||||
**SSH examples:**
|
||||
```bash
|
||||
|
||||
@@ -9,7 +9,7 @@ Prompt templates are Markdown snippets that expand into full prompts. Type `/nam
|
||||
Pi loads prompt templates from:
|
||||
|
||||
- Global: `~/.pi/agent/prompts/*.md`
|
||||
- Project: `.pi/prompts/*.md`
|
||||
- Project: `.pi/prompts/*.md` (only after the project is trusted)
|
||||
- Packages: `prompts/` directories or `pi.prompts` entries in `package.json`
|
||||
- Settings: `prompts` array with files or directories
|
||||
- CLI: `--prompt-template <path>` (repeatable)
|
||||
@@ -64,10 +64,11 @@ Type `/` followed by the template name in the editor. Autocomplete shows availab
|
||||
|
||||
## Arguments
|
||||
|
||||
Templates support positional arguments and simple slicing:
|
||||
Templates support positional arguments, defaults, and simple slicing:
|
||||
|
||||
- `$1`, `$2`, ... positional args
|
||||
- `$@` or `$ARGUMENTS` for all args joined
|
||||
- `${1:-default}` uses arg 1 when present/non-empty, otherwise `default`
|
||||
- `${@:N}` for args from the Nth position (1-indexed)
|
||||
- `${@:N:L}` for `L` args starting at N
|
||||
|
||||
@@ -80,6 +81,12 @@ description: Create a component
|
||||
Create a React component named $1 with features: $@
|
||||
```
|
||||
|
||||
Default values are useful for optional arguments:
|
||||
|
||||
```markdown
|
||||
Summarize the current state in ${1:-7} bullet points.
|
||||
```
|
||||
|
||||
Usage: `/component Button "onClick handler" "disabled support"`
|
||||
|
||||
## Loading Rules
|
||||
|
||||
@@ -49,9 +49,11 @@ pi
|
||||
| Provider | Environment Variable | `auth.json` key |
|
||||
|----------|----------------------|------------------|
|
||||
| Anthropic | `ANTHROPIC_API_KEY` | `anthropic` |
|
||||
| Ant Ling | `ANT_LING_API_KEY` | `ant-ling` |
|
||||
| Azure OpenAI Responses | `AZURE_OPENAI_API_KEY` | `azure-openai-responses` |
|
||||
| OpenAI | `OPENAI_API_KEY` | `openai` |
|
||||
| DeepSeek | `DEEPSEEK_API_KEY` | `deepseek` |
|
||||
| NVIDIA NIM | `NVIDIA_API_KEY` | `nvidia` |
|
||||
| Google Gemini | `GEMINI_API_KEY` | `google` |
|
||||
| Mistral | `MISTRAL_API_KEY` | `mistral` |
|
||||
| Groq | `GROQ_API_KEY` | `groq` |
|
||||
@@ -62,6 +64,7 @@ pi
|
||||
| OpenRouter | `OPENROUTER_API_KEY` | `openrouter` |
|
||||
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway` |
|
||||
| ZAI | `ZAI_API_KEY` | `zai` |
|
||||
| ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` | `zai-coding-cn` |
|
||||
| OpenCode Zen | `OPENCODE_API_KEY` | `opencode` |
|
||||
| OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` |
|
||||
| Hugging Face | `HF_TOKEN` | `huggingface` |
|
||||
@@ -84,8 +87,10 @@ Store credentials in `~/.pi/agent/auth.json`:
|
||||
```json
|
||||
{
|
||||
"anthropic": { "type": "api_key", "key": "sk-ant-..." },
|
||||
"ant-ling": { "type": "api_key", "key": "..." },
|
||||
"openai": { "type": "api_key", "key": "sk-..." },
|
||||
"deepseek": { "type": "api_key", "key": "sk-..." },
|
||||
"nvidia": { "type": "api_key", "key": "nvapi-..." },
|
||||
"google": { "type": "api_key", "key": "..." },
|
||||
"opencode": { "type": "api_key", "key": "..." },
|
||||
"opencode-go": { "type": "api_key", "key": "..." },
|
||||
@@ -99,22 +104,48 @@ Store credentials in `~/.pi/agent/auth.json`:
|
||||
|
||||
The file is created with `0600` permissions (user read/write only). Auth file credentials take priority over environment variables.
|
||||
|
||||
API key credentials can also include provider-scoped environment values. These values are used before process environment variables when resolving the credential key, provider/model headers, and provider configuration such as Cloudflare account IDs, Azure OpenAI settings, Vertex project/location, Bedrock settings, `PI_CACHE_RETENTION`, and `HTTP_PROXY`/`HTTPS_PROXY`.
|
||||
|
||||
```json
|
||||
{
|
||||
"cloudflare-ai-gateway": {
|
||||
"type": "api_key",
|
||||
"key": "$CLOUDFLARE_API_KEY",
|
||||
"env": {
|
||||
"CLOUDFLARE_API_KEY": "...",
|
||||
"CLOUDFLARE_ACCOUNT_ID": "account-id",
|
||||
"CLOUDFLARE_GATEWAY_ID": "gateway-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use this when pi should use different provider settings than the project shell environment.
|
||||
|
||||
### Key Resolution
|
||||
|
||||
The `key` field supports three formats:
|
||||
The `key` field supports command execution, environment interpolation, and literals:
|
||||
|
||||
- **Shell command:** `"!command"` executes and uses stdout (cached for process lifetime)
|
||||
- **Shell command:** `"!command"` at the start executes the whole value as a command and uses stdout (cached for process lifetime)
|
||||
```json
|
||||
{ "type": "api_key", "key": "!security find-generic-password -ws 'anthropic'" }
|
||||
{ "type": "api_key", "key": "!op read 'op://vault/item/credential'" }
|
||||
```
|
||||
- **Environment variable:** Uses the value of the named variable
|
||||
- **Environment interpolation:** `"$ENV_VAR"` or `"${ENV_VAR}"` uses the value of the named variable. Interpolation works inside larger literals.
|
||||
```json
|
||||
{ "type": "api_key", "key": "MY_ANTHROPIC_KEY" }
|
||||
{ "type": "api_key", "key": "$MY_ANTHROPIC_KEY" }
|
||||
{ "type": "api_key", "key": "${KEY_PREFIX}_${KEY_SUFFIX}" }
|
||||
```
|
||||
- **Literal value:** Used directly
|
||||
`$FOO_BAR` is the variable `FOO_BAR`; use `${FOO}_BAR` when `BAR` is literal text. Missing environment variables make the value unresolved.
|
||||
- **Escapes:** `"$$"` emits a literal `"$"`; `"$!"` emits a literal `"!"` without triggering command execution.
|
||||
```json
|
||||
{ "type": "api_key", "key": "$$literal-dollar-prefix" }
|
||||
{ "type": "api_key", "key": "$!literal-bang-prefix" }
|
||||
```
|
||||
- **Literal value:** Used directly. Plain uppercase strings such as `MY_API_KEY` are literals; use `$MY_API_KEY` for environment variables.
|
||||
```json
|
||||
{ "type": "api_key", "key": "sk-ant-..." }
|
||||
{ "type": "api_key", "key": "public" }
|
||||
```
|
||||
|
||||
OAuth credentials are also stored here after `/login` and managed automatically.
|
||||
@@ -181,7 +212,7 @@ export AWS_BEDROCK_FORCE_HTTP1=1
|
||||
|
||||
### Cloudflare AI Gateway
|
||||
|
||||
`CLOUDFLARE_API_KEY` can be set via `/login`. The account ID and gateway slug must be set as environment variables.
|
||||
`CLOUDFLARE_API_KEY` can be set via `/login`. The account ID and gateway slug can be set as environment variables or in the API key credential's `env` object in `auth.json`.
|
||||
|
||||
```bash
|
||||
export CLOUDFLARE_API_KEY=... # or use /login
|
||||
@@ -205,7 +236,7 @@ For normal pi usage, prefer unified billing or stored BYOK. Inline BYOK requires
|
||||
|
||||
### Cloudflare Workers AI
|
||||
|
||||
`CLOUDFLARE_API_KEY` can be set via `/login`. `CLOUDFLARE_ACCOUNT_ID` must be set as an environment variable.
|
||||
`CLOUDFLARE_API_KEY` can be set via `/login`. `CLOUDFLARE_ACCOUNT_ID` can be set as an environment variable or in the API key credential's `env` object in `auth.json`.
|
||||
|
||||
```bash
|
||||
export CLOUDFLARE_API_KEY=... # or use /login
|
||||
|
||||
@@ -136,6 +136,7 @@ Sessions are saved automatically:
|
||||
```bash
|
||||
pi -c # Continue most recent session
|
||||
pi -r # Browse previous sessions
|
||||
pi --name "my task" # Set session display name at startup
|
||||
pi --session <path|id> # Open a specific session
|
||||
```
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ pi --mode rpc [options]
|
||||
Common options:
|
||||
- `--provider <name>`: Set the LLM provider (anthropic, openai, google, etc.)
|
||||
- `--model <pattern>`: Model pattern or ID (supports `provider/id` and optional `:<thinking>`)
|
||||
- `--name <name>` / `-n <name>`: Set the session display name at startup
|
||||
- `--no-session`: Disable session persistence
|
||||
- `--session-dir <path>`: Custom session storage directory
|
||||
|
||||
@@ -373,11 +374,14 @@ Response:
|
||||
"summary": "Summary of conversation...",
|
||||
"firstKeptEntryId": "abc123",
|
||||
"tokensBefore": 150000,
|
||||
"estimatedTokensAfter": 32000,
|
||||
"details": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`estimatedTokensAfter` is a heuristic estimate over the rebuilt message context immediately after compaction, not a provider-exact token count.
|
||||
|
||||
#### set_auto_compaction
|
||||
|
||||
Enable or disable automatic compaction when context is nearly full.
|
||||
@@ -702,7 +706,7 @@ Response:
|
||||
}
|
||||
```
|
||||
|
||||
The current session name is available via `get_state` in the `sessionName` field.
|
||||
The current session name is available via `get_state` in the `sessionName` field. To set the initial name when starting RPC mode, pass `--name <name>` or `-n <name>` to the `pi --mode rpc` process.
|
||||
|
||||
### Commands
|
||||
|
||||
@@ -931,6 +935,7 @@ The `reason` field is `"manual"`, `"threshold"`, or `"overflow"`.
|
||||
"summary": "Summary of conversation...",
|
||||
"firstKeptEntryId": "abc123",
|
||||
"tokensBefore": 150000,
|
||||
"estimatedTokensAfter": 32000,
|
||||
"details": {}
|
||||
},
|
||||
"aborted": false,
|
||||
@@ -1010,7 +1015,7 @@ Some `ExtensionUIContext` methods are not supported or degraded in RPC mode beca
|
||||
- `getTheme()` returns `undefined`
|
||||
- `setTheme()` returns `{ success: false, error: "..." }`
|
||||
|
||||
Note: `ctx.hasUI` is `true` in RPC mode because the dialog and fire-and-forget methods are functional via the extension UI sub-protocol.
|
||||
Note: `ctx.mode` is `"rpc"` and `ctx.hasUI` is `true` in RPC mode because the dialog and fire-and-forget methods are functional via the extension UI sub-protocol. Use `ctx.mode === "tui"` to guard TUI-specific features like `custom()` that require a real terminal.
|
||||
|
||||
### Extension UI Requests (stdout)
|
||||
|
||||
|
||||
@@ -472,6 +472,7 @@ Specify which built-in tools to enable:
|
||||
- Default built-ins: `read`, `bash`, `edit`, `write`
|
||||
- `noTools: "all"` disables all tools
|
||||
- `noTools: "builtin"` disables default built-ins while keeping extension and custom tools enabled
|
||||
- `excludeTools` disables specific built-in, extension, or custom tool names after any `tools` allowlist is applied
|
||||
|
||||
The `edit` tool returns `details.diff` for Pi's TUI display and `details.patch` as a standard unified patch for SDK consumers.
|
||||
|
||||
@@ -487,6 +488,11 @@ const { session } = await createAgentSession({
|
||||
const { session } = await createAgentSession({
|
||||
tools: ["read", "bash", "grep"],
|
||||
});
|
||||
|
||||
// Disable one tool while keeping the rest available
|
||||
const { session } = await createAgentSession({
|
||||
excludeTools: ["ask_question"],
|
||||
});
|
||||
```
|
||||
|
||||
#### Tools with Custom cwd
|
||||
@@ -1104,8 +1110,14 @@ DefaultResourceLoader
|
||||
type ResourceLoader
|
||||
createEventBus
|
||||
|
||||
// Helpers
|
||||
// Constants and helpers
|
||||
CONFIG_DIR_NAME
|
||||
defineTool
|
||||
getAgentDir
|
||||
getPackageDir
|
||||
getReadmePath
|
||||
getDocsPath
|
||||
getExamplesPath
|
||||
|
||||
// Session management
|
||||
SessionManager
|
||||
|
||||
59
packages/coding-agent/docs/security.md
Normal file
59
packages/coding-agent/docs/security.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Security
|
||||
|
||||
Pi is a local coding agent. It runs with the permissions of the user account that starts it, and it treats files writable by that user as inside the same local trust boundary.
|
||||
|
||||
## Project Trust
|
||||
|
||||
Project trust controls whether pi loads project-local settings, resources, packages, and extensions. It is not a sandbox and it does not restrict what the model can ask tools to do after you start working in a directory.
|
||||
|
||||
Pi considers a project to have resources that require trust when it finds any of these from the current working directory:
|
||||
|
||||
- `.pi/settings.json`
|
||||
- `.pi/extensions`, `.pi/skills`, `.pi/prompts`, or `.pi/themes`
|
||||
- `.pi/SYSTEM.md` or `.pi/APPEND_SYSTEM.md`
|
||||
- project `.agents/skills` in the current directory or an ancestor directory
|
||||
|
||||
A bare `.pi` directory does not count as a project resource that requires trust.
|
||||
|
||||
When an interactive session starts in a project with resources that require trust and no saved decision for the current directory or a parent directory, pi follows `defaultProjectTrust` from global settings. The default value is `"ask"`, which asks whether to trust the project when UI is available. Saved decisions are stored by canonical directory in `~/.pi/agent/trust.json`, and the closest saved decision on the current or parent path applies before the global default.
|
||||
|
||||
Trusting a project allows pi to load project resources that require trust, including:
|
||||
|
||||
- `.pi/settings.json`
|
||||
- `.pi` resources such as extensions, skills, prompt templates, themes, and system prompt files
|
||||
- missing project packages configured through project settings
|
||||
- project-local extensions and project package-managed extensions
|
||||
|
||||
Declining trust skips protected resources. `AGENTS.md` and `CLAUDE.md` context files are loaded regardless of project trust unless context loading is disabled. Before trust is resolved, pi only loads context files, user/global extensions, and CLI `-e` extensions. User/global and CLI extensions can handle the `project_trust` event; the first extension that returns a yes/no decision owns the decision.
|
||||
|
||||
Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, `defaultProjectTrust: "ask"` and `"never"` ignore such resources, while `"always"` trusts them. Use `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
|
||||
|
||||
## No Built-in Sandbox
|
||||
|
||||
Pi does not include a built-in sandbox. Built-in tools can read files, write files, edit files, and run shell commands with the permissions of the pi process. Extensions are TypeScript modules that run with the same permissions. Package installs, shell commands, language servers, test commands, and other developer tools behave as ordinary local processes.
|
||||
|
||||
This is intentional. Pi is designed to operate on local source trees, invoke project toolchains, and integrate with the user's existing development environment. A partial in-process sandbox would be easy to misunderstand as a security boundary while still depending on the host shell, filesystem, package managers, credentials, and extension code. Real isolation needs to come from the operating system or a virtualization/container boundary.
|
||||
|
||||
Project trust is only an input-loading guard. It prevents a repository from silently changing pi's settings or extensions before you approve it. It does not make untrusted code, untrusted prompts, or untrusted model output safe. Prompt injection from repository files, comments, documentation, context files, or build output is expected local-agent risk and cannot be reliably prevented by pi.
|
||||
|
||||
## Running Untrusted or Unmonitored Work
|
||||
|
||||
For untrusted repositories, generated code you do not intend to monitor closely, or unattended automation, run pi in a contained environment. Use a container, VM, micro-VM, remote sandbox, or policy-controlled sandbox with only the files and credentials required for the task.
|
||||
|
||||
Common patterns are documented in [Containerization](containerization.md):
|
||||
|
||||
- run the whole `pi` process inside a container/sandbox
|
||||
- run host pi while routing built-in tool execution into a Gondolin micro-VM
|
||||
- mount only the workspace paths the agent should access
|
||||
- avoid mounting host `~/.pi/agent` unless the container should access host sessions, settings, and credentials
|
||||
- pass the minimum required API keys or use short-lived credentials
|
||||
- restrict network access when the task does not need it
|
||||
- review diffs and outputs before copying results back to trusted systems
|
||||
|
||||
If you bind-mount a host workspace read/write, writes from inside the container or VM can still modify host files. Use read-only mounts or copy files into and out of the sandbox when you need stronger protection from unintended writes.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
To report a security issue, follow the repository [Security Policy](https://github.com/earendil-works/pi-mono/blob/main/SECURITY.md). Do not open a public issue for security-sensitive reports.
|
||||
|
||||
Expected local-agent behavior, lack of a built-in sandbox, prompt injection from untrusted content, and behavior of user-installed extensions or skills are generally outside the security boundary unless the report demonstrates a real privilege-boundary bypass or shows how pi grants access that the local user did not already have.
|
||||
@@ -282,7 +282,7 @@ Set `label` to `undefined` to clear a label.
|
||||
|
||||
### SessionInfoEntry
|
||||
|
||||
Session metadata (e.g., user-defined display name). Set via `/name` command or `pi.setSessionName()` in extensions.
|
||||
Session metadata (e.g., user-defined display name). Set via `/name`, `--name` / `-n`, or `pi.setSessionName()` in extensions.
|
||||
|
||||
```json
|
||||
{"type":"session_info","id":"k1l2m3n4","parentId":"j0k1l2m3","timestamp":"2024-12-03T14:35:00.000Z","name":"Refactor auth module"}
|
||||
|
||||
@@ -10,6 +10,7 @@ Sessions auto-save to `~/.pi/agent/sessions/`, organized by working directory. E
|
||||
pi -c # Continue most recent session
|
||||
pi -r # Browse and select from past sessions
|
||||
pi --no-session # Ephemeral mode; do not save
|
||||
pi --name "my task" # Set session display name at startup
|
||||
pi --session <path|id> # Use a specific session file or partial session ID
|
||||
pi --fork <path|id> # Fork a session file or partial session ID into a new session
|
||||
```
|
||||
@@ -56,6 +57,13 @@ Use `/name <name>` to set a human-readable session name:
|
||||
/name Refactor auth module
|
||||
```
|
||||
|
||||
Set the name at startup with `--name` or `-n`:
|
||||
|
||||
```bash
|
||||
pi --name "Refactor auth module"
|
||||
pi --name "CI audit" -p "Review this build failure"
|
||||
```
|
||||
|
||||
Named sessions are easier to find in `/resume` and `pi -r`.
|
||||
|
||||
## Branching with `/tree`
|
||||
|
||||
@@ -9,6 +9,18 @@ Pi uses JSON settings files with project settings overriding global settings.
|
||||
|
||||
Edit directly or use `/settings` for common options.
|
||||
|
||||
## Project Trust
|
||||
|
||||
On interactive startup, pi asks before trusting a project folder that contains project-local settings, resources, or project `.agents/skills` and has no saved decision for the folder or a parent folder in `~/.pi/agent/trust.json`. Trusting a project allows pi to load `.pi/settings.json` and `.pi` resources, install missing project packages, and execute project extensions.
|
||||
|
||||
Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, they use `defaultProjectTrust` from global settings: `ask` (default) and `never` ignore those project resources, while `always` trusts them. Pass `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
|
||||
|
||||
If no extension or saved decision applies, `defaultProjectTrust` controls the fallback behavior. Set it to `"ask"`, `"always"`, or `"never"` in `~/.pi/agent/settings.json`, or change it with `/settings`.
|
||||
|
||||
`pi config` and package commands use the same project trust flow, except `pi update` never prompts. Pass `--approve` to trust project-local settings for one command or `--no-approve` to ignore them.
|
||||
|
||||
Use `/trust` in interactive mode to save a project trust decision for future sessions, including trust for the immediate parent folder. It writes `~/.pi/agent/trust.json` only; the current session is not reloaded, so restart pi for changes to take effect.
|
||||
|
||||
## All Settings
|
||||
|
||||
### Model & Thinking
|
||||
@@ -40,13 +52,16 @@ Edit directly or use `/settings` for common options.
|
||||
|---------|------|---------|-------------|
|
||||
| `theme` | string | `"dark"` | Theme name (`"dark"`, `"light"`, or custom) |
|
||||
| `quietStartup` | boolean | `false` | Hide startup header |
|
||||
| `defaultProjectTrust` | string | `"ask"` | Fallback project trust behavior: `"ask"`, `"always"`, or `"never"`. Global setting only |
|
||||
| `collapseChangelog` | boolean | `false` | Show condensed changelog after updates |
|
||||
| `enableInstallTelemetry` | boolean | `true` | Send an anonymous install/update version ping after first install or changelog-detected updates. This does not control update checks |
|
||||
| `enableAnalytics` | boolean | `false` | Opt-in analytics data sharing. Currently only asked for during the experimental first-time setup (`PI_EXPERIMENTAL=1`) |
|
||||
| `trackingId` | string | - | Analytics tracking identifier, generated when `enableAnalytics` is turned on |
|
||||
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"`, `"fork"`, or `"none"` |
|
||||
| `treeFilterMode` | string | `"default"` | Default filter for `/tree`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, `"all"` |
|
||||
| `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) |
|
||||
| `autocompleteMaxVisible` | number | `5` | Max visible items in autocomplete dropdown (3-20) |
|
||||
| `showHardwareCursor` | boolean | `false` | Show terminal cursor |
|
||||
| `showHardwareCursor` | boolean | `false` | Show the terminal cursor while TUI positions it for IME support |
|
||||
|
||||
### Telemetry and update checks
|
||||
|
||||
@@ -54,6 +69,18 @@ Edit directly or use `/settings` for common options.
|
||||
|
||||
Set `PI_SKIP_VERSION_CHECK=1` to disable the Pi version update check. Use `--offline` or `PI_OFFLINE=1` to disable all startup network operations described here, including update checks, package update checks, and install/update telemetry.
|
||||
|
||||
### Network
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `httpProxy` | string | - | HTTP proxy URL applied as `HTTP_PROXY` and `HTTPS_PROXY`. Global setting only. |
|
||||
|
||||
```json
|
||||
{
|
||||
"httpProxy": "http://127.0.0.1:7890"
|
||||
}
|
||||
```
|
||||
|
||||
### Warnings
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
@@ -101,11 +128,13 @@ Set `PI_SKIP_VERSION_CHECK=1` to disable the Pi version update check. Use `--off
|
||||
| `retry.maxRetries` | number | `3` | Maximum agent-level retry attempts |
|
||||
| `retry.baseDelayMs` | number | `2000` | Base delay for agent-level exponential backoff (2s, 4s, 8s) |
|
||||
| `retry.provider.timeoutMs` | number | SDK default | Provider/SDK request timeout in milliseconds |
|
||||
| `retry.provider.maxRetries` | number | SDK default | Provider/SDK retry attempts |
|
||||
| `retry.provider.maxRetries` | number | `0` | Provider/SDK retry attempts |
|
||||
| `retry.provider.maxRetryDelayMs` | number | `60000` | Max server-requested delay before failing (60s) |
|
||||
|
||||
When a provider requests a retry delay longer than `retry.provider.maxRetryDelayMs` (e.g., Google's "quota will reset after 5h"), the request fails immediately with an informative error instead of waiting silently. Set to `0` to disable the cap.
|
||||
|
||||
Keep `retry.provider.maxRetries` at `0` unless provider-level retries are explicitly needed. Setting it above `0` can make SDK/provider retries handle out-of-usage-limit errors before Pi sees them, which may block the agent until the provider quota resets in some circumstances.
|
||||
|
||||
```json
|
||||
{
|
||||
"retry": {
|
||||
@@ -127,7 +156,9 @@ When a provider requests a retry delay longer than `retry.provider.maxRetryDelay
|
||||
|---------|------|---------|-------------|
|
||||
| `steeringMode` | string | `"one-at-a-time"` | How steering messages are sent: `"all"` or `"one-at-a-time"` |
|
||||
| `followUpMode` | string | `"one-at-a-time"` | How follow-up messages are sent: `"all"` or `"one-at-a-time"` |
|
||||
| `transport` | string | `"sse"` | Preferred transport for providers that support multiple transports: `"sse"`, `"websocket"`, or `"auto"` |
|
||||
| `transport` | string | `"auto"` | Preferred transport for providers that support multiple transports: `"sse"`, `"websocket"`, `"websocket-cached"`, or `"auto"` |
|
||||
| `httpIdleTimeoutMs` | number | `300000` | HTTP header/body idle timeout in milliseconds, also used by providers with explicit stream idle timeouts. Set to `0` to disable. |
|
||||
| `websocketConnectTimeoutMs` | number | `15000` | WebSocket connect/open handshake timeout in milliseconds for providers that support WebSocket transports. Set to `0` to disable. |
|
||||
|
||||
### Terminal & Images
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Pi loads skills from:
|
||||
- Global:
|
||||
- `~/.pi/agent/skills/`
|
||||
- `~/.agents/skills/`
|
||||
- Project:
|
||||
- Project (only after the project is trusted):
|
||||
- `.pi/skills/`
|
||||
- `.agents/skills/` in `cwd` and ancestor directories (up to git repo root, or filesystem root when not in a repo)
|
||||
- Packages: `skills/` directories or `pi.skills` entries in `package.json`
|
||||
|
||||
@@ -6,6 +6,12 @@ Pi uses the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-p
|
||||
|
||||
Work out of the box.
|
||||
|
||||
## Apple Terminal
|
||||
|
||||
Pi enables enhanced key reporting when available. If Terminal.app still sends plain Return for `Shift+Enter`, pi uses a local macOS modifier fallback to treat that Return as `Shift+Enter`.
|
||||
|
||||
This fallback only works when pi runs on the same Mac as Terminal.app. It cannot detect the local keyboard over remote SSH.
|
||||
|
||||
## Ghostty
|
||||
|
||||
Add to your Ghostty config (`~/Library/Application Support/com.mitchellh.ghostty/config` on macOS, `~/.config/ghostty/config` on Linux):
|
||||
@@ -34,7 +40,7 @@ If you want `Shift+Enter` to keep working in tmux via that remap, add `ctrl+j` t
|
||||
|
||||
## WezTerm
|
||||
|
||||
Create `~/.wezterm.lua`:
|
||||
WezTerm usually works out of the box for `Shift+Enter` via xterm modifyOtherKeys. To use the Kitty keyboard protocol explicitly, create `~/.wezterm.lua`:
|
||||
|
||||
```lua
|
||||
local wezterm = require 'wezterm'
|
||||
@@ -43,14 +49,50 @@ config.enable_kitty_keyboard = true
|
||||
return config
|
||||
```
|
||||
|
||||
On macOS, WezTerm binds `Option+Enter` to fullscreen by default. To use `Option+Enter` for pi follow-up queueing, add this key override:
|
||||
|
||||
```lua
|
||||
local wezterm = require 'wezterm'
|
||||
local config = wezterm.config_builder()
|
||||
config.keys = {
|
||||
{
|
||||
key = 'Enter',
|
||||
mods = 'ALT',
|
||||
action = wezterm.action.SendString('\x1b[13;3u'),
|
||||
},
|
||||
}
|
||||
return config
|
||||
```
|
||||
|
||||
If you already have a `config.keys` table, add the entry to it.
|
||||
|
||||
On WSL, WezTerm may require a visible hardware cursor for IME candidate window positioning. If CJK IME candidates do not follow the text cursor, set `PI_HARDWARE_CURSOR=1` before running pi or set `showHardwareCursor` to `true` in settings.
|
||||
|
||||
## Alacritty
|
||||
|
||||
Alacritty usually works out of the box for `Shift+Enter`. On macOS, `Option+Enter` may arrive as plain `Enter`. To use `Option+Enter` for pi follow-up queueing, add to `~/.config/alacritty/alacritty.toml`:
|
||||
|
||||
```toml
|
||||
[[keyboard.bindings]]
|
||||
key = "Enter"
|
||||
mods = "Alt"
|
||||
chars = "\u001b[13;3u"
|
||||
```
|
||||
|
||||
Restart Alacritty after changing the config.
|
||||
|
||||
## VS Code (Integrated Terminal)
|
||||
|
||||
VS Code 1.109.5 and newer enable Kitty keyboard protocol in the integrated terminal by default, so `Shift+Enter` should work out of the box.
|
||||
|
||||
VS Code versions older than 1.109.5 need an explicit terminal keybinding for `Shift+Enter`.
|
||||
|
||||
`keybindings.json` locations:
|
||||
- macOS: `~/Library/Application Support/Code/User/keybindings.json`
|
||||
- Linux: `~/.config/Code/User/keybindings.json`
|
||||
- Windows: `%APPDATA%\\Code\\User\\keybindings.json`
|
||||
|
||||
Add to `keybindings.json` to enable `Shift+Enter` for multi-line input:
|
||||
Add to `keybindings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ Pi loads themes from:
|
||||
|
||||
- Built-in: `dark`, `light`
|
||||
- Global: `~/.pi/agent/themes/*.json`
|
||||
- Project: `.pi/themes/*.json`
|
||||
- Project: `.pi/themes/*.json` (only after the project is trusted)
|
||||
- Packages: `themes/` directories or `pi.themes` entries in `package.json`
|
||||
- Settings: `themes` array with files or directories
|
||||
- CLI: `--theme <path>` (repeatable)
|
||||
@@ -137,7 +137,7 @@ vim ~/.pi/agent/themes/my-theme.json
|
||||
}
|
||||
```
|
||||
|
||||
- `name` is required and must be unique.
|
||||
- `name` is required, must be unique, and must not contain `/`.
|
||||
- `vars` is optional. Define reusable colors here, then reference them in `colors`.
|
||||
- `colors` must define all 51 required tokens.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ tmux kill-server
|
||||
tmux
|
||||
```
|
||||
|
||||
Pi requests extended key reporting automatically when Kitty keyboard protocol is not available. With `extended-keys-format csi-u`, tmux forwards modified keys in CSI-u format, which is the most reliable configuration.
|
||||
Pi requests extended key reporting automatically when Kitty keyboard protocol is not available. With `extended-keys-format csi-u`, tmux forwards modified keys in CSI-u format, which is the most reliable configuration. The `extended-keys-format` option requires tmux 3.5 or later.
|
||||
|
||||
## Why `csi-u` Is Recommended
|
||||
|
||||
@@ -57,5 +57,7 @@ This affects the default keybindings (`Enter` to submit, `Shift+Enter` for newli
|
||||
|
||||
## Requirements
|
||||
|
||||
- tmux 3.2 or later (run `tmux -V` to check)
|
||||
- tmux 3.5 or later for `extended-keys-format csi-u` (run `tmux -V` to check)
|
||||
- A terminal emulator that supports extended keys (Ghostty, Kitty, iTerm2, WezTerm, Windows Terminal)
|
||||
|
||||
With tmux 3.2 through 3.4, omit `extended-keys-format csi-u`; Pi still supports tmux's default xterm `modifyOtherKeys` format.
|
||||
|
||||
@@ -50,9 +50,9 @@ When a `Focusable` component has focus, TUI:
|
||||
1. Sets `focused = true` on the component
|
||||
2. Scans rendered output for `CURSOR_MARKER` (a zero-width APC escape sequence)
|
||||
3. Positions the hardware terminal cursor at that location
|
||||
4. Shows the hardware cursor
|
||||
4. Shows the hardware cursor only when `showHardwareCursor` is enabled
|
||||
|
||||
This enables IME candidate windows to appear at the correct position for CJK input methods. The `Editor` and `Input` built-in components already implement this interface.
|
||||
The cursor remains hidden by default. This keeps the fake cursor rendering, while still positioning the hardware cursor for terminals that track IME candidate windows with hidden cursors. Some terminals require a visible hardware cursor for IME positioning; enable it with `showHardwareCursor`, `setShowHardwareCursor(true)`, or `PI_HARDWARE_CURSOR=1`. The `Editor` and `Input` built-in components already implement this interface.
|
||||
|
||||
### Container Components with Embedded Inputs
|
||||
|
||||
@@ -145,8 +145,11 @@ const result = await ctx.ui.custom<string | null>(
|
||||
// Responsive: hide on narrow terminals
|
||||
visible: (termWidth, termHeight) => termWidth >= 80,
|
||||
},
|
||||
// Get handle for programmatic visibility control
|
||||
// Get handle for programmatic focus and visibility control
|
||||
onHandle: (handle) => {
|
||||
// handle.focus() - focus this overlay and bring it to the visual front
|
||||
// handle.unfocus() - release input to normal fallback
|
||||
// handle.unfocus({ target }) - release input to a specific component or null
|
||||
// handle.setHidden(true/false) - toggle visibility
|
||||
// handle.hide() - permanently remove
|
||||
},
|
||||
@@ -154,6 +157,12 @@ const result = await ctx.ui.custom<string | null>(
|
||||
);
|
||||
```
|
||||
|
||||
### Overlay Focus
|
||||
|
||||
A focused visible overlay keeps input ownership across temporary non-overlay UI. If an overlay opens another `ctx.ui.custom()` component without `{ overlay: true }`, that replacement UI receives input while it is active; when it closes, the focused overlay can reclaim input.
|
||||
|
||||
Use `handle.unfocus()` when a visible overlay should stop owning input and let TUI fall back to another visible capturing overlay or the previous focus target. Use `handle.unfocus({ target })` when a specific component should receive input while the overlay stays visible. Passing `{ target: null }` intentionally leaves no focused component until focus is set again.
|
||||
|
||||
### Overlay Lifecycle
|
||||
|
||||
Overlay components are disposed when closed. Don't reuse references - create fresh instances:
|
||||
@@ -248,7 +257,7 @@ md.setText("Updated markdown");
|
||||
|
||||
### Image
|
||||
|
||||
Renders images in supported terminals (Kitty, iTerm2, Ghostty, WezTerm).
|
||||
Renders images in supported terminals (Kitty, iTerm2, Ghostty, WezTerm, Warp).
|
||||
|
||||
```typescript
|
||||
const image = new Image(
|
||||
|
||||
@@ -76,6 +76,7 @@ Sessions are saved automatically to `~/.pi/agent/sessions/`, organized by workin
|
||||
pi -c # Continue most recent session
|
||||
pi -r # Browse and select a session
|
||||
pi --no-session # Ephemeral mode; do not save
|
||||
pi --name "my task" # Set session display name at startup
|
||||
pi --session <path|id> # Use a specific session file or session ID
|
||||
pi --fork <path|id> # Fork a session into a new session file
|
||||
```
|
||||
@@ -109,6 +110,21 @@ Replace the default system prompt with:
|
||||
|
||||
Append to the default prompt without replacing it with `APPEND_SYSTEM.md` in either location.
|
||||
|
||||
### Project Trust
|
||||
|
||||
On interactive startup, pi asks before trusting a project folder that contains project-local settings, resources, or project `.agents/skills` and has no saved decision for the folder or a parent folder in `~/.pi/agent/trust.json`. Trusting a project allows pi to load `.pi/settings.json` and `.pi` resources, install missing project packages, and execute project extensions.
|
||||
|
||||
Before the trust decision, pi loads only context files, user/global extensions, and CLI `-e` extensions so they can handle the `project_trust` event. Project-local extensions, project package-managed extensions, and project settings are loaded only after the project is trusted. This split also applies when switching to a session from a different cwd whose trust has not been resolved in the current process.
|
||||
|
||||
Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, they use `defaultProjectTrust` from global settings: `ask` (default) and `never` ignore those project resources, while `always` trusts them. Pass `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
|
||||
|
||||
If no extension or saved decision applies, `defaultProjectTrust` controls the fallback behavior. Set it to `"ask"`, `"always"`, or `"never"` in `~/.pi/agent/settings.json`, or change it with `/settings`.
|
||||
|
||||
`pi config` and package commands use the same project trust flow, except `pi update` never prompts. Pass `--approve` to trust project-local settings for one command or `--no-approve` to ignore them.
|
||||
|
||||
Use `/trust` in interactive mode to save a project trust decision for future sessions, including trust for the immediate parent folder. It writes `~/.pi/agent/trust.json` only; the current session is not reloaded, so restart pi for changes to take effect.
|
||||
|
||||
|
||||
## Exporting and Sharing Sessions
|
||||
|
||||
Use `/export [file]` to write a session to HTML.
|
||||
@@ -129,15 +145,16 @@ pi [options] [@files...] [messages...]
|
||||
pi install <source> [-l] # Install package, -l for project-local
|
||||
pi remove <source> [-l] # Remove package
|
||||
pi uninstall <source> [-l] # Alias for remove
|
||||
pi update [source|self|pi] # Update pi and packages; skips pinned packages
|
||||
pi update --extensions # Update packages only
|
||||
pi update [source|self|pi] # Update pi only, or one package source
|
||||
pi update --all # Update pi and packages; reconcile pinned git refs
|
||||
pi update --extensions # Update packages only; reconcile pinned git refs
|
||||
pi update --self # Update pi only
|
||||
pi update --extension <src> # Update one package
|
||||
pi list # List installed packages
|
||||
pi config # Enable/disable package resources
|
||||
```
|
||||
|
||||
These commands manage pi packages, not the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall).
|
||||
These commands manage pi packages and `pi update` can update the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall). `pi config` and project package commands accept `--approve`/`--no-approve` to trust or ignore project-local settings for one command. `pi update` never prompts for project trust.
|
||||
|
||||
See [Pi Packages](packages.md) for package sources and security notes.
|
||||
|
||||
@@ -178,12 +195,14 @@ cat README.md | pi -p "Summarize this text"
|
||||
| `--fork <path\|id>` | Fork a session file or partial UUID into a new session |
|
||||
| `--session-dir <dir>` | Custom session storage directory |
|
||||
| `--no-session` | Ephemeral mode; do not save |
|
||||
| `--name <name>`, `-n <name>` | Set session display name at startup |
|
||||
|
||||
### Tool Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--tools <list>`, `-t <list>` | Allowlist specific built-in, extension, and custom tools |
|
||||
| `--exclude-tools <list>`, `-xt <list>` | Disable specific built-in, extension, and custom tools |
|
||||
| `--no-builtin-tools`, `-nbt` | Disable built-in tools but keep extension/custom tools enabled |
|
||||
| `--no-tools`, `-nt` | Disable all tools |
|
||||
|
||||
@@ -216,6 +235,8 @@ pi --no-extensions -e ./my-extension.ts
|
||||
| `--system-prompt <text>` | Replace default prompt; context files and skills are still appended |
|
||||
| `--append-system-prompt <text>` | Append to system prompt |
|
||||
| `--verbose` | Force verbose startup |
|
||||
| `-a`, `--approve` | Trust project-local files for this run |
|
||||
| `-na`, `--no-approve` | Ignore project-local files for this run |
|
||||
| `-h`, `--help` | Show help |
|
||||
| `-v`, `--version` | Show version |
|
||||
|
||||
@@ -241,6 +262,9 @@ pi -p "Summarize this codebase"
|
||||
# Non-interactive with piped stdin
|
||||
cat README.md | pi -p "Summarize this text"
|
||||
|
||||
# Named one-shot session
|
||||
pi --name "release audit" -p "Audit this repository"
|
||||
|
||||
# Different model
|
||||
pi --provider openai --model gpt-4o "Help me refactor"
|
||||
|
||||
@@ -255,6 +279,9 @@ pi --models "claude-*,gpt-4o"
|
||||
|
||||
# Read-only mode
|
||||
pi --tools read,grep,find,ls -p "Review the code"
|
||||
|
||||
# Disable one extension or built-in tool while keeping the rest available
|
||||
pi --exclude-tools ask_question
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
@@ -266,7 +293,7 @@ pi --tools read,grep,find,ls -p "Review the code"
|
||||
| `PI_PACKAGE_DIR` | Override package directory, useful for Nix/Guix store paths |
|
||||
| `PI_OFFLINE` | Disable startup network operations, including update checks, package update checks, and install/update telemetry |
|
||||
| `PI_SKIP_VERSION_CHECK` | Skip the Pi version update check at startup. This prevents the `pi.dev` latest-version request |
|
||||
| `PI_TELEMETRY` | Override install/update telemetry: `1`/`true`/`yes` or `0`/`false`/`no`. This does not disable update checks |
|
||||
| `PI_TELEMETRY` | Override install/update telemetry and provider attribution headers: `1`/`true`/`yes` or `0`/`false`/`no`. This does not disable update checks |
|
||||
| `PI_CACHE_RETENTION` | Set to `long` for extended prompt cache where supported |
|
||||
| `VISUAL`, `EDITOR` | External editor for Ctrl+G |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user