Cấu hình, tùy chỉnh, thiết lập nền tảng và tham chiếu API cho Pi.

Tùy chỉnh Providers

Extensions có thể đăng ký nhà cung cấp mô hình tùy chỉnh thông qua pi.registerProvider(). Điều này cho phép:

  • Proxies - Định tuyến yêu cầu thông qua proxy công ty hoặc cổng API
  • Điểm cuối tùy chỉnh - Sử dụng triển khai mô hình riêng tư hoặc tự lưu trữ
  • OAuth/SSO - Thêm luồng xác thực cho nhà cung cấp doanh nghiệp
  • APIs tùy chỉnh - Triển khai phát trực tuyến cho LLM APIs không chuẩn

Ví dụ Extensions

Xem các ví dụ về nhà cung cấp hoàn chỉnh sau:

Mục lục

Tham khảo nhanh

Extensions có ​​thể đăng ký một pi-ai Provider hoàn chỉnh hoặc sử dụng biểu mẫu cấu hình nhà cung cấp cũ. Ưu tiên một nhà cung cấp hoàn chỉnh khi yêu cầu hành vi xác thực, lọc, làm mới hoặc phát trực tuyến tùy chỉnh. Pi soạn models.json ghi đè lên trên các nhà cung cấp bản địa đã đăng ký.

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

Nhà máy mở rộng cũng có thể là async. Để khám phá mô hình động, hãy tìm nạp và đăng ký các mô hình trong nhà máy thay vì session_start. pi đợi nhà máy trước khi tiếp tục khởi động, vì vậy nhà cung cấp có sẵn trong quá trình khởi động tương tác và pi --list-models.

Ghi đè nhà cung cấp hiện tại

Trường hợp sử dụng đơn giản nhất: chuyển hướng nhà cung cấp hiện có thông qua proxy.

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

Khi chỉ cung cấp baseUrl và/hoặc headers (không có models), tất cả các mô hình hiện có cho nhà cung cấp đó sẽ được giữ nguyên với điểm cuối mới.

Đăng ký nhà cung cấp mới

Để thêm nhà cung cấp hoàn toàn mới, hãy chỉ định models cùng với cấu hình được yêu cầu.

Nếu danh sách mô hình đến từ điểm cuối từ xa, hãy sử dụng nhà máy tiện ích mở rộng không đồng bộ:

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

Việc này sẽ đăng ký các mô hình được tìm nạp trước khi quá trình khởi động kết thúc.

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

Khi models được cung cấp, nó thay thế tất cả các mô hình hiện có cho nhà cung cấp đó.

apiKey và các giá trị tiêu đề tùy chỉnh sử dụng cú pháp giá trị cấu hình giống như models.json: !command khi bắt đầu thực thi lệnh cho toàn bộ giá trị, $ENV_VAR${ENV_VAR} nội suy các biến môi trường, $ phát ra một chữ ``apiKeyvà các giá trị tiêu đề tùy chỉnh sử dụng cú pháp giá trị cấu hình giống nhưmodels.json: !commandkhi bắt đầu thực thi lệnh cho toàn bộ giá trị,$ENV_VAR${ENV_VAR}nội suy các biến môi trường,$phát ra một chữ và$!phát ra một chữ!`.

Hủy đăng ký nhà cung cấp

Sử dụng pi.unregisterProvider(name) để xóa nhà cung cấp đã được đăng ký trước đó qua pi.registerProvider(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");

Việc hủy đăng ký sẽ xóa các mô hình động, dự phòng API key, đăng ký nhà cung cấp OAuth và đăng ký trình xử lý luồng tùy chỉnh của nhà cung cấp đó. Mọi mô hình tích hợp hoặc hành vi của nhà cung cấp đã bị ghi đè đều được khôi phục.

Các cuộc gọi được thực hiện sau giai đoạn tải tiện ích mở rộng ban đầu sẽ được áp dụng ngay lập tức, do đó không cần /reload.

API Các loại

Trường api xác định cách triển khai phát trực tuyến nào được sử dụng:

API Sử dụng cho
anthropic-messages Claude nhân loại API và những người tương thích
openai-completions Số lần hoàn thành trò chuyện OpenAI API và tương thích
openai-responses Phản hồi OpenAI API
azure-openai-responses Phản hồi của Azure OpenAI API
openai-codex-responses Phản hồi Codex của OpenAI API
mistral-conversations Phát trực tuyến hoàn thành trò chuyện Mistral bản địa
google-generative-ai AI sáng tạo của Google API
google-vertex Google Vertex AI API
bedrock-converse-stream Converse Amazon Bedrock API

Hầu hết các nhà cung cấp tương thích với OpenAI đều hoạt động với openai-completions. Sử dụng cấp độ mô hình thinkingLevelMap cho các cấp độ tư duy dành riêng cho mô hình và compat cho các yêu cầu riêng của nhà cung cấp. Các cấp độ xhighmax được chọn tham gia, yêu cầu các mục nhập bản đồ không có giá trị rỗng và có thể được phân tách bằng các lỗ không được hỗ trợ:

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
  }
}]

Sử dụng openrouter cho các điều khiển reasoning: { effort } kiểu OpenRouter. Sử dụng together cho các điều khiển kiểu Together reasoning: { enabled }; với supportsReasoningEffort, nó cũng gửi reasoning_effort. Sử dụng qwen-chat-template cho các máy chủ tương thích với Qwen cục bộ đọc chat_template_kwargs.enable_thinking và cần preserve_thinking. Sử dụng cacheControlFormat: "anthropic" cho các nhà cung cấp tương thích với OpenAI hiển thị bộ đệm ẩn lời nhắc kiểu Anthropic thông qua cache_control trên lời nhắc hệ thống, định nghĩa công cụ cuối cùng và nội dung văn bản kết quả công cụ, người dùng cuối cùng hoặc trợ lý.

Đối với các nhà cung cấp tương thích với Con người đang sử dụng api: "anthropic-messages", hãy đặt compat.forceAdaptiveThinking: true trên các mô hình hoặc nhà cung cấp có mô hình ngược dòng yêu cầu tư duy thích ứng (thinking.type: "adaptive" cộng với output_config.effort). Các mô hình Claude thích ứng tích hợp sẽ tự động thiết lập điều này. Chỉ đặt compat.allowEmptySignature: true cho các nhà cung cấp phát ra các chữ ký suy nghĩ trống rỗng và mong đợi signature: "" khi phát lại.

Ghi chú di chuyển: Mistral đã chuyển từ openai-completions sang mistral-conversations. Sử dụng mistral-conversations cho các mẫu Mistral bản địa. Nếu bạn cố tình định tuyến các điểm cuối tùy chỉnh/tương thích với Mistral thông qua openai-completions, hãy đặt cờ compat một cách rõ ràng nếu cần.

Tiêu đề xác thực

Nếu nhà cung cấp của bạn mong đợi Authorization: Bearer <key> nhưng không sử dụng API tiêu chuẩn, hãy đặt 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: [...]
});

Chìa khóa được giải quyết cho mỗi yêu cầu. Tiêu đề yêu cầu rõ ràng Authorization được ưu tiên hơn giá trị được tạo.

OAuth Hỗ trợ

Thêm xác thực OAuth/SSO tích hợp với /login:

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

Sau khi đăng ký, người dùng có thể xác thực qua /login corporate-ai.

OAuthĐăng nhậpGọi lại

Đối tượng callbacks cung cấp các tương tác trung lập với giao diện người dùng cho luồng do nhà cung cấp sở hữu:

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

OAuthThông tin xác thực

Thông tin xác thực được duy trì trong ~/.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
}

Phát trực tuyến tùy chỉnh API

Đối với các nhà cung cấp có API không chuẩn, hãy triển khai streamSimple. Nghiên cứu cách triển khai của nhà cung cấp hiện có trước khi viết bài của riêng bạn:

Triển khai tham khảo:

Mẫu luồng

Tất cả các nhà cung cấp đều theo cùng một mẫu:

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

Các loại sự kiện

Đẩy các sự kiện qua stream.push() theo thứ tự sau:

  1. { type: "start", partial: output } - Đã bắt đầu phát trực tiếp

  2. Sự kiện nội dung (có thể lặp lại, theo dõi contentIndex cho mỗi khối):

    • { type: "text_start", contentIndex, partial } - Khối văn bản đã bắt đầu
    • { type: "text_delta", contentIndex, delta, partial } - Đoạn văn bản
    • { type: "text_end", contentIndex, content, partial } - Khối văn bản đã kết thúc
    • { type: "thinking_start", contentIndex, partial } - Bắt đầu suy nghĩ
    • { type: "thinking_delta", contentIndex, delta, partial } - Đoạn suy nghĩ
    • { type: "thinking_end", contentIndex, content, partial } - Suy nghĩ kết thúc
    • { type: "toolcall_start", contentIndex, partial } - Cuộc gọi công cụ đã bắt đầu
    • { type: "toolcall_delta", contentIndex, delta, partial } - Lệnh gọi công cụ JSON chunk
    • { type: "toolcall_end", contentIndex, toolCall, partial } - Cuộc gọi công cụ đã kết thúc
  3. { type: "done", reason, message } hoặc { type: "error", reason, error } - Đã kết thúc luồng

Trường partial trong mỗi sự kiện chứa trạng thái AssistantMessage hiện tại. Cập nhật output.content khi bạn nhận được dữ liệu, sau đó đưa output làm partial.

Khối nội dung

Thêm khối nội dung vào output.content khi chúng xuất hiện:

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

Cuộc gọi công cụ

Lệnh gọi công cụ yêu cầu tích lũy JSON và phân tích cú pháp:

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

Cách sử dụng và chi phí

Cập nhật mức sử dụng từ phản hồi API và tính chi phí:

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

Lỗi tràn ngữ cảnh

Khi một yêu cầu vượt quá cửa sổ ngữ cảnh của mô hình, pi có thể tự động phục hồi bằng cách thu gọn cuộc hội thoại và thử lại. Sự phục hồi này chỉ bắt đầu nếu pi nhận ra lỗi là tràn.

Quá trình phát hiện sẽ chạy trên thông báo trợ lý cuối cùng:

Nếu nhà cung cấp của bạn trả về lỗi tràn kèm theo thông báo pi không nhận ra, hãy bình thường hóa lỗi từ cùng một tiện ích mở rộng đã đăng ký nhà cung cấp. Sử dụng trình xử lý message_end để viết lại tin nhắn trợ lý sao cho errorMessage của nó bắt đầu bằng cụm từ pi nhận dạng. Dự phòng chung context_length_exceeded là lựa chọn an toàn nhất.

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 chạy trước khi pi theo dõi thông báo trợ lý để tự động nén, do đó, errorMessage được viết lại là những gì pi kiểm tra. Với điều này, pi sẽ:

  1. Phát hiện tràn từ errorMessage.
  2. Loại bỏ thông báo trợ lý không thành công khỏi ngữ cảnh trực tiếp.
  3. Chạy nén.
  4. Hãy thử lại yêu cầu một lần.

Bảo vệ việc viết lại cẩn thận:

  • Phạm vi áp dụng cho nhà cung cấp của bạn (message.providerctx.model?.provider) để không xử lý các lỗi không liên quan từ các nhà cung cấp khác.
  • Khớp với mẫu dành riêng cho nhà cung cấp, không phải mẫu tràn chung của pi. Việc viết lại các lỗi giới hạn tốc độ hoặc điều chỉnh (rate limit, too many requests) sẽ kích hoạt quá trình nén sai thay vì đường dẫn thử lại với bước lùi thông thường của pi.
  • Bỏ qua khi errorMessage đã bao gồm context_length_exceeded nên trình xử lý không có hiệu lực.

Sự đăng ký

Đăng ký chức năng truyền phát của bạn:

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

Kiểm tra việc triển khai của bạn

Kiểm tra nhà cung cấp của bạn dựa trên các bộ thử nghiệm tương tự được sử dụng bởi các nhà cung cấp tích hợp. Sao chép và điều chỉnh các tệp thử nghiệm này từ packages/ai/test/:

Bài kiểm tra Mục đích
stream.test.ts Truyền phát cơ bản, xuất văn bản
tokens.test.ts Đếm và sử dụng mã thông báo
abort.test.ts Hủy bỏXử lý tín hiệu
empty.test.ts Phản hồi trống/tối thiểu
context-overflow.test.ts Giới hạn cửa sổ ngữ cảnh
image-limits.test.ts Xử lý đầu vào hình ảnh
unicode-surrogate.test.ts Trường hợp cạnh Unicode
tool-call-without-result.test.ts Các trường hợp cạnh gọi công cụ
image-tool-result.test.ts Hình ảnh trong kết quả công cụ
total-tokens.test.ts Tính toán tổng số token
cross-provider-handoff.test.ts Chuyển giao bối cảnh giữa các nhà cung cấp

Chạy thử nghiệm với các cặp nhà cung cấp/mô hình của bạn để xác minh tính tương thích.

Tham khảo cấu hình

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

Tham chiếu định nghĩa mô hình

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

openrouter gửi reasoning: { effort }. deepseek gửi thinking: { type: "enabled" | "disabled" }reasoning_effort khi được bật. together gửi reasoning: { enabled } và cả reasoning_effort khi supportsReasoningEffort được bật. qwen dành cho cấp cao nhất theo phong cách DashScope enable_thinking. Sử dụng qwen-chat-template cho các máy chủ tương thích với Qwen cục bộ đọc chat_template_kwargs.enable_thinking và cần preserve_thinking. Sử dụng chat-template cho chat_template_kwargs có thể định cấu hình, ví dụ: DeepSeek V3.x đằng sau vLLM với chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }. Sử dụng thinkingFormat: "baseten" với chatTemplateArgs khi nhà cung cấp mong muốn chuyển đổi các giá trị dưới chat_template_args và tùy chọn hỗ trợ cấp cao nhất reasoning_effort. cacheControlFormat: "anthropic" áp dụng các điểm đánh dấu cache_control kiểu Anthropic cho lời nhắc hệ thống, định nghĩa công cụ cuối cùng và nội dung văn bản kết quả công cụ, trợ lý hoặc người dùng cuối cùng.