kosong:LLM 抽象层
kosong 的 provider 适配、finish reason 归一化与 thinking 档位,以及它在 v0.43 里的新位置:v2 引擎改用自带的 human/llm 请求库。
packages/kosong(马来语「空」)是 LLM / provider 抽象层,src/ 约 9200 行,其中约 6900 行是 provider 适配。它没有任何 workspace 内部依赖,只依赖 Anthropic、OpenAI、Google 三家官方 SDK 与 zod、zod-to-json-schema,可以独立拿出来用。
位置变了
v0.29 时,kosong 是 v1 引擎发请求的那一层。v0.43 里:
- agent-core-v2 不依赖 kosong。它的
package.json里没有 kosong,import 边界检查还会直接拦下任何引用(packages/agent-core-v2/scripts/check-import-boundaries.mjs:135):
if (KOSONG_PATH_RE.test(specifier)) {
violations.push({
file: absFile,
line,
message: `the kosong kernel is deleted (${specifier}) — request/provider code lives in #human/llm, the v2 compatibility boundary is #/llm-adapter`,
});- 用 kosong 的是
packages/node-sdk:SDK 的公开类型(ContentPart、ThinkingEffort、ToolCall)、provider 配置类型、错误类(APIStatusError等)和 models.dev 目录解析都来自这里。
也就是说,kosong 仍是仓库里那个可独立使用的抽象层,但引擎发请求走的是 v2 自己的 src/human/llm/。下面先讲 kosong 本身,再做对照。
结构
packages/kosong/src/
├── provider.ts ChatProvider 接口与核心类型(274 行)
├── message.ts 消息与 ContentPart 模型(253 行)
├── generate.ts 生成流程(365 行)
├── catalog.ts models.dev 风格的模型元数据(509 行)
├── errors.ts 错误分类(622 行)
└── providers/
├── anthropic.ts 1334 行
├── openai-responses.ts 1228 行
├── google-genai.ts 998 行
├── openai-legacy.ts 723 行 (Chat Completions)
├── kimi.ts 682 行
├── kimi-errors.ts 87 行 (v0.29 之后新增)
└── ...createProvider() 按 type 分派:anthropic、openai(Chat Completions)、openai_responses、google-genai / vertexai、kimi。ContentPart 仍是 TextPart | ThinkPart | ImageURLPart | AudioURLPart | VideoURLPart,推理内容与视频输入都是一等类型。
FinishReason 归一化
export type FinishReason =
| 'completed'
| 'tool_calls'
| 'truncated'
| 'filtered'
| 'paused'
| 'other';provider.ts:76 上方的注释列了对照:completed 对应 OpenAI 的 stop、Anthropic 的 end_turn / stop_sequence、Gemini 的 STOP;truncated 对应 length、max_tokens、MAX_TOKENS、max_output_tokens;paused 是 Anthropic 特有的 pause_turn。最值得记住的一句:
Note that the OpenAI Responses API and Google GenAI report
'completed'here; only the Chat Completions–style providers and Anthropic surface a dedicated value.
Responses API 与 Gemini 带着工具调用也报 completed,所以判断要不要执行工具不能只看 finish reason。两个逃生舱照旧:rawFinishReason 保留原始字符串,null 表示 provider 根本没给。
ThinkingEffort
export type ThinkingEffort = 'off' | 'on' | (string & {});(string & {}) 让联合类型既保留 'off' / 'on' 的字面量提示,又允许任意字符串:只有开关的模型用 'on',分档模型用自己声明的档位(low、high、max)。兼容与回退在到达适配器之前就已解决,适配器只做翻译。
MaxCompletionTokensOptions 也还在:Chat Completions 系的 provider 用 usedContextTokens 与 maxContextTokens 把输出上限收紧到剩余窗口。
Kimi 的例外出口
// Kimi provider: exported so callers can narrow a `ChatProvider` to the Kimi
// backend (instanceof) and apply Kimi-specific request params (generation
// kwargs, `thinking.keep` extra body).
export { KimiChatProvider } from './providers/kimi';
export type { ExtraBody, GenerationKwargs, KimiOptions, ThinkingConfig } from './providers/kimi';
export { classifyKimiQuotaError } from './providers/kimi-errors';抽象层故意开的口子:调用方可以 instanceof KimiChatProvider 收窄类型,使用 Kimi 专属参数。新增的 classifyKimiQuotaError 把「额度或余额耗尽」从普通 429 限流里分出来,isRetryableGenerateError() 对它直接返回 false——在充值之前,重试永远不会成功。
对照:v2 的 human/llm
v2 的请求库在 packages/agent-core-v2/src/human/llm/,设计说明见包内 packages/agent-core-v2/docs/zh/llm.md。概念一脉相承,结构换了一套:
| 关注点 | kosong | v2 |
|---|---|---|
| 协议适配 | 每家一个 ChatProvider 类 | 四个协议基座(openai、openai-responses、anthropic、google-genai),各含 contract、format、lower、trait、requester 等模块 |
| 厂商定制 | 子类与构造参数 | 按协议类型化的 trait(OpenAITrait、AnthropicTrait 等) |
| provider 定义 | createProvider(config) 分派到类 | createProvider() 声明协议、连接与错误转换;Kimi 声明了 openai、anthropic、openai_responses 三种协议 |
| 输出 | 回调 | 可序列化事件流:llm.sent、llm.streaming.part、llm.done、llm.failed.* |
| 重试 | 只提供 isRetryableGenerateError(),由调用方重试 | 纯函数策略,由 turn 状态机驱动(见上一篇) |
| 消息转换 | 各适配器内联 | Pattern Rewriter:有序、独立的 pattern 改写消息区间后再 lowering |
原样搬过来的也不少:FinishReason 与 ThinkingEffort 在 human/llm/finish-reason.ts、human/llm/thinking.ts 里是同样的定义;UNKNOWN_CAPABILITY 与能力矩阵思路一致;输出上限收紧挪到 llm-adapter/model/completion-budget.ts,同样带着 usedContextTokens 与 maxContextTokens。
src/llm-adapter/ 是 v2 其余代码与 human 内核之间的兼容边界:基于类的错误(APIContextOverflowError 等)、token 估算、模型目录与请求器实现都在这里。
能力与目录
「模型有什么能力」仍是一份可更新的数据:kosong 的 catalog.ts 解析 models.dev 格式,node-sdk 的 fetchCatalog / loadBuiltInCatalog 负责拉取与兜底,apps/kimi-code/src/built-in-catalog.ts 是内置副本。同一段 agent 代码要在视觉、thinking、上下文窗口差很多的模型之间跑,只能在运行时查能力,不能硬编码。
那些小文件
跨 provider 的麻烦在 kosong 的 providers/ 下各有一个小文件:merge-user-messages.ts(有的 provider 不允许连续 user 消息)、tool-call-id.ts(ID 格式与长度各家不同)、reasoning-key.ts(推理内容字段名不同)、request-auth.ts、chat-completions-stream.ts、anthropic-profile.ts。v2 把同类问题收进协议基座,例如 human/llm/toolCallIdNormalizer.ts 专门把流式响应里重复的 tool call id 重映射成唯一 id。
下一篇:上下文与投影。