跳转至

模型目录

提供商插件的目录参考:共享实时模型发现、目录辅助函数、价格标准化,以及更窄的单提供商入口点。
属于构建提供商插件指南的一部分。

对于轻量级的模型引用标准化,请使用
openclaw/plugin-sdk/model-ref-parse。其 normalizeGooglePreviewModelId
和 normalizeAntigravityPreviewModelId 导出共享目录的别名规则,而不会加载提供商重放或传输辅助函数。
使用 splitTrailingAuthProfile 分离尾部的身份验证配置文件,同时保留
模型版本和本地量化后缀。

实时模型发现

如果你的提供商暴露了 OpenAI 兼容的 /models API,请将单提供商辅助函数纳入共享发现:

catalog: {
  buildProvider: () => ({
    api: "openai-completions",
    baseUrl: "https://api.acme-ai.com/v1",
    models: [...STATIC_MODELS],
  }),
  buildStaticProvider: () => ({
    api: "openai-completions",
    baseUrl: "https://api.acme-ai.com/v1",
    models: [...STATIC_MODELS],
  }),
  liveModelDiscovery: true,
},

liveModelDiscovery: true 是公开的 Plugin SDK 契约,具有以下行为:

领域 契约
凭据 发现使用目录中已解析的提供商凭据,如果身份验证提供了 discoveryApiKey,则优先使用它。密钥引用标记永远不会作为令牌发送。默认请求使用 Authorization: Bearer <token>;对于其他供应商身份验证方案,请使用 buildRequestHeaders。
端点 默认 URL 是相对于生效的提供商 baseUrl 的 models;当启用 allowExplicitBaseUrl 时,还包括操作员覆盖。对于其他相对路径,请使用 endpointPath。仅对于固定的供应商 URL 使用 endpointUrl: { url, requireBaseUrl };除非生效的基础 URL 仍等于 requireBaseUrl,否则发现会被跳过,因此自定义代理凭据不会被发送到供应商。
网络限制 获取请求使用 OpenClaw 的 SSRF 防护,跨分页共享一个 5 秒超时预算,每页 4 MiB 响应限制,以及 50 页限制。跨源分页链接会被拒绝;跨源重定向后会移除凭据。
缓存 成功的非空目录会按提供商、端点和已解析凭据缓存 60 秒。空结果或不可用的结果不会被缓存。
过滤 精确匹配的实时 ID 会保留其受信任的静态元数据。新行会被保守地投影为文本/聊天模型。已禁用、已归档、已弃用、明确非聊天、嵌入、重排序、审核、语音、仅图像和仅视频的行会被排除。仅使用 readRows 从非标准响应信封中选择行;特定提供商的模型语义仍应放在自定义目录中。
准入 可选。当你的请求构造针对特定模型版本时,设置 acceptUnknownModel: ({ id, record }) => boolean,以便发现无法发布你尚无法构建有效请求的模型。它仅针对你的静态目录尚未发布的 ID 调用;已知 ID 会绕过它并保留其已发布的元数据。返回 false 以丢弃该行。省略它的提供商保持之前的行为不变。优先将供应商宣传的能力与你自己的契约检查进行比较,而不是使用手工维护的模型列表;当行没有携带能力数据时,采取失败关闭策略。
区域 契约
失败 实时发现是建议性的。身份验证、网络、超时、分页、解析、空目录和过滤失败会返回由提供方拥有的静态种子,而不是移除该提供方。

相对目录缓存 TTL 在成功加载完成时开始。缓存命中会保留该截止时间,显式的提供方绝对截止时间保持不变。 待处理加载保留其初始过期时间,以便停滞的工作可以被替换。

捆绑提供方在其目录选项中设置 discoveryMode: "strict"。 此代码选项使成功的空结果保持为空,并通过 ProviderCatalogResult.outcomes 报告获取失败,而不是将种子模型作为成功刷新返回。HTTP 401/403 会产生目录范围的 auth-rejected 结果;其他获取失败会产生 unavailable。 静态目录或跳过的发现都不会产生实时结果。 每个结果都携带为实际请求选定的配置(如果该配置提供了其凭据)。家族提供方独立报告每个同级提供方。 显式的 ready 结果可以包含 modelOrder: string[],以在选择器中对其已发现的模型进行排序。这不会添加模型或授予访问权限;缺失的模型会被忽略,而没有 modelOrder 的结果保留清单顺序。 提供方范围的刷新会保留在所选提供方的已注册别名下报告的显式结果;无关的同级结果仍被排除。 在正缓存生命周期下,经过验证的空结果使用与非空结果相同的成功观察生命周期。过期后,普通目录读取返回保留的行,同时现有清单所有者在后台刷新提供方。ttlMs: 0 仍然禁用响应缓存,并且不为该续期路径记录过期时间。

公共元数据请求在发现选项中声明 authentication: "none"。准备好的请求因此没有凭据或配置身份; 其缓存键独立于已配置的推理凭据。 返回的提供方配置仍保留其推理凭据。

省略 discoveryMode 的外部调用保留上述建议性契约。 公共 Chutes、Hugging Face、KiloCode 和 Vercel AI Gateway 的发现函数和构建器也保留该默认值。它们的捆绑目录钩子显式传递 { discoveryMode: "strict" };Hugging Face 发现在其现有超时参数之后接受此选项对象。Chutes 公共默认值在 HTTP 401 之后保留其匿名重试;严格调用在没有选定凭据的情况下从不重试。 严格路径和建议性路径共享相同的受保护传输和缓存,但具有不同的缓存身份。建议性调用仍然只保留非空结果。 自定义实时构建器可以在其目录钩子处使用 runLiveProviderCatalog 来报告成功获取并将获取错误转换为结果。 仅返回提供方配置不会建立实时发现结果。 为了兼容性,由旧版目录钩子返回且没有结果的非空行,在相同凭据下会在提供方范围失败中存活。这不会建立成功的发现来源,也不会保留无关的配置行和补充行。空旧版目录和特定配置失败不使用该回退;成功替换会清除先前的行来源。 将元数据源回退与账户发现分开;不要匿名重试被拒绝的账户请求,也不要在严格构建器内替换种子行。

自定义目录钩子可能从 ctx.resolveProviderApiKey() 接收可选的 mode 元数据:api_key、oauth 或 token。如果存在, 它描述该查找选定的凭据。在选择供应商身份验证方案时使用它;单独的 resolveProviderAuth() 调用可能选择不同配置。省略的 mode 元数据不会改变现有回调行为。

ctx.resolveProviderAuth() 可能在 OAuth 准备耗尽候选项时设置 preparationFailed: true。不要将该标志视为配置缺失,也不要重新开始解析相同配置。钩子仍可选择另一个凭据源。其返回的提供方配置或显式结果仍然具有权威性;否则目录所有者会报告已消耗的准备失败,并附带尝试的配置身份。

对于非 Bearer 或非标准列表端点,传递选项而不是 true:

liveModelDiscovery: {
  endpointPath: "model-catalog",
  buildRequestHeaders: ({ apiKey, discoveryApiKey }) => ({
    "vendor-version": "2026-01-01",
    "x-api-key": discoveryApiKey ?? apiKey ?? "",
  }),
  readRows: (body) =>
    body && typeof body === "object" &&
    Array.isArray((body as { models?: unknown }).models)
      ? (body as { models: unknown[] }).models
      : [],
},

不要将 endpointUrl 用作无条件的备用主机。其 requireBaseUrl 检查是凭据隔离边界,适用于模型列表主机与推理主机不同的提供商。

如果提供商需要自定义模型语义,而不是保守的 OpenAI 兼容投影,则仅在插件中保留该投影。将其作为 projectRows 传入;共享运行时仍负责受保护的请求、提供商身份验证请求头、缓存准入和静态回退。

当实时 API 仅告知当前哪些由提供商拥有的静态目录行可用时,使用 buildLiveModelProviderConfig:

```typescript index.ts import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { buildLiveModelProviderConfig, type LiveModelCatalogFetchGuard, } from "openclaw/plugin-sdk/provider-catalog-live-runtime";

const STATIC_MODELS = [ { id: "acme-large", name: "Acme Large", reasoning: true, input: ["text", "image"], cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }, contextWindow: 200000, maxTokens: 32768, }, { id: "acme-small", name: "Acme Small", reasoning: false, input: ["text"], cost: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 }, contextWindow: 128000, maxTokens: 8192, }, ] as const;

async function buildAcmeLiveProvider(params: { apiKey: string; discoveryApiKey?: string; fetchGuard?: LiveModelCatalogFetchGuard; }) { return await buildLiveModelProviderConfig({ providerId: "acme-ai", endpoint: "https://api.acme-ai.com/v1/models", providerConfig: { baseUrl: "https://api.acme-ai.com/v1", api: "openai-completions", }, models: STATIC_MODELS, apiKey: params.apiKey, discoveryApiKey: params.discoveryApiKey, fetchGuard: params.fetchGuard, ttlMs: 60_000, auditContext: "acme-ai-model-discovery", projectRows: (rows, fallback) => rows.flatMap((row) => { const model = projectAcmeModel(row, fallback); return model ? [model] : []; }), }); }

export default definePluginEntry({ id: "acme-ai", name: "Acme AI", register(api) { api.registerProvider({ id: "acme-ai", label: "Acme AI", catalog: { order: "simple", run: async (ctx) => { const auth = ctx.resolveProviderAuth("acme-ai"); const apiKey = auth.apiKey ?? ctx.resolveProviderApiKey("acme-ai").apiKey; if (!apiKey) return null; return { provider: await buildAcmeLiveProvider({ apiKey, discoveryApiKey: auth.discoveryApiKey, }), }; }, }, staticCatalog: { order: "simple", run: async () => ({ provider: { baseUrl: "https://api.acme-ai.com/v1", api: "openai-completions", models: [...STATIC_MODELS], }, }), }, }); }, });

`run` 应保持身份验证门控,并在没有可用凭据时返回 `null`。保留离线 `staticRun` 或静态回退,使设置、文档、测试和选择器界面不依赖实时网络访问。使用适合模型列表新鲜度的 TTL,避免请求时文件系统轮询,并且仅当上游响应不是 OpenAI 兼容的 `{ data: [{ id, object }] }` 形状时,才传入提供商特定的 `readRows` / `readModelId`。

在模型运行时准备期间,`staticCatalog.run` 和 `prepareSyntheticAuth` 会接收一个可选的 `signal`。关闭以及插件/配置替换会中止它。当其中止时,停止等待中的获取,并在钩子完成前结束资源清理。OpenClaw 会丢弃已取消的结果,并在替换方获取相同的 agent 资源之前等待清理完成。

对于独立的权威元数据源,相同的 `provider-catalog-live-runtime` 子路径会暴露 `ProviderCatalogSnapshot`:每个条目将一个运行时模型与其生命周期状态配对。`projectUpstreamProviderCatalogSnapshot` 从可信种子和已接受的上游行重建该快照,并丢弃已撤回的仅上游模型。`projectProviderCatalogSnapshotRows` 将已宣告的 ID 与活动快照条目求交集,并按端点顺序去重;`listProviderCatalogSnapshotEntries` 为目录消费者投影相同的生命周期事实。将种子生命周期策略和特定于模型的装饰保留在所属插件中。在刷新元数据后派生静态回退资格,使首次失败或完全被过滤的发现使用当前状态。公共元数据不会建立账户权限,也不会扩大发现的凭据范围。

私有的 `createUpstreamProviderCatalog` 辅助函数将这一快照生命周期保留在一个已准备的所有者中。提供可信种子、提供商路由、元数据和模型列表端点、静态条目资格以及任何模型装饰。可选的 `upstreamSeed` 控制哪些种子生命周期事实在上游刷新后保留。该所有者暴露 `getSnapshot`、`refreshMetadata`、`buildStaticProvider` 和 `buildLiveProvider`;凭据属于每个构建调用。实时构建在派生静态资格并对已宣告 ID 求交集之前刷新元数据。元数据获取失败会保留之前的快照;模型列表失败和空结果仍按严格处理。当源中缺少该提供商时,`refreshMetadata` 返回 `undefined`,因此显式模型准备不会将保留的元数据误认为刷新成功。插件策略仍负责决定哪些模型可以直接从种子或当前快照解析。

上游推理元数据将省略的控制保留为未指定,并将空的选项或 effort 列表保留为无 effort 控制。原生 `null` effort 映射为 `none`;提供商原生 effort 名称保留其大小写。这些事实与模型内部是否执行推理保持分离。



官方插件使用私有的、纯的
`openclaw/plugin-sdk/model-catalog-pricing` 运行时子路径。它暴露
`normalizeModelPricingCatalog(rows, normalizePricing, options?)`,用于
提供方自有的定价源。它返回完整成本的映射:缺失
的价格会被省略,而格式错误的声明价格、无效或重复的
模型 ID,以及没有可用价格的源会返回 `undefined`。请提供
提供方的单位转换。选项可选择 `readModelId(model)`(默认
`model.id`)、`readPricing(model)`(默认 `model.pricing`)和
`isSupportedPricing(rawPricing)`(默认 `true`)。声明的价格会在
不支持的定价计划被省略之前进行归一化和验证;即使行未定价或不受支持,重复
ID 也会被拒绝。非 Token 域
可以从 `readPricing` 返回 `undefined`。不会导入任何认证、发现或运行时
加载器。

DeepInfra 的 `pricing-api.ts` 使用这些选择器处理其原生数组和
`model_name` 标识。发布使用选项契约的插件(包括
DeepInfra 和 Venice)时,需搭配匹配的主机,并在发布时协调其插件 API
和最低主机版本下限。该私有子路径不是
独立版本化的第三方兼容 API。

该子路径还暴露 `normalizeOpenRouterModelPricing(pricing)`,用于
原生 OpenRouter 定价对象。它将每 Token 费率和静态
提示长度覆盖转换为完整的每百万成本计划,无需
网络访问或来自其他来源的价格。覆盖严格适用于高于
`min_prompt_tokens` 的部分,计入未缓存输入、缓存读取和缓存写入。
匹配条目按源顺序应用:对于每个价格键,后面的条目优先,
包括阈值相同时;省略的键继承原生基础值或较早的匹配条目。基础中缺失的缓存费率默认为零。
无效的有效 Token 费率返回 `undefined`。具有基于时间或
未知条件的条目会被跳过;其他已知计费维度会被忽略。

## 选择目录增强钩子 {#selecting-catalog-augmentation-hooks}

`augmentModelCatalogWithProviderPlugins` 从
`openclaw/plugin-sdk/provider-catalog-runtime` 导出。其可选的顶层
`providerIds` 选择要运行哪些已注册的 `augmentModelCatalog` 钩子:

- 省略 `providerIds` 可保留无范围行为。
- 传入 `[]` 表示不运行任何增强钩子。
- 传入提供方 ID 或已注册别名以选择匹配的钩子。匹配
  会归一化 ID 和别名,包括提供方家族使用的钩子别名。

该选择器**不会**过滤所选钩子返回的行。一个家族
钩子可能返回多个提供方的行;调用方负责任何行过滤。
该辅助函数返回补充行,而不是输入 `context.entries`。

随附的 v2026.9.4 导出没有 `providerIds` 选择器,运行时可能会忽略该
选项。依赖范围化钩子选择的插件必须在 `openclaw.compat.pluginApi` 中要求包含该选择器的主机版本。省略该选项可在较旧和较新主机上保留现有行为。

该顶层选择器独立于 `catalog.run` 回调上下文。
当 `ctx.providerIds` 存在时,它包含为该目录所有者选择的归一化提供方
标识。当钩子不服务于其中任何标识时,在解析
凭据或发起网络请求之前返回 `null`;
OpenClaw 还会将返回的标识过滤到该范围内。缺失范围
表示调用方请求了完整目录。

如果上游提供方使用的控制 Token 与 OpenClaw 不同,请添加一个
小型双向文本转换,而不是替换流路径:

```typescript
api.registerTextTransforms({
  input: [
    { from: /red basket/g, to: "blue basket" },
    { from: /paper ticket/g, to: "digital ticket" },
    { from: /left shelf/g, to: "right shelf" },
  ],
  output: [
    { from: /blue basket/g, to: "red basket" },
    { from: /digital ticket/g, to: "paper ticket" },
    { from: /right shelf/g, to: "left shelf" },
  ],
});

input 在传输前重写最终系统提示和文本消息内容。output 在 OpenClaw 解析其自身控制标记或通道投递前重写助手文本增量和最终文本。

对于仅注册一个使用 API 密钥认证且带有单个目录支持运行时的文本提供方的捆绑提供方,请优先使用更窄的 defineSingleProviderPluginEntry(...) 辅助函数:

import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";

export default defineSingleProviderPluginEntry({
  id: "acme-ai",
  name: "Acme AI",
  description: "Acme AI model provider",
  provider: {
    label: "Acme AI",
    docsPath: "/providers/acme-ai",
    auth: [
      {
        methodId: "api-key",
        label: "Acme AI API key",
        hint: "API key from your Acme AI dashboard",
        optionKey: "acmeAiApiKey",
        flagName: "--acme-ai-api-key",
        envVar: "ACME_AI_API_KEY",
        promptMessage: "Enter your Acme AI API key",
        defaultModel: "acme-ai/acme-large",
      },
    ],
    catalog: {
      buildProvider: () => ({
        api: "openai-completions",
        baseUrl: "https://api.acme-ai.com/v1",
        models: [{ id: "acme-large", name: "Acme Large" }],
      }),
      buildStaticProvider: () => ({
        api: "openai-completions",
        baseUrl: "https://api.acme-ai.com/v1",
        models: [{ id: "acme-large", name: "Acme Large" }],
      }),
    },
  },
});

buildProvider 是当 OpenClaw 能够解析真实提供方认证时使用的实时目录路径。它可以执行提供方特定的发现。仅对离线行使用 buildStaticProvider,这些行在配置认证之前可以安全显示;它不得要求凭据或发起网络请求。 OpenClaw 的 models list --all 显示目前仅对捆绑提供方插件执行静态目录,且使用空配置、空环境变量,以及没有 agent/workspace 路径。

如果你的认证流程还需要在入门过程中修补 models.providers.*、别名和 agent 默认模型,请使用来自 openclaw/plugin-sdk/provider-onboard 的预设辅助函数。对于已注册的仅连接设置, 请使用带有惰性 catalogModels 供应方的 createProviderConnectionPresetAppliers(...)。普通设置会写入连接事实、别名和缺失的主模型,而不会将内置目录复制到已保存的配置中。 显式 models.mode: "replace" 会评估供应方,并将生成的行合并到已编写行之后。每个设置结果拥有其生成的模型数据。

在替换模式必须保留现有必需默认规则时,使用 createDefaultModelsConnectionPresetAppliers(...):仅当已配置的提供商缺少选定的 defaultModelId 时,才添加所提供的 defaultModels。applyProviderConnectionConfig(...) 为每次调用时解析其端点或主模型的认证流程提供目录变体。这些辅助函数会保留已编写的行、现有别名和回退项。认证流程仍负责任何显式默认选择。

现有的公开 createDefaultModelPresetAppliers(...)、createDefaultModelsPresetAppliers(...) 和 createModelCatalogPresetAppliers(...) 在普通模式下保留其目录播种行为。后两者还接受惰性模型供应器。已发布配置辅助函数的提供商可以在其现有辅助函数和新注册的设置辅助函数之间共享一个预设描述符;迁移注册时,不要悄悄更改已发布辅助函数的契约。

独立发布的插件必须在 openclaw.compat.pluginApi 中要求包含这些辅助函数的宿主版本。核心版本同步工具会随配套版本更新该范围;这些导入在缺少这些辅助函数的旧宿主上无法工作。

当提供商的原生端点在常规 openai-completions 传输上支持流式 usage 块时,请优先使用 openclaw/plugin-sdk/provider-catalog-shared 中的共享目录辅助函数,而不是硬编码 provider-id 检查。supportsNativeStreamingUsageCompat(...) 和 applyProviderNativeStreamingUsageCompat(...) 会从端点能力映射中检测支持,因此即使插件正在使用自定义 provider id,原生 Moonshot/DashScope 风格端点仍会加入。

上述实时发现示例涵盖 /models 风格的提供商 API。将该发现保留在 catalog.run 内,并以可用认证为门控,同时保持 staticRun 无网络,用于离线目录生成。

共享凭据的官方提供商插件可以使用私有运行时 openclaw/plugin-sdk/provider-catalog-shared 子路径中的 resolveFirstProviderCatalogAuth(ctx.resolveProviderApiKey, providerIds)。保持提供商优先级遵循调用方的有序 ID。该辅助函数会在遇到第一个包含 apiKey 或 discoveryApiKey 的结果时停止,并返回该完整结果,保留其配置文件和认证模式。未解析的 SecretRef 标记优先于另一个提供商的实时密钥;字段绝不会跨账户混合。当没有任何提供商具有认证时,它返回 undefined,并传播查找失败。使用此宿主导出的官方插件版本必须在 compat.pluginApi 中要求提供该导出的宿主版本。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw