v1.1.0Pi v1.1.0 源码分析Analysis of Pi v1.1.0 · 2026-10-07

pi-aipi-ai

统一的 LLM API:模型目录、认证、42 个 provider、事件流Unified LLM API with automatic model discovery and provider configuration

pi-aiInfra

← → 翻页 · F 全屏 · O 目录 · T 明暗 · L 语言← → navigate · F fullscreen · O overview · T theme · L language

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.001
01 · 入口与打包01 · Entry points and bundlingpi-ai

入口与打包Entry points and bundling

pi-ai entry points and bundling懒加载链Lazy-loading chain15 个 *.lazy.ts 包装15 *.lazy.ts wrappers@earendil-works/pi-ai无副作用side-effect freeTypeBox 立即加载eager TypeBox…/pi-ai/models最小smallest…/providers/<id>SDK 首次请求时加载SDK on first request…/providers/all目录全部打包all catalogs bundledSDK 仍懒加载SDKs still lazy…/api/<id>SDK 立即加载eager SDK…/oauth · /bun-oauth按需on demand…/utils/*按文件per file…/pi-ai/compat已弃用deprecatedproviders/anthropic.tscreateProvider({ api })anthropic-messages.lazy.tslazyApi(() => import(…))anthropic-messages.tsstream · streamSimple@anthropic-ai/sdk首次请求才加载loaded on first request导入路径IMPORT PATH得到什么WHAT YOU GET加载成本LOAD COST核心:类型、Type、createModels、createProvider、EventStream、工具函数core: types, Type, createModels, createProvider, EventStream, helpers仅 Models 运行时(不含 TypeBox 校验)Models runtime only (no TypeBox validation)一个 provider:模型目录 + 认证 + 懒加载的 APIone provider: catalog + auth + lazily-loaded APIbuiltinModels() · builtinProviders()(42 个)builtinModels() · builtinProviders() (42)直接调用某个 API 实现(stream / streamSimple)one API implementation directly (stream / streamSimple)OAuth 登录辅助(Node / Bun)OAuth login helpers (Node / Bun)event-stream · validation · transcript · overflow …旧的全局 API(stream / complete / getModel)超集old global API superset (stream / complete / getModel)

每个 API 都有一个 *.lazy.ts:lazyApi(() => import("./x.ts")),provider 只引用这个包装。Every API has a *.lazy.ts: lazyApi(() => import("./x.ts")); providers only reference that wrapper.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.002
02 · Models 与 Provider02 · Models and providerspi-ai

Models 与 ProviderModels and providers

The Models collection and providersrefresh() · 每个 provider 的 refreshModels(ctx)refresh() · each provider’s refreshModels(ctx)builtinProviders()providers/all · 42 个providers/all · 42 providerscreateProvider({…})id · api | { [api]: … } · modelsfetchModels? · images? · classifiers? · auth?createModels({ providers })或 builtinModels()or builtinModels()«implements Models»ModelsImpl−providers: Map<id, Provider>−credentials: CredentialStore (InMemory)−store?: ModelsStore · authContext+getModel(provider, id) · getModels()+getAvailableModels() · checkAuth()+refresh({ allowNetwork, signal })+login(id, interaction) · logout(id)+stream · complete · streamSimple · completeSimple+streamDeferred · cancelDeferred+generateImages · classify (never reject)«interface»Provider<TApi>+id · name · baseUrl? · headers?+auth?: ApiKeyAuth | OAuthAuth …+getModels() · getAllModels()+refreshModels?(ctx)+stream · streamSimple+fetchDeferred? · cancelDeferred?+generateImages? · classify?dispatch(model, run)单一 api,或按 model.api 查表single api, or look up by model.api缺失 → error 流(不抛出)missing → error stream (no throw)读取 ModelsStoreread ModelsStorectx.stored.models先发布缓存publish cachedpublish({ update })allowNetwork?allowNetwork?否 / 已中止 → 结束no / aborted → stopfetchModels(ctx)网络获取目录fetch the catalog合并后发布merge & publish按 id 覆盖基线override baseline by id每个调用each callsProvider[]new*stream 时on streamgetModels() 永远同步:返回 基线目录 ∪ 动态目录;refresh 只改变动态部分getModels() stays synchronous: baseline ∪ dynamic models; refresh only swaps the dynamic part

一个 provider 可以按 model.api 分派到多个 API(如 Fireworks 同时用 OpenAI 与 Anthropic 兼容 API)。One provider can dispatch to several APIs by model.api (e.g. Fireworks uses both OpenAI- and Anthropic-compatible APIs).

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.003
03 · 一次 streamSimple 调用03 · One streamSimple callpi-ai

一次 streamSimple 调用One streamSimple call

models.streamSimple() end to endsetup()(异步,在外层流背后)setup() (async, behind the outer stream)models.streamSimple(model, context, options)normalizeContext(context)→ TranscriptContext(同步)→ TranscriptContext (sync)lazyStream(model, setup)立即返回外层流returns the outer stream now调用方开始迭代caller iteratesfor await … · result()requireChatProvider未知 provider → 抛出unknown provider → throwapplyAuth(model, opts)resolveProviderAuth()provider.streamSimpledispatch(model.api)lazyApi → import()首次加载 API 模块first load of the API moduleapi.streamSimple()SDK 请求 → 内层流SDK request → inner streamforwardStream(outer, inner)逐个 push 事件 → end(result)push each event → end(result)任一步骤抛出any step throws→ push({ type: "error", reason: "error" })done / error 消息done / error message写入 durationMs(1.1.0)gets durationMs (1.1.0)setup()resolveProviderAuth order:1 options.apiKey override2 stored credential (oauth refresh | api_key)3 ambient env (ANTHROPIC_API_KEY …)之后合并 headers → transformHeaders → env;then merge headers → transformHeaders → env;auth.baseUrl 存在时覆盖 model.baseUrlauth.baseUrl overrides model.baseUrlcatch约定:流式 API 从不 reject —— 所有失败都编码成流里的最终 error 事件,contract: streaming APIs never reject — every failure is encoded as the final error eventouter.result() 同样解析为带 stopReason 与 errorMessage 的 AssistantMessageouter.result() resolves to an AssistantMessage carrying stopReason + errorMessage1234567

认证解析顺序:调用方的 apiKey 覆盖 → 已存凭据(OAuth 会先刷新)→ 环境变量。Auth resolution order: caller apiKey override → stored credential (OAuth refreshed first) → environment.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.004
04 · 事件语法与 EventStream04 · Event grammar and EventStreampi-ai

事件语法与 EventStreamEvent grammar and EventStream

AssistantMessageEvent grammar (12 types)内容块 × n(按 contentIndex)content blocks × n (by contentIndex)EventStream<T, R>utils/event-stream.tsstartpartialtext_starttext_delta× n · deltatext_end完整块full blockthinking_startthinking_delta× n · deltathinking_end完整块full blocktoolcall_starttoolcall_delta× n · deltatoolcall_end完整块full blockdonestop | length | toolUse | deferrederrorerror | abortedpendingstoplengthtoolUseerroraborteddeferred生产者producerpush(event) · end(result?)queue: FifoQueue<T>waiting: FifoQueue<resolve>消费者consumerfor await (const e of s)result(): Promise<R>done / error 时 resolveresolves on done / errorreason每个事件都带 partial(当前的 AssistantMessage);toolcall_delta 的参数由 parseStreamingJson 增量解析every event carries partial (the AssistantMessage so far); toolcall_delta args are parsed incrementally by parseStreamingJsonStopReasonpending 只出现在 partial 上;deferred = 异步任务,稍后 streamDeferred(handle)pending only on partials; deferred = async job, fetch later with streamDeferred(handle)无人等待no waiter直接交付hand off排队等待parkisComplete(e) 为真后 done = true,后续 push 被忽略;AssistantMessageEventStream 在 done/error/end 时用 performance.now() 写入 durationMsonce isComplete(e) is true, done = true and later pushes are dropped; AssistantMessageEventStream stamps durationMs (performance.now) on done / error / endFifoQueue 用两个数组实现摊还 O(1) 出队FifoQueue uses two arrays for amortised O(1) dequeue

7 种 StopReason:pending 只出现在 partial 上;deferred 表示异步任务,之后用 streamDeferred(handle) 取结果。Seven StopReasons: pending appears only on partials; deferred means an async job, fetched later with streamDeferred(handle).

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.005
05 · 跨模型消息转换05 · Cross-model message transformpi-ai

跨模型消息转换Cross-model message transform

transformMessages: cross-model handoffMessage[]来自任意 provider 的历史history from any providertransformMessages(messages, model, normalizeToolCallId?)api/transform-messages.ts目标 API 的请求target API requestconvertMessages(…)用户消息user messagethinking 块thinking blocks工具调用 idtool-call ids失败的 assistantfailed assistant孤立工具调用orphaned tool callssystem 消息system messages模型不支持图像 → 图像换成文字占位model lacks image input → images become a text placeholder同一模型:保留签名;换模型:转成文本,空的丢弃;redacted 跨模型直接丢弃same model: keep signature; other model: become text, empty ones dropped; redacted dropped cross-modelnormalizeToolCallId:如 OpenAI 450+ 字符 id → Anthropic ^[a-zA-Z0-9_-]+$ ≤ 64;toolResult 同步改写normalizeToolCallId: e.g. 450+ char OpenAI ids → Anthropic ^[a-zA-Z0-9_-]+$ ≤ 64; toolResults remapped toostopReason 为 error / aborted 的消息整条跳过(不可回放)messages with stopReason error / aborted are skipped (not replayable)没有结果的调用 → 合成 isError 的 toolResult("No result provided")calls without a result → synthesize an isError toolResult ("No result provided")在工具调用与其结果之间出现时延后,保证结果紧跟调用held back while tool results are pending, so results stay adjacent to their calls按顺序应用于整个applied in order to the历史;每次请求都会执行whole history, every request结果:在 Claude 中途切到 GPT(或反之)时,同一份会话仍能被新 provider 接受result: switch from Claude to GPT (or back) mid-session and the same history is still accepted by the new provider123456

thinking 签名只在同一模型之间有效;跨模型时 thinking 转成普通文本,redacted thinking 直接丢弃。Thinking signatures are only valid on the same model; across models thinking becomes plain text and redacted thinking is dropped.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.006
06 · 一个 API 实现:anthropic-messages06 · One API implementation: anthropic-messagespi-ai

一个 API 实现:anthropic-messagesOne API implementation: anthropic-messages

anthropic-messages: SSE → eventsmapStopReason()streamSimple() 的 thinking 配置thinking in streamSimple()message_startstartcontent_block_start · texttext_startcontent_block_start · thinkingthinking_start… · redacted_thinkingthinking_startcontent_block_start · tool_usetoolcall_startcontent_block_deltatext_deltathinking_deltatoolcall_deltacontent_block_stop*_endmessage_deltamessage_stopdonestoplengthtoolUseerrorAnthropic SSEANTHROPIC SSE处理HANDLING发出的事件EMITTED记录 id · usagerecord id · usage新建 text 块new text block新建 thinking 块new thinking block"[Reasoning redacted]" + 签名"[Reasoning redacted]" + signature新建 toolCall,partialJson = ""new toolCall, partialJson = ""text / thinking / input_json / signature删除 partialJson,完成块delete partialJson, finish blockstop_reason → mapStopReason · usagecalculateCost(model, usage)end_turn · pause_turn · stop_sequence→max_tokens→tool_use→refusal · sensitive→!reasoningthinkingEnabled: false自适应模型adaptive modelsthinking: adaptive + effort(由 reasoning 映射)thinking: adaptive + effort (mapped from reasoning)其余模型other modelsadjustMaxTokensForThinking → clampMaxTokensToContextbudgetmin(budget, maxTokens − 1024)

工具参数用 parseStreamingJson 增量解析,UI 可以在参数流式到达时就显示。Tool arguments are parsed incrementally with parseStreamingJson, so a UI can show them while they stream.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.007
07 · 思考等级、预算与成本07 · Thinking levels, budgets and costpi-ai

思考等级、预算与成本Thinking levels, budgets and cost

Thinking levels, token budgets and costmaxTokens 的推导(token 型思考 API)deriving maxTokens (token-budget thinking APIs)«interface»Usage+input · output · cacheRead · cacheWrite+cacheWrite1h? (Anthropic only)+reasoning? (subset of output)+totalTokens+cost: { input · output · cacheRead cacheWrite · total } (USD)ModelThinkingLevel(getSupportedThinkingLevels / clampThinkingLevel)ModelThinkingLevel (getSupportedThinkingLevels / clampThinkingLevel)off不思考no thinkingminimal1,024 toklow2,048 tokmedium8,192 tokhigh16,384 tokxhigh需 thinkingLevelMap 声明only if thinkingLevelMap has itmax需 thinkingLevelMap 声明only if thinkingLevelMap has it预算 = DEFAULT_THINKING_BUDGETS(可被 options.thinkingBudgets 覆盖);不支持的等级被 clamp 到最近的可用等级budget = DEFAULT_THINKING_BUDGETS (override with options.thinkingBudgets); unsupported levels clamp to the nearest available one输入(估算)input (estimated)安全余量safety思考预算thinking budget回答 ≥ 1024answer ≥ 1024空闲freecontextWindowclampMaxTokensToContext:maxTokens ≤ contextWindow − 估算输入 − 4096;思考预算 ≤ maxTokens − MIN_ANSWER_TOKENSclampMaxTokensToContext: maxTokens ≤ contextWindow − estimated input − 4096; thinking budget ≤ maxTokens − MIN_ANSWER_TOKENSmodels.tscalculateCost(model, usage):inputTokens = input + cacheRead + cacheWriterates = highest tier with inputTokens > tier.inputTokensAbovecost.input = rates.input / 1e6 × inputcost.cacheWrite = (rates.cacheWrite × short + rates.input × 2 × cacheWrite1h) / 1e6cost.total = input + output + cacheRead + cacheWriteisContextOverflow(message, contextWindow)(utils/overflow.ts)识别各家“上下文过长”错误,供压缩重试isContextOverflow(message, contextWindow) (utils/overflow.ts) recognises each provider’s context-overflow error for compact-and-retryprovider-retry.ts:retryProviderRequest 对 429 / 5xx 重试,受 maxRetryDelayMs 约束provider-retry.ts: retryProviderRequest retries 429 / 5xx, bounded by maxRetryDelayMs

xhigh 与 max 只有在模型的 thinkingLevelMap 明确声明时才可用;null 表示该等级被禁用。xhigh and max exist only if the model's thinkingLevelMap declares them; null disables a level.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.008
08 · 图像与分类器08 · Images and classifierspi-ai

图像与分类器Images and classifiers

One-shot APIs: images and classifiersgenerateImages(model, context, opts)→ Promise<AssistantImages>classify(model, context, opts)→ Promise<ClassifierResult>Models不 reject:失败 = 结果里的never reject: failure =stopReason "error"stopReason "error" in resultopenrouterprovideropenrouter-imagesImageApiAssistantImagesoutput[] · usage? · stopReasontypesafe-system-onecloudflare-workers-ai-system-onellama-cpp-classifyopenai-decisionsImageModel(model.type = "image")不出现在 getModels(),用 getAllModels() 查找ImageModels (type "image") are not in getModels(); find them via getAllModels()无 generateImages 的 provider → 返回 error 结果providers without generateImages → error resulttypesafe · opencode · openrouter · vercel-ai-gatewaycloudflare-workers-ai无内置 provider · 通过 createProvider 注册no built-in provider · register via createProvideropenaiClassifierContext{ state: JSON, images?(1.1.0), questions: { [k]: choice | score | bool } }{ state: JSON, images? (1.1.0), questions: { [k]: choice | score | bool } }ClassifierResult{ answers: { choice·probabilities | score | probability }, usage?, stopReason }

分类器 API 有 4 种;1.1.0 新增 openai-decisions,以及 ClassifierContext.images。There are four classifier APIs; 1.1.0 adds openai-decisions and ClassifierContext.images.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.009
扩展点Extension pointspi-coding-agent

pi-ai 的扩展点Where to plug into pi-ai

1
createProvider()createProvider()

注册自定义 provider:任意 baseUrl、自己的目录与认证。Register a custom provider: any baseUrl, your own catalog and auth.

2
ProviderStreamsProviderStreams

实现 stream / streamSimple 即可接入一种新的线协议。Implement stream / streamSimple to add a new wire protocol.

3
Model.compatModel.compat

为 OpenAI / Anthropic 兼容服务声明差异(字段名、能力开关)。Declare quirks of OpenAI / Anthropic-compatible servers (field names, capability flags).

4
ApiKeyAuth · OAuth authApiKeyAuth · OAuth auth

自定义登录与凭据解析;CredentialStore 可替换为持久化存储。Custom login and credential resolution; swap CredentialStore for persistent storage.

5
ModelsStoreModelsStore

持久化动态目录,启动时先发布缓存。Persist dynamic catalogs and publish the cache at start-up.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.010
←
1 / 10