openclaw源码解读(13)LLM 交互与流式处理层
文件:/extensions/openai/openai-provider.ts
核心职责是:
这是 OpenAI 模型的 Provider Plugin,OpenClaw 用它来管理所有 OpenAI 模型(API Key 直连 + ChatGPT/Codex OAuth 两种方式)。
它不做 LLM 调用,而是告诉上层系统:"用户想用 X 模型 → 应该走哪个 API 端点、哪种传输协议、什么认证方式"。run-loop.ts 是执行层("怎么跑"),openai-provider.ts 是配置层("用什么跑")。run-loop 调用 provider 获取模型配置,然后发起 LLM 请求。
文件整体架构(6 大块)
1: 常量定义 (模型 ID、上下文窗口、成本、端点 URL)
1.1 常量区(L1-L161):模型参数表
├── PROVIDER_ID = "openai"
├── API Endpoints
│ ├── https://api.openai.com/v1/models ← API Key 模型发现
│ └── https://api.openai.com/.../models?... ← Codex OAuth 模型发现
├── Context Tokens(每个模型的上下文上限)
│ ├── GPT-5.6: 1,050,000 (直连) / 372,000 (Codex)
│ ├── GPT-5.5: 1,000,000 (窗口) / 272,000 (实际)
│ ├── GPT-5.5 Pro: 1,000,000
│ ├── GPT-5.4: 1,050,000
│ └── GPT-5.4 Mini/Nano: 400,000
└── 定价表($/百万token)
每个模型 { input, output, cacheRead, cacheWrite }
关键发现: 同一个模型(如 GPT-5.6)走不同通道时上下文窗口不同:
- 直连 OpenAI API → 1,050,000 tokens
- 走 Codex 通道 → 372,000 tokens
这解释了为什么需要区分路由。
1.2 成本定义(美元/百万 token)

注意: GPT-5.6 有三个变体(Sol/Terra/Luna),价格梯度明显。这是 OpenClaw 做 cost-aware 模型选择 的基础。
1.3 Manifest 静态目录

从 openclaw.plugin.json 文件读取离线静态模型目录。这是"兜底"配置,当网络不可用时使用。
2: 模型发现(L189-294,L381-438):从 API 动态拉模型列表
整体结构如下:
buildOpenAILiveProviderConfig() ← API Key 方式,调 /v1/models
buildOpenAICodexLiveProviderConfig() ← Codex OAuth 方式,调 Codex 专用端点
│
├── readCodexModelRows() 解析 API 返回的 { models: [...] }
├── readCodexModelString/Boolean/PositiveInteger()
│ 从每个 model row 中提取字段(slug, context_window, 等)
└── buildOpenAICodexModelFromLiveRow()
将一行 API 数据 → ModelDefinitionConfig
关键逻辑:如果 API 没说某个字段,fallback 到 manifest 模板
2.1 buildOpenAILiveProviderConfig L189-218
async function buildOpenAILiveProviderConfig(
params: BuildOpenAILiveProviderConfigParams,
): Promise<ModelProviderConfig> {
const baseUrl = // 1. 解析 baseUrl(用户配置 > 环境变量 > 默认值)
normalizeOptionalString(params.baseUrl) ?? resolveOpenAIDefaultBaseUrl(params.env);
const models = buildOpenAIManifestModelsForBaseUrl(baseUrl); // 2. 从 manifest 构建基础模型列表,并根据 baseUrl 调整
if (!shouldFetchOpenAILiveModels(baseUrl)) { // 3. 判断:是否需要从 OpenAI API 实时拉取模型列表?
return {
baseUrl,
api: "openai-responses",
apiKey: params.apiKey,
models,
};
}
return await buildLiveModelProviderConfig({ // 4. 是官方 API,调用 /v1/models 端点实时获取模型列表
providerId: PROVIDER_ID,
endpoint: OPENAI_MODELS_ENDPOINT,
providerConfig: {
baseUrl,
api: "openai-responses",
},
models,
apiKey: params.apiKey,
discoveryApiKey: params.discoveryApiKey,
fetchGuard: params.fetchGuard,
signal: params.signal,
ttlMs: OPENAI_MODELS_CACHE_TTL_MS,
auditContext: "openai-model-discovery",
});
}
核心逻辑:
- 如果 baseUrl 指向
api.openai.com→ 实时拉取模型列表(保证新模型自动可用) - 如果 baseUrl 是自定义的(如 Azure、代理)→ 只用 manifest 中的静态列表
2.2 Codex 模型目录构建 (readCodexModel* / buildOpenAICodexModel* 系列函数)
Codex 是什么?在这个文件中,Codex 是 OpenAI 的一种认证/传输模式,具体来说:
| 维度 | 标准 OpenAI API | Codex 通道 |
|---|---|---|
| 认证方式 | API Key | OAuth(ChatGPT 账号登录) |
| API 端点 | api.openai.com/v1/... |
OPENAI_CODEX_RESPONSES_BASE_URL |
| 传输协议标识 | "openai-responses" |
"openai-chatgpt-responses" |
| 模型发现端点 | /v1/models |
/models?client_version=0.144.5 |
| 上下文窗口 | 完整(如 1,050,000) | 缩减(如 372,000) |
| 适用场景 | 开发者直接调用 | Codex CLI / Agent 运行时 |
一句话总结:Codex 不是一个模型,而是 OpenAI 为 Codex 产品(CLI Agent)提供的专用 OAuth 认证通道,走这个通道时模型参数(上下文窗口等)会有所不同。
2.3 readCodexModel* 系列函数 L220-283
这些函数是从 Codex 模型发现 API 的响应中安全提取字段的工具函数:
// 从响应行中读取字符串字段(如 model 的 slug、display_name)
function readCodexModelString(row: unknown, key: string): string | undefined
// 读取正整数字段(如 context_window、max_output_tokens)
function readCodexModelPositiveInteger(row: unknown, keys: readonly string[]): number | undefined
// 读取字符串数组(如 input_modalities: ["text", "image"])
function readCodexModelStringArray(row: unknown, keys: readonly string[]): readonly string[]
// 读取推理级别(如 ["low", "medium", "high", "xhigh", "max"])
function readCodexReasoningLevels(row: unknown): readonly string[] | undefined
// 读取布尔字段(如 show_in_picker)
function readCodexModelBoolean(row: unknown, key: string): boolean | undefined
为什么需要这些函数? 因为 Codex 模型发现 API 返回的 JSON 结构不固定(可能是 snake_case 也可能是 camelCase),这些函数做了防御性解析,兼容两种命名风格。
2.4 buildOpenAICodexModelFromLiveRow(核心函数)L381-438
这是将 Codex API 返回的一行数据转换为 OpenClaw 内部 ModelDefinitionConfig 的函数:
function buildOpenAICodexModelFromLiveRow(row: unknown): ModelDefinitionConfig | undefined {
// 1. 过滤:只保留 visibility="list" 且 show_in_picker !== false 的模型
if (!shouldIncludeCodexModelRow(row)) return undefined;
// 2. 提取模型 ID(优先 slug,其次 id)
const modelId = readCodexModelString(row, "slug") ?? readCodexModelString(row, "id");
// 3. 查找 manifest 中的回退配置(如果 API 没返回某些字段,用 manifest 兜底)
const fallback = resolveCodexModelFallback(modelId);
// 4. 提取推理级别 → 决定 thinkingLevelMap
const reasoningLevels = readCodexReasoningLevels(row);
// 5. 提取上下文窗口、最大输出 token(API 值 > fallback 值 > 默认值)
const contextWindow = readCodexModelPositiveInteger(row, ["max_context_window", "maxContextWindow"])
?? fallback?.contextWindow ?? contextTokens ?? DEFAULT_CONTEXT_TOKENS;
// 6. 组装最终的 ModelDefinitionConfig
return {
id: modelId,
api: "openai-chatgpt-responses", // ← 关键:标记为 Codex 传输协议
baseUrl: OPENAI_CODEX_RESPONSES_BASE_URL, // ← 关键:指向 Codex 专用端点
reasoning: (reasoningLevels?.length ?? 0) > 0,
contextWindow,
maxTokens,
cost: fallback?.cost ?? OPENAI_UNKNOWN_MODEL_COST,
...
};
}
3.4 buildOpenAICodexLiveProviderConfig L458-503
async function buildOpenAICodexLiveProviderConfig(params): Promise<ModelProviderConfig> {
try {
// 1. 从 Codex 模型发现端点拉取实时模型列表(带 60 秒缓存)
const rows = await getCachedLiveProviderModelRows({
endpoint: OPENAI_CODEX_MODELS_ENDPOINT,
// 请求头带 OAuth token 和 Account ID
buildRequestHeaders: ({ discoveryApiKey }) => ({
Authorization: `Bearer ${discoveryApiKey}`,
"ChatGPT-Account-ID": params.accountId,
}),
});
// 2. 将每一行转换为 ModelDefinitionConfig
const models = rows.map(buildOpenAICodexModelFromLiveRow).filter(Boolean);
// 3. 如果成功获取到模型,返回 Codex Provider 配置
if (models.length > 0) {
return {
baseUrl: OPENAI_CODEX_RESPONSES_BASE_URL,
api: "openai-chatgpt-responses",
auth: "oauth", // ← 注意:认证方式是 OAuth,不是 API Key
models,
};
}
} catch {
// 4. 如果 Codex 发现失败(网络错误、OAuth 过期等),静默降级
}
// 5. 降级:返回静态 Codex 配置(从 manifest 读取)
return buildOpenAICodexStaticProviderConfig();
}
关键设计:Codex 发现是"advisory"(建议性的),失败不会阻断系统,会静默降级到静态配置。
3.5 normalizeOpenAICodexCatalogModel(GPT-5.6 特殊处理)L333-371
function normalizeOpenAICodexCatalogModel(model): ModelDefinitionConfig | undefined {
const modelId = normalizeLowercaseStringOrEmpty(model.id);
// GPT-5.6 基础版在 Codex 通道中不可用(返回 undefined = 过滤掉)
if (modelId === OPENAI_GPT_56_MODEL_ID) return undefined;
// GPT-5.6 Sol/Terra/Luna 在 Codex 通道中:
if (modelId === OPENAI_GPT_56_SOL_MODEL_ID || ...) {
return {
...model,
// 上下文窗口从 1,050,000 缩减到 372,000
contextWindow: OPENAI_CODEX_GPT_56_CONTEXT_TOKENS,
contextTokens: OPENAI_CODEX_GPT_56_CONTEXT_TOKENS,
// thinking 模式的 "off" 映射为 null(GPT-5.6 不支持关闭 thinking)
thinkingLevelMap: { ...model.thinkingLevelMap, off: null },
// Sol 和 Terra 额外支持 "ultra" 推理级别
...(supportsNativeUltra ? { compat: { supportedReasoningEfforts: [..., "ultra"] } } : {}),
};
}
return model; // 其他模型原样返回
}
3: 传输协议路由决策 (L522-844):决定用哪个 API
这是核心部分——OpenAI 现在有两种 API 协议:
3.1 三种传输协议
| 协议标识 | 含义 | 适用场景 |
|---|---|---|
"openai-completions" |
传统 Chat Completions API | 用户显式配置使用旧 API |
"openai-responses" |
OpenAI Responses API(新) | 标准 API Key 认证 |
"openai-chatgpt-responses" |
Codex/ChatGPT 专用 Responses API | OAuth 认证 / Codex 运行时 |
3.2 概览
shouldUseOpenAIResponsesTransport()
→ 如果用户配了 completions 但没有显式指定,自动"升级"到 responses
shouldUseCodexResponsesHooks()
→ 判断要不要走 Codex 通道(baseUrl 是 chatgpt.com / OAuth 认证)
resolveOpenAIGptForwardCompatModel() ★ 重点函数
→ 当用户请求 gpt-5.6 / gpt-5.5 / chat-latest 等"别名"时
→ 从模板模型 clone,覆盖 context/tokens/cost 等参数
→ 这就是为什么 openai/gpt-5.6 可以直接用
3.3 shouldUseOpenAIResponsesTransport L522-542
function shouldUseOpenAIResponsesTransport(params): boolean {
// 只有标记为 "openai-completions" 的才需要判断是否升级
if (params.api !== "openai-completions") return false;
// 如果用户显式配置了 completions 路由,尊重用户选择,不升级
if (resolveAuthoredOpenAICompletionsRoute(params)) return false;
// OpenAI 官方 provider + 官方端点 → 自动升级到 Responses API
if (isOwnerProvider) return !params.baseUrl || isPlatformEndpoint;
// 非官方 provider 但指向 OpenAI 平台端点 → 也升级
return isPlatformEndpoint;
}
设计意图: OpenAI 正在从 Completions API 迁移到 Responses API,OpenClaw 自动帮用户升级,但如果用户显式配置了 completions,则尊重用户。
3.4 shouldResolveDynamicModelThroughCodex L670-697
function shouldResolveDynamicModelThroughCodex(ctx): boolean {
// 如果 api 是 "openai-chatgpt-responses" 或 baseUrl 是 Codex 端点 → 走 Codex
if (shouldUseCodexResponsesHooks(...)) return true;
// 如果明确配置了 openai-responses 或 openai-completions → 不走 Codex
if (ctx.providerConfig?.api === "openai-responses" || ...) return false;
// 如果模型是"仅平台可用"(如 gpt-4o)→ 不走 Codex
if (isOpenAIPlatformOnlyRouteModelId(ctx.modelId)) return false;
// 如果模型是"仅订阅可用"(如 gpt-5.3-codex-spark)→ 走 Codex
if (isOpenAISubscriptionOnlyRouteModelId(ctx.modelId)) return true;
// 最终判断:如果 agentRuntimeId 是 "codex" → 走 Codex
return ctx.agentRuntimeId === "codex";
}
3.5 模型前向兼容解析 (resolveOpenAIGptForwardCompatModel) L707-844
这个函数解决一个问题:用户请求了一个 manifest 中不存在的模型 ID 怎么办?
function resolveOpenAIGptForwardCompatModel(ctx) {
// 根据模型 ID 匹配已知模板
if (lower === OPENAI_GPT_55_MODEL_ID) {
templateIds = [OPENAI_GPT_55_MODEL_ID, OPENAI_GPT_54_MODEL_ID];
patch = { api: "openai-responses", reasoning: true, cost: OPENAI_GPT_55_COST, ... };
} else if (...) { ... }
// 尝试从模板克隆(复用已有模型的配置,覆盖差异字段)
return cloneFirstTemplateModel({ providerId, modelId, templateIds, ctx, patch })
// 如果模板也找不到,用 patch 中的默认值构建
?? normalizeModelCompat({ id, name, ...patch, ... });
}
设计意图: 当 OpenAI 发布新模型时,即使 manifest 还没更新,只要模型 ID 匹配已知模式,就能自动推断出合理的配置。
4: ProviderPlugin 组装导出 (buildOpenAIProvider) L846-1085
这是整个文件的入口函数,返回一个完整的ProviderPlugin 对象,向 OpenClaw 注册:
export function buildOpenAIProvider(): ProviderPlugin {
const codexHooks = buildOpenAICodexProviderHooks(); // Codex 通道的钩子
const codexResponsesHooks = buildOpenAIResponsesProviderHooks(); // Codex Responses 钩子
const responsesHooks = buildOpenAIResponsesProviderHooks({ transport: "sse" }); // 标准 SSE 钩子
return {
id: "openai",
label: "OpenAI",
hookAliases: ["azure-openai", "azure-openai-responses"], // 兼容 Azure
// ===== 认证方式 =====
auth: [
...buildOpenAIChatGPTAuthMethods(), // 登录 ChatGPT 账号
createProviderApiKeyAuthMethod({...}), // 直接用 OPENAI_API_KEY
],
// ===== 动态拉模型列表(在线) =====
catalog: {
run: async (ctx) => {
// 优先级:OAuth/Codex > API Key > 环境变量
// 1. 尝试 OAuth 认证 → 走 Codex 模型发现
// 2. 尝试 API Key → 走标准模型发现
// 3. 都没有 → 返回 null
},
},
// ===== 静态 Manifest 列表(离线兜底) =====
staticCatalog: { run: async () => ({ providers: { openai: OPENAI_MANIFEST_PROVIDER } }) },
// ===== 根据 auth 模式走 Codex 还是 API Key 路径 =====
resolveDynamicModel: (ctx) =>
shouldResolveDynamicModelThroughCodex(ctx)
? codexHooks.resolveDynamicModel?.(ctx) // Codex 通道
: resolveOpenAIGptForwardCompatModel(ctx), // 标准通道
// ===== 统一输出格式 =====
normalizeResolvedModel: (ctx) => { /* 传输协议升级/降级 */ },
// ===== completions → responses 自动升级 =====
normalizeTransport: (ctx) => { /* completions → responses 升级 */ },
// ===== 给请求附加 thinking/reasoning 参数 =====
prepareExtraParams: (ctx) => {
// 根据是 Codex 还是标准通道,选择不同的参数准备逻辑
return (useCodexTransport ? codexResponsesHooks : responsesHooks).prepareExtraParams?.(ctx);
},
// ===== =====
// ===== 用量查询(ChatGPT 订阅) =====
fetchUsageSnapshot: codexHooks.fetchUsageSnapshot,
// ===== OAuth token 刷新 =====
refreshOAuth: codexHooks.refreshOAuth,
// ===== 错误分类(billing/server_error) =====
classifyFailoverReason: ({ code }) => classifyOpenAiFailoverCode(code),
// ===== 错误处理 =====
matchesContextOverflowError: ({ errorMessage }) => /content_filter.*too long/i.test(errorMessage),
classifyFailoverReason: ({ code }) => classifyOpenAiFailoverCode(code),
// ===== 思考模式 =====
resolveReasoningOutputMode: () => "native",
// =====Thinking 等级映射 (off/xhigh/max)=======
resolveThinkingProfile: (...) => resolveUnifiedOpenAIThinkingProfile(...),
// ===== 注入"合成"模型(GPT-5.5 Pro 等没有 API 的动态模型) =====
augmentModelCatalog: (ctx) => { /* 为缺失的模型生成合成目录条目 */ },
};
}
这个文件的核心设计思想
| 设计点 | 说明 | 答辩 |
|---|---|---|
| 双通道架构 | 同一个 OpenAI Provider 支持 API Key(标准)和 OAuth(Codex)两种认证通道 | "Provider 层将认证方式和传输协议解耦,同一模型可以通过不同通道访问,系统自动选择最优路径" |
| 三级模型发现 | 实时 API > 静态 Manifest > 前向兼容推断 | "模型目录有三级降级策略,确保在任何网络条件下都能工作" |
| 传输协议自动升级 | Completions → Responses 自动迁移,但尊重用户显式配置 | "框架做了协议演进的自动适配,同时保证用户配置的优先级最高" |
| Codex 是通道不是模型 | Codex 是 OAuth 认证 + 专用端点 + 缩减上下文的组合 | "Codex 不是一个独立模型,而是 OpenAI 为 Agent 运行时提供的专用接入通道" |
| 成本感知 | 每个模型都有精确的 cost 定义(input/output/cacheRead/cacheWrite) | "框架内置了成本元数据,上层 Agent 编排可以基于 cost 做模型选择" |
更多推荐
所有评论(0)