@gtrabanco/pi-nan-provider

extensionmaintained

NaN Builders (api.nan.builders) model provider for pi - OpenAI-compatible registration with a models.dev-generated fallback, tier-aware live catalog, and MCP bridges (official web search + optional community media server)

by — · v0.7.0 · published 1d ago

$ pi install npm:@gtrabanco/pi-nan-provider
downloads/mo
2.6K
stars
10
last push
1d ago
open issues
1

Signals

license: MITtestspi manifest: missinginstall size: —deps: 0peer deps: 0

Download trend

3.2K downloads · last 12 weeks (weekly)

README

@gtrabanco/pi-nan-provider

License: MIT Version

NaN Builders model provider + MCP bridges for pi.

Registers the nan provider via pi.registerProvider() using NaN's OpenAI-compatible API (https://api.nan.builders/v1), and bridges NaN's MCP tools into pi with pi.registerTool().


⚡ Quick Start

  1. Get an API Key: Claim your NaN API key here (referral link).
  2. Install:
    pi install npm:@gtrabanco/pi-nan-provider
    
  3. Authenticate:
    export NAN_API_KEY="sk-your-key-here"
    
  4. Verify:
    pi --list-models nan
    

Docs in English (this file) · Documentación en español

⚙️ How it works

The provider uses a two-layer model catalog to ensure reliability:

LayerSourcePurpose
1. Generated Fallbackscripts/models.generated.tsBuild-time snapshot from models.dev. Ensures pi can always start, even if the network fails.
2. Live /models FetchNaN Runtime APIFetches your real-time available models based on your API key's tier. Merged with fallback data.

[!IMPORTANT] Tier Detection: The live list is authoritative. If your key has premium access, those models will appear automatically; otherwise, they are filtered out.

The registration is synchronous on purpose: the generated fallback catalog is available immediately, and pi's Models runtime drives the live refresh (network refresh at interactive startup and periodically, cache-only at registration), persisting the overlay between runs.

🧠 Model-switch safety (cross-model reasoning guard)

When you switch models, pi-ai replays the previous model's reasoning as plain assistant text — with no size bound. A single long or degenerate reasoning trace can therefore overflow a 262K-context model's window, and NaN answers with a generic 400 Invalid request. Check your request parameters. that looks like a provider bug (upstream tracking: pi-nan-provider#3; open upstream issue: pi#6167).

This package drops every replayed cross-model reasoning block, so switching from a 1M-context model to a 262K one (qwen3.6) no longer overflows the window. The models' answers and tool results are untouched — only their internal reasoning traces are removed, so qwen3.6 can still answer about what another model did. Same-model reasoning is never altered, and the guard only acts on requests targeting this package's providers. Set NAN_THINKING_GUARD=0 to disable it.

If a request still overflows — the guard is disabled, the inflation is not a reasoning block (large tool outputs, images), or the destination window is simply smaller — NaN answers the same generic 400 instead of naming the overflow, and pi's auto-compaction does not recognize it, so the session wedges at the ceiling. The provider therefore re-checks the request size on the way out: when that generic 400 arrives for a request estimated over the model's window, the error is rewritten into a context-overflow message pi recognizes, so it compacts and retries instead of stalling. A generic 400 on a within-window request is left untouched, so unrelated errors are never mislabelled.

⏱️ Intermittent truncated streams (auto-retry, no silent stall)

NaN's LiteLLM gateway occasionally closes an SSE stream before emitting the final finish_reason chunk (observed on glm5.3-flash; issue #2). The catalog declares supportsFinishReason: true, so pi-ai turns that into the error Stream ended without finish_reason — which matches pi's retryable-provider pattern and is retried automatically, instead of silently accepting a half-finished answer. If a gateway version never sends finish_reason, the turn now fails visibly once the retry budget is exhausted.

You can override any model's compat per-model in ~/.pi/agent/models.json (pi's docs/models.md → Per-model Overrides); overrides compose above the registered provider. Example (forcing the retry behavior explicitly):

{
  "providers": {
    "nan": {
      "modelOverrides": {
        "glm5.3-flash": { "compat": { "supportsFinishReason": true } }
      }
    }
  }
}

Setting supportsFinishReason: false restores the old silent-stall behavior — not recommended.

Streaming token usage: supportsUsageInStreaming is true by default. NaN's published schema does not document stream_options, but the live gateway honors it — measured 2026-09-16 (#7): two identical streaming calls per model, 0 usage chunks without the flag and exactly 1 with it, on deepseek-v4-flash, glm5.3-flash, qwen3.6, mimo-v2.5 and gemma4. pi therefore reports real input/output/reasoning/cache token counts instead of zeros. If a model turns out not to report streaming usage, opt out per model — the request sanitizer then strips stream_options and the payload stays strict:

{
  "providers": {
    "nan": {
      "modelOverrides": {
        "some-model": { "compat": { "supportsUsageInStreaming": false } }
      }
    }
  }
}

🔑 Authentication

resolve() checks the stored credential first, then falls back to the matching environment variable.

MethodCommand / ActionNotes
Env Varexport NAN_API_KEY="..."Fastest for local development.
/loginpi > /login nanPersistent; stores in ~/.pi/agent/auth.json.
Manual ConfigEdit ~/.pi/agent/auth.jsonDirect JSON manipulation.

Get a key from the NaN platform (user settings → API Keys; referral link).

🔌 MCP Bridges

Since pi does not include a built-in MCP client, this package bridges MCP servers as native pi tools.

Both bridges are enabled and lazy by default. Use /nan-mcp to manage them.

🛠️ Management Command: /nan-mcp

CommandEffect
/nan-mcp statusShows current state of both bridges.
/nan-mcp enable [target]Enables web-search or nan-mcp-server (persisted).
/nan-mcp disable [target]Disables a bridge persistently.

1. Official NaN MCP Server

Official bridge for remote tools via https://api.nan.builders/mcp.

  • nan_web_search(query, ...): Performs web searches through NaN's gateway.

2. Community Media MCP Server

Bridges nan-mcp-server via a minimal local stdio client.

  • Lazy Loading: The server process is spawned only when a tool is invoked and terminated immediately after.
  • Config: Files land in ~/nan-mcp-output/.
ToolPurpose
nan_generate_imageImage generation (flux-2-klein)
nan_edit_imageImage-to-image editing (flux-2-klein)
nan_text_to_speechAudio synthesis (kokoro)
nan_list_voicesList available voices
nan_speech_to_textAudio transcription (whisper)

🔧 Media Bridge Configuration

VariableDefaultDescription
NAN_MEDIA_MCP—Per-session override (0 or false to disable).
NAN_MEDIA_MCP_VERSION1.1.2Pinned server version (recommended).
NAN_MEDIA_MCP_COMMAND—Custom command override.
NAN_MEDIA_MCP_TIMEOUT_MS120000Per-call timeout.
NAN_MCP_TOOLS—Override for the official bridge (0 to disable).

🤖 Automated Update Detection

A newer nan-mcp-server release won't silently drift this bridge's pin. The scheduler (.github/workflows/check-nan-mcp-server-update.yml) runs weekly and, when a newer version is found, opens an issue describing if the bump is breaking or safe.

bun run check-nan-mcp-server            # human-readable report
bun run check-nan-mcp-server --json     # machine-readable JSON
bun run check-nan-mcp-server --issue    # create/refresh the issue

📊 Quota Usage: /nan-usage

Shows your NaN token usage per model, monthly limits, and time until the billing cycle resets.

How it works

/nan-usage reads the session token from ~/.config/nan/session.json — the same file the NaN CLI uses. If the file exists and contains a valid session, the command fetches real usage data from NaN's dashboard. Otherwise, it shows static quota limits from the docs.

Setup

  1. Install the NaN CLI:
    curl -fsSL https://nan.builders/install | sh
    
  2. Log in:
    nan auth login
    
    This sends a sign-in link to your email. Paste the link back into the terminal.
  3. Use in pi:
    /nan-usage
    

[!TIP] The session token is shared automatically — no env vars or extra config needed. If the session expires, run nan auth login again.

What you see

With a valid session (real usage):

📊 NaN Quota Status

⏱️  Next billing reset: 2026-10-01 UTC (8d 14h 32m 15s)

Models with monthly caps:

DeepSeek V4 Flash:
  [████████░░░░░░░░░░░░] 40.2%
  Used: 1.2B / 3.0B (1.8B remaining)

MiMo V2.5:
  [██░░░░░░░░░░░░░░░░░░] 12.5%
  Used: 125.0M / 1.0B (875.0M remaining)

Uncapped models:

Qwen 3.6: 890.5K used
Gemma 4: 234.1K used

Without a session (static limits only):

📊 NaN Quota Status (static limits)

⏱️  Next billing reset: 2026-10-01 UTC (8d 14h 32m 15s)

Model                        Monthly Cap
─────────────────────────────────────────────────
DeepSeek V4 Flash            3.0B
MiMo V2.5                    1.0B
Qwen 3.6                     uncapped
Gemma 4                      uncapped
Qwen 3.8 Flash               500.0M
GLM 5.3 Flash                2.0B
GLM 5.3 👑                   3.0B (rolling 400.0M/4h)

💡 Run `nan auth login` to see real usage data.

📊 Models

Baseline catalog (verified against NaN docs and OpenAPI).

ModelContextMax OutputInputReasoning
qwen3.6262,14465,536text, image✅
gemma4262,14432,768text, image✅
deepseek-v4-flash1,000,000384,000text, image✅
mimo-v2.51,048,576131,072text, image✅
mimo-v2.6-flash1,048,576131,072text, image✅
glm5.3-flash1,000,000131,072text, image✅
qwen3.8-flash262,144131,072text, image✅

[!NOTE]
mimo-v2.6-flash is served by NaN but not yet listed on models.dev provider nan; it enters the catalog through a manual-only entry with the same limits as mimo-v2.5. The model will be auto-detected from models.dev once added there.


🧠 Reasoning controls

NaN's reasoning_effort parameter controls how much the model thinks before answering — but the degree of control varies by model:

ModelReasoning effortHow it works
glm5.3, glm5.3-flashlow · medium · high · maxFully controllable — higher values let the model reason longer
qwen3.6, gemma4none · minimal · low · medium · high · maxnone/minimal skip reasoning entirely; others cap at 2K / 8K / 16K / 32K tokens
deepseek-v4-flash, qwen3.8-flash, mimo-v2.5, mimo-v2.6-flash(accepted but not adjustable)The parameter is accepted and never rejected, but the model manages its own reasoning depth — it is never an error to send a value these models don't adjust

🚀 Development

bun install
bun run generate-models   # Regenerate fallback catalog
bun test                  # Run all tests
bun run typecheck         # Run typechecking

Releases follow strict semver. CI publishes automatically on merge to main.