Pi の設定、拡張、プラットフォーム設定、API リファレンス。

カスタムProviders

Extensions は、pi.registerProvider() を介してカスタム モデル プロバイダーを登録できます。これにより、次のことが可能になります。

  • プロキシ - 企業プロキシまたは API ゲートウェイ経由でリクエストをルーティングします。
  • カスタム エンドポイント - セルフホスト型またはプライベート モデルのデプロイメントを使用します。
  • OAuth/SSO - エンタープライズプロバイダーの認証フローを追加します
  • カスタム APIs - 非標準 LLM APIs のストリーミングを実装します。

例 Extensions

これらの完全なプロバイダーの例を参照してください。

目次

クイックリファレンス

Extensions は、完全な pi-ai Provider を登録することも、従来のプロバイダー設定フォームを使用することもできます。カスタム認証、フィルタリング、更新、またはストリーミング動作が必要な場合は、完全なプロバイダーを推奨します。 Pi は、登録されたネイティブ プロバイダーの上にある models.json をオーバーライドします。

import { createProvider, openAICompletionsApi } from "@earendil-works/pi-ai";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  pi.registerProvider(createProvider({
    id: "native-local",
    name: "Native Local",
    baseUrl: "http://localhost:8080/v1",
    auth: {
      apiKey: {
        name: "Local server API key",
        async login(interaction) {
          return {
            type: "api_key",
            key: await interaction.prompt({ type: "secret", message: "API key" })
          };
        },
        async resolve({ credential }) {
          return credential?.key
            ? { auth: { apiKey: credential.key }, source: "stored API key" }
            : undefined;
        }
      }
    },
    models: [],
    api: openAICompletionsApi()
  }));

  // Legacy provider-config form:
  // Override baseUrl for existing provider
  pi.registerProvider("anthropic", {
    baseUrl: "https://proxy.example.com"
  });

  // Register new provider with models
  pi.registerProvider("my-provider", {
    name: "My Provider",
    baseUrl: "https://api.example.com",
    apiKey: "$MY_API_KEY",
    api: "openai-completions",
    models: [
      {
        id: "my-model",
        name: "My Model",
        reasoning: false,
        input: ["text", "image"],
        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
        contextWindow: 128000,
        maxTokens: 4096
      }
    ]
  });
}

拡張ファクトリーは async にすることもできます。動的なモデル検出の場合、session_start ではなくファクトリでモデルをフェッチして登録します。 pi は起動を続行する前にファクトリーを待機するため、プロバイダーは対話型起動中および pi --list-models まで使用できます。

既存のプロバイダーを上書きする

最も単純な使用例: プロキシ経由で既存のプロバイダーをリダイレクトします。

// All Anthropic requests now go through your proxy
pi.registerProvider("anthropic", {
  baseUrl: "https://proxy.example.com"
});

// Add custom headers to OpenAI requests
pi.registerProvider("openai", {
  headers: {
    "X-Custom-Header": "value"
  }
});

// Both baseUrl and headers
pi.registerProvider("google", {
  baseUrl: "https://ai-gateway.corp.com/google",
  headers: {
    "X-Corp-Auth": "$CORP_AUTH_TOKEN"  // env var or literal
  }
});

baseUrl および/または headers のみが提供される場合 (models は提供されない)、そのプロバイダーの既存のモデルはすべて新しいエンドポイントで保持されます。

新しいプロバイダーを登録する

完全に新しいプロバイダーを追加するには、必要な構成とともに models を指定します。

モデル リストがリモート エンドポイントから取得される場合は、非同期拡張ファクトリを使用します。

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default async function (pi: ExtensionAPI) {
  const response = await fetch("http://localhost:1234/v1/models");
  const payload = (await response.json()) as {
    data: Array<{
      id: string;
      name?: string;
      context_window?: number;
      max_tokens?: number;
    }>;
  };

  pi.registerProvider("local-openai", {
    baseUrl: "http://localhost:1234/v1",
    apiKey: "$LOCAL_OPENAI_API_KEY",
    api: "openai-completions",
    models: payload.data.map((model) => ({
      id: model.id,
      name: model.name ?? model.id,
      reasoning: false,
      input: ["text"],
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      contextWindow: model.context_window ?? 128000,
      maxTokens: model.max_tokens ?? 4096,
    })),
  });
}

これにより、起動が完了する前に、取得したモデルが登録されます。

pi.registerProvider("my-llm", {
  baseUrl: "https://api.my-llm.com/v1",
  apiKey: "$MY_LLM_API_KEY",  // env var reference
  api: "openai-completions",  // which streaming API to use
  models: [
    {
      id: "my-llm-large",
      name: "My LLM Large",
      reasoning: true,        // supports extended thinking
      input: ["text", "image"],
      cost: {
        input: 3.0,           // $/million tokens
        output: 15.0,
        cacheRead: 0.3,
        cacheWrite: 3.75
      },
      contextWindow: 200000,
      maxTokens: 16384
    }
  ]
});

models が提供されると、そのプロバイダーの既存のモデルがすべて置き換えされます。

apiKey およびカスタム ヘッダー値は、models.json と同じ構成値構文を使用します。 !command は、最初に値全体のコマンドを実行し、$ENV_VAR${ENV_VAR} は環境変数を補間し、$ はリテラル ``apiKey およびカスタム ヘッダー値は、models.jsonと同じ構成値構文を使用します。!command は、最初に値全体のコマンドを実行し、$ENV_VAR${ENV_VAR} は環境変数を補間し、$ はリテラル を出力し、$! はリテラルを出力します!`。

プロバイダーの登録を解除する

以前に pi.registerProvider(name,...) で登録されたプロバイダーを削除するには、pi.unregisterProvider(name) を使用します。

// Register
pi.registerProvider("my-llm", {
  baseUrl: "https://api.my-llm.com/v1",
  apiKey: "$MY_LLM_API_KEY",
  api: "openai-completions",
  models: [
    {
      id: "my-llm-large",
      name: "My LLM Large",
      reasoning: true,
      input: ["text", "image"],
      cost: { input: 3.0, output: 15.0, cacheRead: 0.3, cacheWrite: 3.75 },
      contextWindow: 200000,
      maxTokens: 16384
    }
  ]
});

// Later, remove it
pi.unregisterProvider("my-llm");

登録を解除すると、そのプロバイダーの動的モデル、API key フォールバック、OAuth プロバイダー登録、およびカスタム ストリーム ハンドラー登録が削除されます。オーバーライドされた組み込みモデルまたはプロバイダーの動作は復元されます。

最初の拡張ロード フェーズの後に行われた呼び出しはすぐに適用されるため、/reload は必要ありません。

API 種類

api フィールドは、どのストリーミング実装が使用されるかを決定します。

API 用途
anthropic-messages Anthropic Claude API とその互換品
openai-completions OpenAI Chat Completions API と互換性
openai-responses OpenAI の応答 API
azure-openai-responses Azure OpenAI の応答 API
openai-codex-responses OpenAI コーデックスの応答 API
mistral-conversations ネイティブ ミストラル チャット完了ストリーミング
google-generative-ai Google ジェネレーティブ AI API
google-vertex Google Vertex AI API
bedrock-converse-stream アマゾン ベッドロック コンバース API

ほとんどの OpenAI 互換プロバイダーは openai-completions で動作します。モデル固有の思考レベルにはモデルレベルの thinkingLevelMap を使用し、プロバイダーの癖には compat を使用します。 xhigh および max レベルはオプトインであり、null 以外のマップ エントリが必要で、サポートされていないホールによって分離される場合があります。

models: [{
  id: "custom-model",
  // ...
  reasoning: true,
  thinkingLevelMap: {              // map pi levels to provider values; null hides unsupported levels
    minimal: null,
    low: null,
    medium: null,
    high: "default",
    xhigh: null,
    max: "max"
  },
  compat: {
    supportsDeveloperRole: false,   // use "system" instead of "developer"
    supportsReasoningEffort: true,
    maxTokensField: "max_tokens",   // instead of "max_completion_tokens"
    requiresToolResultName: true,   // tool results need name field
    thinkingFormat: "qwen",        // top-level enable_thinking: true
    cacheControlFormat: "anthropic" // Anthropic-style cache_control markers
  }
}]

OpenRouter スタイルの reasoning: { effort } コントロールには openrouter を使用します。 Together スタイルの reasoning: { enabled } コントロールには together を使用します。 supportsReasoningEffort を使用すると、reasoning_effort も送信されます。 chat_template_kwargs.enable_thinking を読み取り、preserve_thinking を必要とするローカルの Qwen 互換サーバーには、qwen-chat-template を使用します。 システム プロンプト、最後のツール定義、および最後のユーザー、アシスタント、またはツール結果のテキスト コンテンツで、cache_control を介して Anthropic スタイルのプロンプト キャッシュを公開する OpenAI 互換プロバイダーには、cacheControlFormat: "anthropic" を使用します。

api: "anthropic-messages" を使用する Anthropic 互換プロバイダーの場合、アップストリーム モデルが適応的思考 (thinking.type: "adaptive" プラス output_config.effort) を必要とするモデルまたはプロバイダーに compat.forceAdaptiveThinking: true を設定します。組み込みの適応クロード モデルは、これを自動的に設定します。空の思考シグネチャを発行し、リプレイ時に signature: "" を期待するプロバイダーにのみ compat.allowEmptySignature: true を設定します。

移行メモ: ミストラルは openai-completions から mistral-conversations に移行しました。 ネイティブの Mistral モデルには mistral-conversations を使用します。 Mistral 互換/カスタム エンドポイントを意図的に openai-completions 経由でルーティングする場合は、必要に応じて compat フラグを明示的に設定します。

認証ヘッダー

プロバイダーが Authorization: Bearer <key> を予期しているが、標準の API を使用していない場合は、authHeader: true を設定します。

pi.registerProvider("custom-api", {
  baseUrl: "https://api.example.com",
  apiKey: "$MY_API_KEY",
  authHeader: true,  // adds Authorization: Bearer header
  api: "openai-completions",
  models: [...]
});

キーはリクエストごとに解決されます。明示的なリクエスト Authorization ヘッダーは、生成された値よりも優先されます。

OAuth サポート

/login と統合する OAuth/SSO 認証を追加します。

import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";

pi.registerProvider("corporate-ai", {
  baseUrl: "https://ai.corp.com/v1",
  api: "openai-responses",
  models: [...],
  oauth: {
    name: "Corporate AI (SSO)",

    async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
      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");

      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);

      return {
        refresh: tokens.refreshToken,
        access: tokens.accessToken,
        expires: Date.now() + tokens.expiresIn * 1000
      };
    },

    async refreshToken(credentials: OAuthCredentials, signal: AbortSignal): Promise<OAuthCredentials> {
      const tokens = await refreshAccessToken(credentials.refresh, signal);
      return {
        refresh: tokens.refreshToken ?? credentials.refresh,
        access: tokens.accessToken,
        expires: Date.now() + tokens.expiresIn * 1000
      };
    },

    getApiKey(credentials: OAuthCredentials): string {
      return credentials.access;
    }
  }
});

登録後、ユーザーは/login corporate-ai 経由で認証できます。

OAuthログインコールバック

callbacks オブジェクトは、プロバイダー所有のフローに対して UI に依存しない対話を提供します。

interface OAuthLoginCallbacks {
  // Open URL in browser (for OAuth redirects)
  onAuth(params: { url: string }): void;

  // Show device code (for device authorization flow)
  onDeviceCode(params: {
    userCode: string;
    verificationUri: string;
    intervalSeconds?: number;
    expiresInSeconds?: number;
  }): void;

  // Show transient progress
  onProgress?(message: string): 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>;
}

OAuth認証情報

資格情報は ~/.pi/agent/auth.json に保持されます。

interface OAuthCredentials {
  refresh: string;   // Refresh token (for refreshToken())
  access: string;    // Access token (returned by getApiKey())
  expires: number;   // Expiration timestamp in milliseconds
}

カスタムストリーミング API

非標準の API を使用するプロバイダーの場合は、 streamSimple を実装します。独自のプロバイダー実装を作成する前に、既存のプロバイダー実装を調べてください。

参考実装:

ストリームパターン

すべてのプロバイダーは同じパターンに従います。

import {
  type AssistantMessage,
  type AssistantMessageEventStream,
  type Context,
  type Model,
  type SimpleStreamOptions,
  calculateCost,
  createAssistantMessageEventStream,
} from "@earendil-works/pi-ai";

function streamMyProvider(
  model: Model<any>,
  context: Context,
  options?: SimpleStreamOptions
): AssistantMessageEventStream {
  const stream = createAssistantMessageEventStream();

  (async () => {
    // Initialize output message
    const output: AssistantMessage = {
      role: "assistant",
      content: [],
      api: model.api,
      provider: model.provider,
      model: model.id,
      usage: {
        input: 0,
        output: 0,
        cacheRead: 0,
        cacheWrite: 0,
        totalTokens: 0,
        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
      },
      stopReason: "pending",
      timestamp: Date.now(),
    };

    try {
      // Push start event
      stream.push({ type: "start", partial: output });

      // Make API request and process response...
      // Push content events as they arrive and set stopReason from the terminal event.
      if (output.stopReason === "pending") {
        throw new Error("Provider stream ended without a stop reason");
      }
      if (output.stopReason === "error" || output.stopReason === "aborted") {
        throw new Error(output.errorMessage || "An unknown error occurred");
      }

      // Push done event
      stream.push({
        type: "done",
        reason: output.stopReason,
        message: output
      });
      stream.end();
    } catch (error) {
      output.stopReason = options?.signal?.aborted ? "aborted" : "error";
      output.errorMessage = error instanceof Error ? error.message : String(error);
      stream.push({ type: "error", reason: output.stopReason, error: output });
      stream.end();
    }
  })();

  return stream;
}

イベントの種類

次の順序で stream.push() 経由でイベントをプッシュします。

  1. { type: "start", partial: output } - ストリームが開始されました

  2. コンテンツ イベント (繰り返し可能、ブロックごとに contentIndex を追跡):

    • { type: "text_start", contentIndex, partial } - テキストブロックが開始されました
    • { type: "text_delta", contentIndex, delta, partial } - テキストチャンク
    • { type: "text_end", contentIndex, content, partial } - テキストブロックが終了しました
    • { type: "thinking_start", contentIndex, partial } - 思考が始まりました
    • { type: "thinking_delta", contentIndex, delta, partial } - 思考の塊
    • { type: "thinking_end", contentIndex, content, partial } - 思考は終了しました
    • { type: "toolcall_start", contentIndex, partial } - ツール呼び出しが開始されました
    • { type: "toolcall_delta", contentIndex, delta, partial } - ツール呼び出し JSON チャンク
    • { type: "toolcall_end", contentIndex, toolCall, partial } - ツール呼び出しが終了しました
  3. { type: "done", reason, message } または { type: "error", reason, error } - ストリームが終了しました

各イベントの partial フィールドには、現在の AssistantMessage 状態が含まれます。データを受信したら output.content を更新し、outputpartial として含めます。

コンテンツブロック

コンテンツ ブロックが到着したら、output.content に追加します。

// Text block
output.content.push({ type: "text", text: "" });
stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });

// As text arrives
const block = output.content[contentIndex];
if (block.type === "text") {
  block.text += delta;
  stream.push({ type: "text_delta", contentIndex, delta, partial: output });
}

// When block completes
stream.push({ type: "text_end", contentIndex, content: block.text, partial: output });

ツール呼び出し

ツール呼び出しには、JSON の蓄積と解析が必要です。

// Start tool call
output.content.push({
  type: "toolCall",
  id: toolCallId,
  name: toolName,
  arguments: {}
});
stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });

// Accumulate JSON
let partialJson = "";
partialJson += jsonDelta;
try {
  block.arguments = JSON.parse(partialJson);
} catch {}
stream.push({ type: "toolcall_delta", contentIndex, delta: jsonDelta, partial: output });

// Complete
stream.push({
  type: "toolcall_end",
  contentIndex,
  toolCall: { type: "toolCall", id, name, arguments: block.arguments },
  partial: output
});

使用量とコスト

API 応答から使用量を更新し、コストを計算します。

output.usage.input = response.usage.input_tokens;
output.usage.output = response.usage.output_tokens;
output.usage.cacheRead = response.usage.cache_read_tokens ?? 0;
output.usage.cacheWrite = response.usage.cache_write_tokens ?? 0;
output.usage.totalTokens = output.usage.input + output.usage.output +
                           output.usage.cacheRead + output.usage.cacheWrite;
calculateCost(model, output.usage);

コンテキストオーバーフローエラー

リクエストがモデルのコンテキスト ウィンドウを超える場合、pi は会話を圧縮して再試行することで自動的に回復できます。この回復は、pi が障害をオーバーフローとして認識した場合にのみ開始されます。

検出は、最終的なアシスタント メッセージに対して実行されます。

  • stopReason === "error"
  • errorMessage は、pi の既知のオーバーフロー パターンの 1 つに一致します (packages/ai/src/utils/overflow.ts を参照)

プロバイダーが pi が認識できないメッセージを含むオーバーフロー エラーを返した場合は、プロバイダーを登録したのと同じ拡張機能からのエラーを正規化します。 message_end ハンドラーを使用してアシスタント メッセージを書き換え、その errorMessage が pi が認識するフレーズで始まるようにします。一般的なフォールバック context_length_exceeded が最も安全な選択です。

const MY_PROVIDER_OVERFLOW_PATTERN = /your provider's overflow phrase/i;

export default function (pi: ExtensionAPI) {
  pi.registerProvider("my-provider", { /* ... */ });

  pi.on("message_end", (event, ctx) => {
    const message = event.message;
    if (message.role !== "assistant") return;
    if (message.stopReason !== "error") return;
    if (
      message.provider !== "my-provider" &&
      ctx.model?.provider !== "my-provider"
    )
      return;

    const errorMessage = message.errorMessage ?? "";
    if (errorMessage.includes("context_length_exceeded")) return;
    if (!MY_PROVIDER_OVERFLOW_PATTERN.test(errorMessage)) return;

    return {
      message: {
        ...message,
        errorMessage: `context_length_exceeded: ${errorMessage}`,
      },
    };
  });
}

message_end は、pi が自動圧縮のためにアシスタント メッセージを追跡する前に実行されるため、書き換えられた errorMessage が pi のチェック対象になります。これを設定すると、pi は次のようになります。

  1. errorMessageからのオーバーフローを検出します。
  2. 失敗したアシスタント メッセージをライブ コンテキストから削除します。
  3. 圧縮を実行します。
  4. リクエストを一度再試行してください。

リライトを慎重に保護してください。

  • スコープをプロバイダー (message.provider および ctx.model?.provider) に設定すると、他のプロバイダーからの無関係なエラーは影響を受けません。
  • pi の一般的なオーバーフロー パターンではなく、プロバイダー固有のパターンと一致します。レート制限またはスロットリング エラー (rate limittoo many requests) を書き換えると、pi の通常のバックオフ付き再試行パスの代わりに、誤って圧縮がトリガーされます。
  • errorMessage にすでに context_length_exceeded が含まれている場合はスキップします。そのため、ハンドラーは冪等です。

登録

ストリーム関数を登録します。

pi.registerProvider("my-provider", {
  baseUrl: "https://api.example.com",
  apiKey: "$MY_API_KEY",
  api: "my-custom-api",
  models: [...],
  streamSimple: streamMyProvider
});

実装のテスト

組み込みプロバイダーで使用されるのと同じテスト スイートに対してプロバイダーをテストします。これらのテスト ファイルを packages/ai/test/ からコピーして調整します。

テスト 目的
stream.test.ts 基本的なストリーミング、テキスト出力
tokens.test.ts トークンのカウントと使用法
abort.test.ts アボート信号処理
empty.test.ts 空の/最小限の応答
context-overflow.test.ts コンテキストウィンドウの制限
image-limits.test.ts 画像入力処理
unicode-surrogate.test.ts Unicode のエッジケース
tool-call-without-result.test.ts ツール呼び出しのエッジケース
image-tool-result.test.ts ツール結果内の画像
total-tokens.test.ts 合計トークンの計算
cross-provider-handoff.test.ts プロバイダー間のコンテキストのハンドオフ

プロバイダーとモデルのペアでテストを実行し、互換性を確認します。

構成リファレンス

interface ProviderConfig {
  /** Display name for the provider in UI such as /login. */
  name?: string;

  /** API endpoint URL. Required when defining models. */
  baseUrl?: string;

  /** 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. */
  api?: Api;

  /** Custom streaming implementation for non-standard APIs. */
  streamSimple?: (
    model: Model<Api>,
    context: Context,
    options?: SimpleStreamOptions
  ) => AssistantMessageEventStream;

  /** 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. */
  authHeader?: boolean;

  /** Models to register. If provided, replaces all existing models for this provider. */
  models?: ProviderModelConfig[];

  /** OAuth provider for /login support. */
  oauth?: {
    name: string;
    login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;
    refreshToken(credentials: OAuthCredentials, signal: AbortSignal): Promise<OAuthCredentials>;
    getApiKey(credentials: OAuthCredentials): string;
  };
}

モデル定義リファレンス

interface ProviderModelConfig {
  /** Model ID (e.g., "claude-sonnet-4-20250514"). */
  id: string;

  /** Display name (e.g., "Claude 4 Sonnet"). */
  name: string;

  /** API type override for this specific model. */
  api?: Api;

  /** API endpoint URL override for this specific model. */
  baseUrl?: string;

  /** Whether the model supports extended thinking. */
  reasoning: boolean;

  /** Maps pi thinking levels to provider/model-specific values; null marks a level unsupported. */
  thinkingLevelMap?: Partial<Record<"off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max", string | null>>;

  /** Supported input types. */
  input: ("text" | "image")[];

  /** Cost per million tokens (for usage tracking). */
  cost: {
    input: number;
    output: number;
    cacheRead: number;
    cacheWrite: number;
  };

  /** Maximum context window size in tokens. */
  contextWindow: number;

  /** Maximum output tokens. */
  maxTokens: number;

  /** Custom headers for this specific model. */
  headers?: Record<string, string>;

  /** Compatibility settings for the selected API. */
  compat?: {
    // openai-completions
    supportsStore?: boolean;
    supportsDeveloperRole?: boolean;
    supportsReasoningEffort?: boolean;
    supportsUsageInStreaming?: boolean;
    supportsFinishReason?: boolean;
    supportsStrictMode?: boolean;
    supportsOpenAIGrammarTools?: boolean; // openai-completions/openai-responses; false falls back to normal function tools
    maxTokensField?: "max_completion_tokens" | "max_tokens";
    requiresToolResultName?: boolean;
    requiresAssistantAfterToolResult?: boolean;
    requiresThinkingAsText?: boolean;
    requiresReasoningContentOnAssistantMessages?: boolean;
    thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "baseten" | "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 }>;
    chatTemplateArgs?: Record<string, string | number | boolean | null | { "$var": "thinking.enabled" | "thinking.effort"; omitWhenOff?: boolean }>;
    cacheControlFormat?: "anthropic";
    sessionAffinityFormat?: "openai" | "openai-nosession" | "openrouter";
    sendSessionAffinityHeaders?: boolean;

    // anthropic-messages
    supportsEagerToolInputStreaming?: boolean;
    supportsLongCacheRetention?: boolean;
    sendSessionAffinityHeaders?: boolean;
    supportsCacheControlOnTools?: boolean;
    forceAdaptiveThinking?: boolean;
    allowEmptySignature?: boolean;
    supportsStrictTools?: boolean;
  };
}

openrouterreasoning: { effort } を送信します。 deepseek が有効になっている場合、thinking: { type: "enabled" | "disabled" }reasoning_effort を送信します。 togetherreasoning: { enabled } を送信し、supportsReasoningEffort が有効な場合は reasoning_effort も送信します。 qwen は、DashScope スタイルのトップレベル enable_thinking 用です。 chat_template_kwargs.enable_thinking を読み取り、preserve_thinking を必要とするローカルの Qwen 互換サーバーには、qwen-chat-template を使用します。構成可能な chat_template_kwargs には chat-template を使用します。たとえば、chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } } の vLLM の背後にある DeepSeek V3.x です。プロバイダーが chat_template_args 未満のトグル値を予期し、オプションでトップレベルの reasoning_effort をサポートする場合は、thinkingFormat: "baseten"chatTemplateArgs とともに使用します。 cacheControlFormat: "anthropic" は、Anthropic スタイルの cache_control マーカーをシステム プロンプト、最後のツール定義、および最後のユーザー、アシスタント、またはツール結果のテキスト コンテンツに適用します。