> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bettertoken.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Pi Agent: API de terceiros, Base URL, API Key e modelos

> Conecte o Pi Agent a uma API de terceiros com models.json, Base URL, API Key e Model ID. Configure protocolos, verifique a conexão e resolva erros comuns.

export const PiModelPicker = ({locale = "en", plazaUrl = "https://bettertoken.ai/pricing"}) => {
  const translations = {
    en: {
      get: "Get model list",
      refresh: "Refresh model list",
      loading: "Loading models...",
      api: "API",
      search: "Search models",
      models: "Models",
      selected: "Selected",
      select: "Select",
      copyId: "Copy Model ID",
      copyConfig: "Copy configuration",
      download: "Download models.json",
      copied: "Copied",
      copyError: "Copy failed. Select and copy the text below.",
      error: "Could not load models. Try again or open the model plaza.",
      empty: "No matching models.",
      plaza: "Model plaza",
      config: "Pi configuration",
      selectAll: "Select all visible models"
    },
    zh: {
      get: "获取模型列表",
      refresh: "刷新模型列表",
      loading: "正在获取模型...",
      api: "接口",
      search: "搜索模型",
      models: "模型",
      selected: "已选择",
      select: "选择",
      copyId: "复制 Model ID",
      copyConfig: "复制配置",
      download: "下载 models.json",
      copied: "已复制",
      copyError: "复制失败，请选中下方文本复制。",
      error: "暂时无法获取模型，请重试或前往模型广场。",
      empty: "没有匹配的模型。",
      plaza: "模型广场",
      config: "Pi 配置",
      selectAll: "选择当前显示的全部模型"
    },
    ru: {
      get: "Получить список моделей",
      refresh: "Обновить список моделей",
      loading: "Загрузка моделей...",
      api: "API",
      search: "Поиск моделей",
      models: "Модели",
      selected: "Выбрано",
      select: "Выбрать",
      copyId: "Скопировать Model ID",
      copyConfig: "Скопировать настройки",
      download: "Скачать models.json",
      copied: "Скопировано",
      copyError: "Не удалось скопировать. Выделите и скопируйте текст ниже.",
      error: "Не удалось загрузить модели. Повторите попытку или откройте каталог моделей.",
      empty: "Подходящих моделей нет.",
      plaza: "Каталог моделей",
      config: "Настройки Pi",
      selectAll: "Выбрать все показанные модели"
    },
    hi: {
      get: "मॉडल सूची प्राप्त करें",
      refresh: "मॉडल सूची रीफ़्रेश करें",
      loading: "मॉडल लोड हो रहे हैं...",
      api: "API",
      search: "मॉडल खोजें",
      models: "मॉडल",
      selected: "चुने गए",
      select: "चुनें",
      copyId: "Model ID कॉपी करें",
      copyConfig: "कॉन्फ़िगरेशन कॉपी करें",
      download: "models.json डाउनलोड करें",
      copied: "कॉपी हो गया",
      copyError: "कॉपी नहीं हो सका। नीचे दिए गए टेक्स्ट को चुनकर कॉपी करें।",
      error: "मॉडल लोड नहीं हो सके। फिर से प्रयास करें या मॉडल कैटलॉग खोलें।",
      empty: "कोई मेल खाने वाला मॉडल नहीं मिला।",
      plaza: "मॉडल कैटलॉग",
      config: "Pi कॉन्फ़िगरेशन",
      selectAll: "दिखाए गए सभी मॉडल चुनें"
    },
    es: {
      get: "Obtener lista de modelos",
      refresh: "Actualizar lista de modelos",
      loading: "Cargando modelos...",
      api: "API",
      search: "Buscar modelos",
      models: "Modelos",
      selected: "Seleccionados",
      select: "Seleccionar",
      copyId: "Copiar Model ID",
      copyConfig: "Copiar configuración",
      download: "Descargar models.json",
      copied: "Copiado",
      copyError: "No se pudo copiar. Selecciona y copia el texto de abajo.",
      error: "No se pudieron cargar los modelos. Inténtalo de nuevo o abre el catálogo.",
      empty: "No hay modelos coincidentes.",
      plaza: "Catálogo de modelos",
      config: "Configuración de Pi",
      selectAll: "Seleccionar todos los modelos visibles"
    },
    "pt-br": {
      get: "Obter lista de modelos",
      refresh: "Atualizar lista de modelos",
      loading: "Carregando modelos...",
      api: "API",
      search: "Buscar modelos",
      models: "Modelos",
      selected: "Selecionados",
      select: "Selecionar",
      copyId: "Copiar Model ID",
      copyConfig: "Copiar configuração",
      download: "Baixar models.json",
      copied: "Copiado",
      copyError: "Não foi possível copiar. Selecione e copie o texto abaixo.",
      error: "Não foi possível carregar os modelos. Tente novamente ou abra o catálogo.",
      empty: "Nenhum modelo correspondente.",
      plaza: "Catálogo de modelos",
      config: "Configuração do Pi",
      selectAll: "Selecionar todos os modelos visíveis"
    },
    ja: {
      get: "モデル一覧を取得",
      refresh: "モデル一覧を更新",
      loading: "モデルを取得中...",
      api: "API",
      search: "モデルを検索",
      models: "モデル",
      selected: "選択済み",
      select: "選択",
      copyId: "Model ID をコピー",
      copyConfig: "設定をコピー",
      download: "models.json をダウンロード",
      copied: "コピーしました",
      copyError: "コピーできませんでした。下のテキストを選択してコピーしてください。",
      error: "モデルを取得できませんでした。再試行するか、モデルカタログを開いてください。",
      empty: "一致するモデルがありません。",
      plaza: "モデルカタログ",
      config: "Pi の設定",
      selectAll: "表示中のモデルをすべて選択"
    },
    fr: {
      get: "Obtenir la liste des modèles",
      refresh: "Actualiser les modèles",
      loading: "Chargement des modèles...",
      api: "API",
      search: "Rechercher des modèles",
      models: "Modèles",
      selected: "Sélectionnés",
      select: "Sélectionner",
      copyId: "Copier le Model ID",
      copyConfig: "Copier la configuration",
      download: "Télécharger models.json",
      copied: "Copié",
      copyError: "La copie a échoué. Sélectionnez et copiez le texte ci-dessous.",
      error: "Impossible de charger les modèles. Réessayez ou ouvrez le catalogue.",
      empty: "Aucun modèle correspondant.",
      plaza: "Catalogue de modèles",
      config: "Configuration de Pi",
      selectAll: "Sélectionner tous les modèles affichés"
    },
    de: {
      get: "Modellliste abrufen",
      refresh: "Modellliste aktualisieren",
      loading: "Modelle werden geladen...",
      api: "API",
      search: "Modelle suchen",
      models: "Modelle",
      selected: "Ausgewählt",
      select: "Auswählen",
      copyId: "Model ID kopieren",
      copyConfig: "Konfiguration kopieren",
      download: "models.json herunterladen",
      copied: "Kopiert",
      copyError: "Kopieren fehlgeschlagen. Markiere und kopiere den Text unten.",
      error: "Modelle konnten nicht geladen werden. Versuche es erneut oder öffne den Modellkatalog.",
      empty: "Keine passenden Modelle.",
      plaza: "Modellkatalog",
      config: "Pi-Konfiguration",
      selectAll: "Alle angezeigten Modelle auswählen"
    },
    ko: {
      get: "모델 목록 가져오기",
      refresh: "모델 목록 새로고침",
      loading: "모델을 가져오는 중...",
      api: "API",
      search: "모델 검색",
      models: "모델",
      selected: "선택됨",
      select: "선택",
      copyId: "Model ID 복사",
      copyConfig: "설정 복사",
      download: "models.json 다운로드",
      copied: "복사됨",
      copyError: "복사하지 못했습니다. 아래 텍스트를 선택하여 복사하세요.",
      error: "모델을 가져오지 못했습니다. 다시 시도하거나 모델 카탈로그를 여세요.",
      empty: "일치하는 모델이 없습니다.",
      plaza: "모델 카탈로그",
      config: "Pi 설정",
      selectAll: "표시된 모든 모델 선택"
    }
  };
  const t = translations[locale] || translations.en;
  const protocols = {
    "openai-responses": {
      label: "Responses",
      endpoint: "openai-response",
      baseUrl: "https://bettertoken.ai/v1"
    },
    "openai-completions": {
      label: "Chat Completions",
      endpoint: "openai",
      baseUrl: "https://bettertoken.ai/v1"
    },
    "anthropic-messages": {
      label: "Anthropic Messages",
      endpoint: "anthropic",
      baseUrl: "https://bettertoken.ai"
    }
  };
  const [models, setModels] = useState(null);
  const [api, setApi] = useState("openai-completions");
  const [search, setSearch] = useState("");
  const [selected, setSelected] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);
  const [feedback, setFeedback] = useState("");
  const request = useRef(null);
  const protocolGroup = useId();
  useEffect(() => () => {
    request.current?.abort();
    request.current = null;
  }, []);
  const eligible = (models || []).filter(model => model.endpoints.includes(protocols[api].endpoint));
  const visible = eligible.filter(model => model.id.toLowerCase().includes(search.trim().toLowerCase()));
  const chosen = eligible.filter(model => selected.includes(model.id));
  const configuration = JSON.stringify({
    providers: {
      bettertoken: {
        baseUrl: protocols[api].baseUrl,
        api,
        apiKey: "$BETTERTOKEN_API_KEY",
        models: chosen.map(model => ({
          id: model.id
        }))
      }
    }
  }, null, 2);
  const loadModels = async () => {
    if (request.current) return;
    const controller = new AbortController();
    request.current = controller;
    setLoading(true);
    setError(false);
    setFeedback("");
    const timer = setTimeout(() => controller.abort(), 15000);
    try {
      const response = await fetch("https://bettertoken.ai/api/pricing", {
        credentials: "omit",
        cache: "no-store",
        headers: {
          Accept: "application/json"
        },
        signal: controller.signal
      });
      if (!response.ok) throw new Error("Catalog request failed");
      const body = await response.json();
      if (body.success !== true || !Array.isArray(body.data)) throw new Error("Invalid catalog");
      const catalog = new Map();
      for (const entry of body.data) {
        if (typeof entry?.model_name !== "string" || !entry.model_name.trim() || !Array.isArray(entry.supported_endpoint_types)) continue;
        if ((/^(?:nano-banana-|gpt-image-)/i).test(entry.model_name)) continue;
        if (entry.supported_endpoint_types.some(endpoint => ["image-generation", "openai-video", "embeddings", "jina-rerank"].includes(endpoint))) continue;
        const endpoints = entry.supported_endpoint_types.filter(endpoint => Object.values(protocols).some(protocol => protocol.endpoint === endpoint));
        if (!endpoints.length) continue;
        const previous = catalog.get(entry.model_name);
        catalog.set(entry.model_name, {
          id: entry.model_name,
          endpoints: Array.from(new Set([...previous?.endpoints || [], ...endpoints]))
        });
      }
      const next = Array.from(catalog.values()).sort((a, b) => a.id.localeCompare(b.id, "en"));
      if (request.current !== controller) return;
      setModels(next);
      setSelected(current => current.filter(id => next.some(model => model.id === id && model.endpoints.includes(protocols[api].endpoint))));
    } catch {
      if (request.current === controller) setError(true);
    } finally {
      clearTimeout(timer);
      if (request.current === controller) {
        request.current = null;
        setLoading(false);
      }
    }
  };
  const copy = async value => {
    try {
      await navigator.clipboard.writeText(value);
      setFeedback(t.copied);
    } catch {
      setFeedback(t.copyError);
    }
  };
  const download = () => {
    const url = URL.createObjectURL(new Blob([configuration + "\n"], {
      type: "application/json"
    }));
    const link = document.createElement("a");
    link.href = url;
    link.download = "models.json";
    link.click();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  };
  return <div className="bt-pi-models not-prose">
      <div className="bt-pi-models-toolbar">
        <fieldset className="bt-pi-models-protocols" disabled={loading}>
          <legend>{t.api}</legend>
          <div className="bt-pi-models-options">
            {Object.entries(protocols).map(([value, protocol]) => <label key={value}>
                <input type="radio" name={protocolGroup} value={value} checked={api === value} onChange={() => {
    setApi(value);
    setSelected([]);
    setFeedback("");
  }} />
                <span>{protocol.label}</span>
              </label>)}
          </div>
        </fieldset>
        <button type="button" className="bt-pi-models-load" disabled={loading} onClick={loadModels}>
          <Icon icon={models === null ? "list" : "rotate-right"} size={16} />
          <span>{loading ? t.loading : models === null ? t.get : t.refresh}</span>
        </button>
      </div>
      {error && <p role="alert" className="bt-pi-models-error">{t.error} <a href={plazaUrl}>{t.plaza}</a></p>}
      {models !== null && <div>
          <label className="bt-pi-models-field bt-pi-models-search">
            <span>{t.search}</span>
            <input type="search" value={search} onChange={event => setSearch(event.target.value)} />
          </label>
          <p className="bt-pi-models-count" aria-live="polite">{t.models}: {visible.length} / {eligible.length} · {t.selected}: {chosen.length}</p>
          <div className="bt-pi-models-list">
            <table>
              <thead>
                <tr>
                  <th className="bt-pi-models-check">
                    <input type="checkbox" aria-label={t.selectAll} disabled={!visible.length} checked={visible.length > 0 && visible.every(model => selected.includes(model.id))} onChange={event => setSelected(current => event.target.checked ? Array.from(new Set([...current, ...visible.map(model => model.id)])) : current.filter(id => !visible.some(model => model.id === id)))} />
                  </th>
                  <th>Model ID</th>
                  <th className="bt-pi-models-copy" aria-label={t.copyId}></th>
                </tr>
              </thead>
              <tbody>
                {visible.map(model => <tr key={model.id}>
                    <td className="bt-pi-models-check">
                      <input type="checkbox" aria-label={t.select + " " + model.id} checked={selected.includes(model.id)} onChange={event => setSelected(current => event.target.checked ? [...current, model.id] : current.filter(id => id !== model.id))} />
                    </td>
                    <td><code>{model.id}</code></td>
                    <td className="bt-pi-models-copy">
                      <button type="button" className="bt-pi-models-icon" title={t.copyId} aria-label={t.copyId + " " + model.id} onClick={() => copy(model.id)}><Icon icon="copy" size={16} /></button>
                    </td>
                  </tr>)}
              </tbody>
            </table>
            {!visible.length && <p className="bt-pi-models-empty">{t.empty}</p>}
          </div>
          {chosen.length > 0 && <div className="bt-pi-models-config">
              <div className="bt-pi-models-config-header">
                <span>{t.config}</span>
                <div className="bt-pi-models-actions">
                  <button type="button" className="bt-pi-models-icon" title={t.copyConfig} aria-label={t.copyConfig} onClick={() => copy(configuration)}><Icon icon="copy" size={16} /></button>
                  <button type="button" className="bt-pi-models-icon" title={t.download} aria-label={t.download} onClick={download}><Icon icon="download" size={16} /></button>
                </div>
              </div>
              <pre aria-label={t.config}><code>{configuration}</code></pre>
            </div>}
        </div>}
      <p role="status" className="bt-pi-models-feedback">{feedback}</p>
    </div>;
};

O Pi Agent, agente de programação de terminal Pi, conecta APIs compatíveis de terceiros pelo `models.json`. O exemplo usa BetterToken: defina a chave, a Base URL do protocolo e o Model ID. Para outro serviço, use a URL, as credenciais e os IDs de modelos desse serviço.

## Configurações principais

| Campo | Valor |
| - | - |
| API Key | BetterToken API Key pela variável `BETTERTOKEN_API_KEY` |
| Base URL | Protocolo OpenAI: `https://bettertoken.ai/v1`; protocolo Anthropic: `https://bettertoken.ai` |
| Model | O Model ID exato do <a href={"https://bettertoken.ai/pricing"}>model plaza</a> |
| Arquivo de configuração | `~/.pi/agent/models.json` |

## Pré-requisitos

* Node.js 22.19 ou mais recente.
* <a href={"https://bettertoken.ai/register"}>Crie uma BetterToken API Key</a> e escolha um modelo com chamadas de ferramentas no model plaza.
* No Windows nativo, instale [Git for Windows](https://git-scm.com/downloads/win) para a ferramenta Bash do Pi, ou execute o Pi no WSL.

## Instalação

Use o comando npm oficial atual do Pi:

```bash theme={null}
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
pi --version
```

## Configuração manual

### Defina a API Key

No terminal que iniciará o Pi, substitua `YOUR_API_KEY` pela sua chave:

<Tabs>
  <Tab title="macOS / Linux">
    ```bash theme={null}
    export BETTERTOKEN_API_KEY="YOUR_API_KEY"
    ```
  </Tab>

  <Tab title="Windows PowerShell">
    ```powershell theme={null}
    $env:BETTERTOKEN_API_KEY="YOUR_API_KEY"
    ```
  </Tab>
</Tabs>

### Obtenha modelos e gere a configuração

Clique em **Obter lista de modelos** para consultar o catálogo público atual do BetterToken sem informar uma API Key. Escolha uma API e modelos com suporte a chamadas de ferramentas; depois copie um Model ID ou baixe o `models.json` gerado.

Se o `models.json` já existir, mescle a entrada `bettertoken` gerada com a configuração atual em vez de substituir o arquivo. A configuração usa a variável `$BETTERTOKEN_API_KEY` definida acima; adicione outros parâmetros conforme as capacidades reais do modelo.

O Pi aceita vários protocolos; escolha um compatível com o modelo e o servidor. Para esta conexão, recomendamos **Anthropic Messages** para Claude, **Responses** para GPT quando o servidor oferecer suporte e **Chat Completions** para outros modelos compatíveis com OpenAI. O seletor usa Chat Completions por padrão e filtra pelas APIs declaradas no catálogo público. Se o modelo não aparecer em Responses, use o exemplo manual abaixo para modelos com suporte confirmado a Responses.

<PiModelPicker locale="pt-br" plazaUrl="https://bettertoken.ai/pricing" />

### Adicione a URL e o modelo

Crie `~/.pi/agent/models.json`. No Windows, o caminho padrão é `%USERPROFILE%\.pi\agent\models.json`. Se o arquivo já existir, adicione a entrada `bettertoken` ao objeto `providers` existente.

Como alternativa à configuração gerada, preencha manualmente este exemplo de Responses API. Substitua `YOUR_MODEL_ID` por um ID do model plaza e mantenha `$BETTERTOKEN_API_KEY` como referência à variável:

```json theme={null}
{
  "providers": {
    "bettertoken": {
      "baseUrl": "https://bettertoken.ai/v1",
      "api": "openai-responses",
      "apiKey": "$BETTERTOKEN_API_KEY",
      "models": [{ "id": "YOUR_MODEL_ID" }]
    }
  }
}
```

Para outra API, altere `baseUrl` e `api` com uma combinação correspondente:

| API | `api` | `baseUrl` |
| - | - | - |
| Responses API | `openai-responses` | `https://bettertoken.ai/v1` |
| Chat Completions | `openai-completions` | `https://bettertoken.ai/v1` |
| Anthropic Messages | `anthropic-messages` | `https://bettertoken.ai` |

Escolha uma API com chamadas de ferramentas para seu modelo. Por exemplo, GPT-6.1 Sol usa Responses. Mantenha a Base URL como na tabela; o Pi adiciona o caminho da requisição.

### Inicie o Pi

No diretório do projeto, use o Model ID configurado:

```bash theme={null}
pi --provider bettertoken --model YOUR_MODEL_ID
```

Defina a variável novamente em um novo terminal ou salve-a na configuração do shell.

## Verificar a conexão

Execute `pi --list-models` e confirme que `bettertoken` e seu modelo aparecem. Depois, inicie o Pi e envie:

```text theme={null}
Reply with PI_OK only. Do not use tools.
```

Após receber `PI_OK`, teste uma pequena tarefa somente de leitura para verificar as chamadas de ferramentas.

## Trocar de modelo

Adicione entradas ao array `models` e execute `/model` no Pi para recarregar `models.json`. Selecione um modelo e pressione `Ctrl+S` para torná-lo padrão em novas sessões. Use nomes locais diferentes para protocolos distintos, cada um com seu `api` e `baseUrl`.

## Erros comuns

| Sintoma | Solução |
| - | - |
| Modelo ausente na lista | Confira o JSON, o Model ID e a variável de ambiente; abra `/model` novamente. |
| `401` ou `Unauthorized` | Verifique se a variável contém uma BetterToken API Key válida e se o Pi inicia no mesmo terminal. |
| `404` ou URL incorreta | Combine `api` e `baseUrl` conforme a tabela. Remova `/responses`, `/chat/completions` ou `/v1/messages` adicionados manualmente. |
| Texto funciona, mas ferramentas falham | Verifique o suporte do modelo e da API; use `openai-responses` para modelos que exigem Responses. |
| Bash ausente no Windows | Instale Git for Windows e reabra o terminal, ou configure `shellPath` seguindo o guia do Pi para Windows. |

## Configuração avançada

Para imagens, níveis de raciocínio ou contexto maior, defina `input`, `reasoning`, `contextWindow` e `maxTokens` de acordo com as capacidades reais do modelo. Consulte o [guia de seleção de modelos](/pt-br/faq/model-calling/model-selection-guide).

## Detalhes técnicos

`providers` e `--provider` são campos de configuração do Pi; `bettertoken` é o nome local deste exemplo. O Pi consulta primeiro `--api-key`, depois as credenciais de `auth.json` e, em seguida, `apiKey` em `models.json`. Se esse nome tiver credenciais antigas salvas, remova a entrada correspondente pelo `/logout` no Pi e conecte novamente.

### Referências oficiais do Pi

* [Instalação e início rápido](https://pi.dev/docs/latest/quickstart)
* [URLs e modelos personalizados](https://pi.dev/docs/latest/models#configure-a-compatible-endpoint)
* [Referência da linha de comando](https://pi.dev/docs/latest/cli)
* [Configuração do Windows](https://pi.dev/docs/latest/windows)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.