Skill Tracer
Skills › Scientific Agent Skills › pi-agent › models.md

models.md

119 lines · skills/pi-agent/references/models.md at 5f2bc03

Open on GitHub
1# Custom Models
2
3Source: https://pi.dev/docs/latest/models
4
5Reviewed against Pi 0.99.2 and the package versions listed in `../SKILL.md` on 2026-09-30.
6
7Add custom providers and models through `~/.pi/agent/models.json` for Ollama, LM Studio, vLLM, SGLang, proxies, and custom endpoints.
8

Custom Models

Source: https://pi.dev/docs/latest/models

Reviewed against Pi 0.99.2 and the package versions listed in ../SKILL.md on 2026-09-30.

Add custom providers and models through ~/.pi/agent/models.json for Ollama, LM Studio, vLLM, SGLang, proxies, and custom endpoints.

Raw lines 1 to 8
9## Minimal Local Example
11```json
13 "providers": {
14 "ollama": {
15 "baseUrl": "http://localhost:11434/v1",
16 "api": "openai-completions",
17 "apiKey": "ollama",
18 "compat": {
19 "supportsDeveloperRole": false,
20 "supportsReasoningEffort": false
21 },
22 "models": [
23 { "id": "llama3.1:8b" },
24 { "id": "qwen2.5-coder:7b" }
25 ]
26 }
27 }
29```
31Only `id` is required per model. The `apiKey` placeholder matters because Pi treats models as requiring auth before they appear in `/model`; keyless local servers should keep a dummy value, store a key via `/login`, or pass `--api-key`. Set `compat.supportsDeveloperRole: false` for OpenAI-compatible servers that reject the `developer` role, and `compat.supportsReasoningEffort: false` when `reasoning_effort` is unsupported. `compat` can be set at provider level (all models) or model level (override). The file reloads each time `/model` opens — no restart needed.
33For `google-generative-ai` custom models, `baseUrl` is required (for example `https://generativelanguage.googleapis.com/v1beta`).

Minimal Local Example

{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "compat": {
        "supportsDeveloperRole": false,
        "supportsReasoningEffort": false
      },
      "models": [
        { "id": "llama3.1:8b" },
        { "id": "qwen2.5-coder:7b" }
      ]
    }
  }
}

Only id is required per model. The apiKey placeholder matters because Pi treats models as requiring auth before they appear in /model; keyless local servers should keep a dummy value, store a key via /login, or pass --api-key. Set compat.supportsDeveloperRole: false for OpenAI-compatible servers that reject the developer role, and compat.supportsReasoningEffort: false when reasoning_effort is unsupported. compat can be set at provider level (all models) or model level (override). The file reloads each time /model opens — no restart needed.

For google-generative-ai custom models, baseUrl is required (for example https://generativelanguage.googleapis.com/v1beta).

Raw lines 9 to 34
35## Supported APIs
37`openai-completions` (most compatible), `openai-responses`, `anthropic-messages`, `google-generative-ai`. Set `api` at provider level as the default, or per model to override.

Supported APIs

openai-completions (most compatible), openai-responses, anthropic-messages, google-generative-ai. Set api at provider level as the default, or per model to override.

Raw lines 35 to 38
39## Provider Fields
41`baseUrl`, `api`, `apiKey`, `oauth`, `headers`, `authHeader`, `models`, `modelOverrides`.
43- `oauth`: dynamic OAuth provider type; currently `"radius"`, which requires the gateway `baseUrl`.
44- `authHeader: true` adds `Authorization: Bearer <apiKey>`.
45- `apiKey` is optional — omit it when auth comes from `/login`/`auth.json`/`--api-key`. Non-built-in providers with `models` need `baseUrl` and an `api` value at provider or model level. Without any auth, models load but stay unavailable in `/model` and `--list-models`.
47`apiKey` and `headers` support command execution (`!command`, whole value), env interpolation (`$ENV` / `${ENV}`, works inside larger literals), escapes (`$$`, `$!`), and literals (plain uppercase strings are literals). Shell commands in `models.json` resolve at request time with no built-in TTL, stale reuse, or recovery — wrap slow or flaky secret commands yourself. `/model` availability checks use configured auth presence and do not execute shell commands.

Provider Fields

baseUrl, api, apiKey, oauth, headers, authHeader, models, modelOverrides.

  • oauth: dynamic OAuth provider type; currently "radius", which requires the gateway baseUrl.
  • authHeader: true adds Authorization: Bearer <apiKey>.
  • apiKey is optional — omit it when auth comes from /login/auth.json/--api-key. Non-built-in providers with models need baseUrl and an api value at provider or model level. Without any auth, models load but stay unavailable in /model and --list-models.

apiKey and headers support command execution (!command, whole value), env interpolation ($ENV / ${ENV}, works inside larger literals), escapes ($$, $!), and literals (plain uppercase strings are literals). Shell commands in models.json resolve at request time with no built-in TTL, stale reuse, or recovery — wrap slow or flaky secret commands yourself. /model availability checks use configured auth presence and do not execute shell commands.

Raw lines 39 to 48
49## Model Fields
51Required: `id`. Optional: `name` (defaults to `id`), `api`, `reasoning` (`false`), `thinkingLevelMap`, `input` (`["text"]` or `["text","image"]`), `contextWindow` (`128000`), `maxTokens` (`16384`), `samplingParams`, `cost` (zeros), `compat` (merged with provider `compat`).
53`/model`, `--list-models`, and the footer display entries by model `id`; `name` is used for `--model` pattern matching and secondary detail text.
55`cost` is per million tokens and supports request-wide input pricing tiers. A tier supplies a complete alternate rate set and applies to the whole request when `input + cacheRead + cacheWrite` exceeds `inputTokensAbove`; the highest matching threshold wins.
57```json
59 "cost": {
60 "input": 5, "output": 30, "cacheRead": 0.5, "cacheWrite": 6.25,
61 "tiers": [{ "inputTokensAbove": 272000, "input": 10, "output": 45, "cacheRead": 1, "cacheWrite": 12.5 }]
62 }
64```

Model Fields

Required: id. Optional: name (defaults to id), api, reasoning (false), thinkingLevelMap, input (["text"] or ["text","image"]), contextWindow (128000), maxTokens (16384), samplingParams, cost (zeros), compat (merged with provider compat).

/model, --list-models, and the footer display entries by model id; name is used for --model pattern matching and secondary detail text.

cost is per million tokens and supports request-wide input pricing tiers. A tier supplies a complete alternate rate set and applies to the whole request when input + cacheRead + cacheWrite exceeds inputTokensAbove; the highest matching threshold wins.

{
  "cost": {
    "input": 5, "output": 30, "cacheRead": 0.5, "cacheWrite": 6.25,
    "tiers": [{ "inputTokensAbove": 272000, "input": 10, "output": 45, "cacheRead": 1, "cacheWrite": 12.5 }]
  }
}
Raw lines 49 to 65
66## Sampling Parameters
68`samplingParams` is a free-form object merged verbatim into every request body for that model, after the fields Pi sets itself — so its keys win, including over Pi's own `temperature`. Use it for sampling controls Pi does not model, such as llama.cpp `min_p` or vLLM `top_k`:
70```json
71{ "id": "deepseek-v4-flash",
72 "samplingParams": { "temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0.0 } }
73```
75Only OpenAI-compatible APIs apply it (`openai-completions`, `openai-responses`, `azure-openai-responses`); other APIs ignore it. Treat it as the single source of sampling truth for a model. In `modelOverrides`, `samplingParams` merges per key with the base model's value.

Sampling Parameters

samplingParams is a free-form object merged verbatim into every request body for that model, after the fields Pi sets itself — so its keys win, including over Pi's own temperature. Use it for sampling controls Pi does not model, such as llama.cpp min_p or vLLM top_k:

{ "id": "deepseek-v4-flash",
  "samplingParams": { "temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0.0 } }

Only OpenAI-compatible APIs apply it (openai-completions, openai-responses, azure-openai-responses); other APIs ignore it. Treat it as the single source of sampling truth for a model. In modelOverrides, samplingParams merges per key with the base model's value.

Raw lines 66 to 76
77## Thinking Level Map
79Keys are Pi levels: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Maps may have holes. Values are tristate: omitted means standard levels through `high` use the provider default mapping while `xhigh`/`max` are unsupported; a string is sent to the provider; `null` marks the level unsupported so it is hidden/skipped/clamped away.
81```json
82{ "id": "deepseek-v4-pro", "reasoning": true,
83 "thinkingLevelMap": { "minimal": null, "low": null, "medium": null, "high": "high", "xhigh": null, "max": "max" } }
84```
86Use `{ "off": null }` for models where thinking cannot be disabled. Older `compat.reasoningEffortMap` configs should move to model-level `thinkingLevelMap`.

Thinking Level Map

Keys are Pi levels: off, minimal, low, medium, high, xhigh, max. Maps may have holes. Values are tristate: omitted means standard levels through high use the provider default mapping while xhigh/max are unsupported; a string is sent to the provider; null marks the level unsupported so it is hidden/skipped/clamped away.

{ "id": "deepseek-v4-pro", "reasoning": true,
  "thinkingLevelMap": { "minimal": null, "low": null, "medium": null, "high": "high", "xhigh": null, "max": "max" } }

Use { "off": null } for models where thinking cannot be disabled. Older compat.reasoningEffortMap configs should move to model-level thinkingLevelMap.

Raw lines 77 to 87
88## Overriding Built-in Providers
90Base-URL-only overrides keep all built-in models and existing auth:
92```json
93{ "providers": { "anthropic": { "baseUrl": "https://my-proxy.example.com/v1" } } }
94```
96If `models` is included, built-in models are kept and custom models are upserted by `id` — a matching `id` replaces the built-in, a new `id` is added.
98`modelOverrides` customizes built-in and matching extension-registered models without replacing the provider list. Supported per-model fields: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `samplingParams` (merged per key), `headers`, `compat`. Unknown model IDs are ignored; provider-level `baseUrl`/`headers` can be combined with it. If `models` is also defined, custom models merge after built-in overrides.
100Example — opt a direct OpenAI GPT-5.6 model into the 1.05M context window (they default to `272000` to stay in the short-context pricing tier):
102```json
103{ "providers": { "openai": { "modelOverrides": { "gpt-5.6-sol": { "contextWindow": 1050000 } } } } }
104```

Overriding Built-in Providers

Base-URL-only overrides keep all built-in models and existing auth:

{ "providers": { "anthropic": { "baseUrl": "https://my-proxy.example.com/v1" } } }

If models is included, built-in models are kept and custom models are upserted by id — a matching id replaces the built-in, a new id is added.

modelOverrides customizes built-in and matching extension-registered models without replacing the provider list. Supported per-model fields: name, reasoning, thinkingLevelMap, input, cost (partial), contextWindow, maxTokens, samplingParams (merged per key), headers, compat. Unknown model IDs are ignored; provider-level baseUrl/headers can be combined with it. If models is also defined, custom models merge after built-in overrides.

Example — opt a direct OpenAI GPT-5.6 model into the 1.05M context window (they default to 272000 to stay in the short-context pricing tier):

{ "providers": { "openai": { "modelOverrides": { "gpt-5.6-sol": { "contextWindow": 1050000 } } } } }
Raw lines 88 to 105
106## Anthropic Messages Compatibility Flags
108`supportsEagerToolInputStreaming` (default `true`; `false` omits `tools[].eager_input_streaming` and sends the legacy fine-grained-tool-streaming beta header), `supportsLongCacheRetention` (default `true`, `cache_control.ttl: "1h"`), `sendSessionAffinityHeaders` (auto-detected), `supportsCacheControlOnTools` (default `true`), `forceAdaptiveThinking` (`thinking.type: "adaptive"` plus `output_config.effort`; built-in adaptive models set it automatically), `allowEmptySignature` (replay empty thinking signatures as `signature: ""`), `supportsStrictTools` (default `false`; built-in Anthropic models enable it).

Anthropic Messages Compatibility Flags

supportsEagerToolInputStreaming (default true; false omits tools[].eager_input_streaming and sends the legacy fine-grained-tool-streaming beta header), supportsLongCacheRetention (default true, cache_control.ttl: "1h"), sendSessionAffinityHeaders (auto-detected), supportsCacheControlOnTools (default true), forceAdaptiveThinking (thinking.type: "adaptive" plus output_config.effort; built-in adaptive models set it automatically), allowEmptySignature (replay empty thinking signatures as signature: ""), supportsStrictTools (default false; built-in Anthropic models enable it).

Raw lines 106 to 109
110## OpenAI Compatibility Flags
112`supportsStore`, `supportsDeveloperRole`, `supportsReasoningEffort`, `supportsUsageInStreaming` (default `true`), `supportsFinishReason` (default `true`; `false` makes Pi infer `stop`/`toolUse` when the stream ends without one), `maxTokensField` (`max_completion_tokens` | `max_tokens`), `requiresToolResultName`, `requiresAssistantAfterToolResult`, `requiresThinkingAsText`, `requiresReasoningContentOnAssistantMessages`, `thinkingFormat` (`openai`, `openrouter`, `deepseek`, `together`, `baseten`, `zai`, `qwen`, `chat-template`, `qwen-chat-template`, `string-thinking`, `ant-ling`), `chatTemplateKwargs`, `chatTemplateArgs` (`chat_template_args` values for `thinkingFormat: "baseten"`), `cacheControlFormat` (`anthropic`), `sendSessionAffinityHeaders` (default `false`), `sessionAffinityFormat` (`openai`, `openai-nosession`, `openrouter`), `supportsStrictMode`, `supportsOpenAIGrammarTools` (default `false`; the built-in catalog enables it for GPT-5+ on OpenAI, OpenAI Codex, Azure OpenAI, GitHub Copilot, opencode, Cloudflare AI Gateway), `vllmPriority`, `supportsMaxOutputTokens` (Responses), `supportsToolSearch` (Responses), `supportsLongCacheRetention` (default `true`; Responses uses `prompt_cache_options.ttl: "30m"` on GPT-5.6+ and `prompt_cache_retention: "24h"` on earlier models), `openRouterRouting`, `vercelGatewayRouting`.
114Thinking-format notes: `openrouter` uses `reasoning: { effort }`; `together` uses `reasoning: { enabled }` plus `reasoning_effort` when `supportsReasoningEffort`; `qwen` uses top-level `enable_thinking`; `qwen-chat-template` targets local Qwen servers needing `chat_template_kwargs.enable_thinking` and `preserve_thinking`; `chat-template` plus `chatTemplateKwargs` targets vLLM/Hugging Face templates, e.g. `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }` for DeepSeek V3.x. `baseten` plus `chatTemplateArgs` targets providers exposing toggle controls through `chat_template_args`, optionally with top-level `reasoning_effort`. `$var` accepts `thinking.enabled`, `thinking.effort`, or `thinking.budget`.
116`openRouterRouting` is sent as-is in the OpenRouter `provider` field (`only`, `order`, `ignore`, `sort`, `max_price`, `quantizations`, `zdr`, …). `vercelGatewayRouting` takes `only`/`order`.
119`inputLimits.images.resize` controls new image encoding (`maxWidth`, `maxHeight`, `maxBytes` for base64 payload, `jpegQuality`); historical images are not rewritten on model changes. `promptCache: { short, long }` declares best-effort lifetimes in seconds for cache warming. Both can be overridden through `modelOverrides`. Classifier models are separate from `/model`; use `ModelRuntime.getAvailableOfType("classifier")` and `classify()` or codemode's `models` API. Virtual model routing is experimental; use the current [virtual-model guide](https://pi.dev/docs/latest/virtual-models) and checked `jev-router.ts` example rather than changing persisted physical model identities yourself.

OpenAI Compatibility Flags

supportsStore, supportsDeveloperRole, supportsReasoningEffort, supportsUsageInStreaming (default true), supportsFinishReason (default true; false makes Pi infer stop/toolUse when the stream ends without one), maxTokensField (max_completion_tokens | max_tokens), requiresToolResultName, requiresAssistantAfterToolResult, requiresThinkingAsText, requiresReasoningContentOnAssistantMessages, thinkingFormat (openai, openrouter, deepseek, together, baseten, zai, qwen, chat-template, qwen-chat-template, string-thinking, ant-ling), chatTemplateKwargs, chatTemplateArgs (chat_template_args values for thinkingFormat: "baseten"), cacheControlFormat (anthropic), sendSessionAffinityHeaders (default false), sessionAffinityFormat (openai, openai-nosession, openrouter), supportsStrictMode, supportsOpenAIGrammarTools (default false; the built-in catalog enables it for GPT-5+ on OpenAI, OpenAI Codex, Azure OpenAI, GitHub Copilot, opencode, Cloudflare AI Gateway), vllmPriority, supportsMaxOutputTokens (Responses), supportsToolSearch (Responses), supportsLongCacheRetention (default true; Responses uses prompt_cache_options.ttl: "30m" on GPT-5.6+ and prompt_cache_retention: "24h" on earlier models), openRouterRouting, vercelGatewayRouting.

Thinking-format notes: openrouter uses reasoning: { effort }; together uses reasoning: { enabled } plus reasoning_effort when supportsReasoningEffort; qwen uses top-level enable_thinking; qwen-chat-template targets local Qwen servers needing chat_template_kwargs.enable_thinking and preserve_thinking; chat-template plus chatTemplateKwargs targets vLLM/Hugging Face templates, e.g. chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } } for DeepSeek V3.x. baseten plus chatTemplateArgs targets providers exposing toggle controls through chat_template_args, optionally with top-level reasoning_effort. $var accepts thinking.enabled, thinking.effort, or thinking.budget.

openRouterRouting is sent as-is in the OpenRouter provider field (only, order, ignore, sort, max_price, quantizations, zdr, …). vercelGatewayRouting takes only/order.

inputLimits.images.resize controls new image encoding (maxWidth, maxHeight, maxBytes for base64 payload, jpegQuality); historical images are not rewritten on model changes. promptCache: { short, long } declares best-effort lifetimes in seconds for cache warming. Both can be overridden through modelOverrides. Classifier models are separate from /model; use ModelRuntime.getAvailableOfType("classifier") and classify() or codemode's models API. Virtual model routing is experimental; use the current virtual-model guide and checked jev-router.ts example rather than changing persisted physical model identities yourself.

Raw lines 110 to 119