opencode LLM 包架构解析:Schema 优先的 LLM 核心与四轴 Route 模型 opencode LLM 包架构解析Schema 优先的 LLM 核心与四轴 Route 模型【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode本文围绕opencode-ai/llm包的架构指南AGENTS.md展开系统讲解这套基于 Effect 的 LLM 核心的请求流程、Route 四轴Protocol / Endpoint / Auth / Framing组合模型、Provider Facade 配置模式、工具调度运行时以及协议文件编写规范与 cassette 录制测试体系。读完本文后你可以理解 opencode 如何把「一套类型化请求/响应/事件/工具语言」与「各家提供商的差异适配」彻底解耦并知道如何为该项目新增一条 provider 路由、编写一个类型化工具或运行一次录制测试。什么是 opencode-ai/llmpackages/llm是 opencode 的 Schema 优先 LLM 核心包一套类型化的请求、响应、事件和工具语言提供商的怪癖quirks全部收敛在适配器里不出现在调用方代码中。README 给出的最小示例即典型用法import { Effect } from effect import { LLM, LLMClient } from opencode-ai/llm import { OpenAI } from opencode-ai/llm/providers const model OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).responses(gpt-4o-mini) const request LLM.request({ model, system: You are concise., prompt: Say hello in one short sentence., generation: { maxTokens: 40 }, }) const program Effect.gen(function* () { const response yield* LLMClient.generate(request) console.log(response.text) })事件流是 provider 中立的——OpenAI Chat、OpenAI Responses、Anthropic Messages、Gemini、Bedrock Converse 以及任何 OpenAI 兼容部署返回的事件形状完全一致。包入口在 src/llm.ts对外导出面通过 package.json 的exports字段精确划分根导出、./route高级 barrel、按提供商拆分的./providers/*以及按协议拆分的./protocols/*openai-chat、openai-responses、anthropic-messages、gemini、bedrock-converse、openai-compatible-chat。Effect 编码规范该包构建在 Effect 之上AGENTS.md 对 Effect 写法有明确约定新代码必须遵循在包边界上优先使用HttpClient.HttpClient/HttpClientResponse.HttpClientResponse而不是 web 的fetch/Response流式数据一律使用Stream.Stream避免临时性的 async generator 或手工 web reader 循环除非 Effect 的StreamAPI 确实无法建模该行为JSON 编解码使用 Effect Schema codec如Schema.fromJsonString(...)实现代码中不直接写JSON.parse/JSON.stringify在Effect.gen中直接 yield 可 yield 的错误return yield* new MyError(...)而不是Effect.fail(new MyError(...))成功值有意为空时使用Effect.void而非Effect.succeed(undefined)。从源码结构看这些约定在 src/route/client.ts 中得到了贯彻compile、prepare、generate等入口均使用Effect.fn(LLM.xxx)具名函数声明便于追踪与测试断言。命名约定per-type 构造器与 LLM 命名空间同一事物的两种构造方式就多了一种。因此约定类型专属构造器挂在类型本身上而不是做成顶层再导出。直接使用Message.system(...) Message.user(...) Message.assistant(...) Message.tool(...) Model.make(...) ToolDefinition.make(...) ToolCallPart.make(...) ToolResultPart.make(...) ToolChoice.make(...) ToolChoice.named(...) SystemPart.make(...) GenerationOptions.make(...)顶层LLM命名空间保留给「请求形态的调用 API」LLM.request、LLM.generate、LLM.stream、LLM.updateRequest、LLM.generateObject。在 src/llm.ts 中可以看到这一约定的落地request(input)是一个薄构造器把易用型输入system: string、prompt: string归一化进规范 Schema 类——SystemPart.content(requestSystem)、messages.map(Message.make)、ToolDefinition.make、GenerationOptions.make等最终new LLMRequest({...})返回同一个 Schema 类实例。updateRequest(input, patch)则是「先展开回RequestInput再合并 patch」的不可变更新。此外LLM.generateObject的实现也印证了「不制造第二套模型」的原则它内部强制构造一个名为generate_object的合成工具并配合ToolChoice.named在所有协议上走完全相同的路径——刻意回避各家 provider 原生的 JSON mode以保证行为一致。请求流程从 LLMRequest 到 LLMResponse预期调用方式是先构造、再执行const request LLM.request({ model: OpenAI.configure({ apiKey }).responses(gpt-4o-mini), system: You are concise., prompt: Say hello., }) const response yield* LLMClient.generate(request)LLM.request(...)构造一个LLMRequest。LLMClient.generate(...)随后读取request.model.route上携带的可执行路由构建 provider 原生 body向路由的 transport 索取一个真实的HttpClientRequest.HttpClientRequest经由RequestExecutor.Service发出把 provider 流解析为公共LLMEvent最终返回LLMResponse。三个执行入口各有分工LLMClient.stream(request)—— 调用方想要增量LLMEvent流LLMClient.generate(request)—— 把同样的事件收集成LLMResponseLLMClient.prepareBody(request)—— 把请求编译过整条路由管线但不真正发送。可选的Body类型参数把.body收窄为路由原生形状例如prepareOpenAIChatBody(...)返回PreparedRequestOfOpenAIChatBody。运行时 body 完全相同泛型只是调用方做出的类型级断言。client.ts 中的compile注释精确描述了这条管线的重要边界// compile is the important boundary: it turns a common LLMRequest into a // validated provider body plus transport-private prepared data, but does not // execute transport. const compile Effect.fn(LLM.compile)(function* (request: LLMRequest) { const resolved applyCachePolicy(resolveRequestOptions(request)) const route resolved.model.route const body yield* route.body .from(resolved) .pipe(Effect.flatMap(ProviderShared.validateWith(Schema.decodeUnknownEffect(route.body.schema)))) const prepared yield* route.prepareTransport(body, resolved) ... })注意其中applyCachePolicy(resolveRequestOptions(request))一步请求级generation/providerOptions/http会先与模型默认值、路由默认值逐轴合并mergeGenerationOptions、mergeProviderOptions、mergeHttpOptions缓存策略在编译期就落进 body——这与 README 中「prompt 缓存默认开启、cache: auto是缺省值」的描述一致。过滤或收窄事件流使用LLMEvent.is.*驼峰守卫例如events.filter(LLMEvent.is.toolCall)。kebab-case 的LLMEvent.guards[tool-call]形式仍然可用但新代码应优先is.*。Route 四轴模型一条路由 Protocol Endpoint Auth Framing这是整个包最核心的架构决策。路由Route是四个正交部件的已注册、可执行组合Protocolsrc/route/protocol.ts——语义 API 契约。拥有请求 body 构造body.from、body schemabody.schema、流事件 schemastream.event以及事件到LLMEvent的状态机stream.step。Route.make(...)会用body.schema校验并 JSON 编码 body用stream.event解码帧。实例OpenAIChat.protocol、OpenAIResponses.protocol、AnthropicMessages.protocol、Gemini.protocol、BedrockConverse.protocol。Endpointsrc/route/endpoint.ts——URL 构造。host、path、route query 都挂在 endpoint 上。Endpoint.path(/chat/completions, { baseURL })是常见形态当路径内嵌模型 id 或 body 字段时如Endpoint.path(({ body }) /model/${body.modelId}/converse-stream)传入一个函数。Authsrc/route/auth.ts——每请求传输鉴权。Provider facade 在选模型之前把凭证配置到路由上通常通过Auth.bearer(apiKey)或Auth.header(name, apiKey)。需要每请求签名的路由Bedrock SigV4、未来的 Vertex IAM、Azure AAD把Auth实现为对 body 签名并把签名头合并进结果签名的函数。Framingsrc/route/framing.ts——字节 → 帧。SSEFraming.sse是共享实现Bedrock 把 AWS event-stream 的帧保持为类型化的Framingobject值与它的协议并存。通过Route.make(...)组合它们export const route Route.make({ id: openai-chat, provider: openai, protocol: OpenAIChat.protocol, endpoint: Endpoint.path(/chat/completions, { baseURL: https://api.openai.com/v1, }), auth: Auth.bearer(), framing: Framing.sse, })路由上的defaults是「请求塑形默认值」headers、limits、generation、providerOptions、http。Endpoint 的 host/query 属于路由 endpoint。选中的Model值只携带模型 id、provider id 和已配置的路由值模型能力/目录元数据活在这个包之外协议兼容性由请求降级lowering阶段和类型化LLMError强制。从源码看Route接口client.ts 的RouteBody, Prepared还暴露with(patch)不可变修补路由facade 覆盖 auth/endpoint 的入口、model(input)由路由构造带路由值的Model以及prepareTransport/streamPrepared传输私有准备与流读取。makeRouteModel中有两个硬性前置条件路由必须能解析出 provider且 endpoint 必须已有baseURL——Route.model(...)在 baseURL 缺失时会直接抛出要求「先配置路由」。这正是「无规范 URL 的路由必须先配置后执行」这条约定在代码中的落点。四轴分解的收益DeepSeek、TogetherAI、Cerebras、Baseten、Fireworks、DeepInfra 全部原样复用OpenAIChat.protocol——每个 provider 部署只是一段 5~15 行的Route.make(...)调用而不是 300~400 行的路由克隆某个协议里修一个 bug一次提交就能传导到该协议的所有消费者。非 HTTP 传输的接缝是Transport当某 provider 提供非 HTTP 传输OpenAI 的 WebSocket Responses 后端、假想的双向流式 API时WebSocketTransport.jsonTransport.with(...)构造一个 IO 模板其prepare在编译期接收路由 endpoint/auth构建 WebSocket URL 与消息其frames从 socket 产出解码后的文本。同样的协议与 endpoint 来源不同的 transport。LLMClient.layerclient.ts 末尾同时装配RequestExecutor.Service与可选的WebSocketExecutor.Service两种运行时在此汇合。URL 构造规则Endpoint拥有{ baseURL, path, query }。每个协议路由在 provider 有规范地址时会带一个如https://api.openai.com/v1provider 助手在选模型之前通过配置路由来覆盖 endpoint 字段。没有规范 URL 的路由OpenAI 兼容 Chat、GitHub Copilot执行前必须完成配置。对 URL 由类型化输入派生的 providerAzure 资源名、Bedrock regionprovider 助手在调用.model(...)之前配置路由 endpoint。当输入接受两条二选一的派生路径时AzureresourceName或baseURL使用 route/auth-options.ts 中的AtLeastOneT。Provider Facade先配置、后选模型面向 provider 的 API 是「路由值之上的已配置 facade」endpoint/auth/资源/API 版本的设置在选模型之前完成模型选择器只接受一个模型 id 或部署 idconst openai OpenAI.configure({ apiKey, baseURL }) const model openai.responses(gpt-4o-mini) const azure Azure.configure({ resourceName, apiKey, apiVersion: v1 }) const deployment azure.responses(my-deployment) const gateway CloudflareAIGateway.configure({ accountId, gatewayId, gatewayApiKey, apiKey }) const proxied gateway.model(openai/gpt-4o-mini)Facade 应保持小而显式直接构造 id 时使用 branded 的ProviderID.make(...)和ModelID.make(...)用model表示默认 API 路径用命名方法表示 provider 原生替代路径OpenAI 的responses、responsesWebSocket、chatprovider 专属设置放.configure(...)不要新增model(id, overrides)这种重复构造路径仅当高级内部接线确有需要时才单独导出底层routes数组apiKey作为 provider 专属糖auth作为显式覆盖在 provider option 类型里用ProviderAuthOption保持二者互斥用AuthOptions.bearer(options, PROVIDER_API_KEY)把apiKey解析为Auth——它尊重显式auth覆盖并回退到Auth.config(envVar)使缺失的 key 表现为类型化Authentication错误而不是运行时崩溃对需要不同必填设置的同一厂商产品使用独立的顶层 facade如CloudflareAIGateway与CloudflareWorkersAI。Provider.make(...)对简单静态 provider 定义仍然可用但新的内置 provider 应优先使用普通已配置 facade除非某个 helper 在不增加运行时行为的前提下消除了真实重复。auth.ts 中的MissingCredentialError/AuthenticationReason映射toLLMError正是「缺失凭证 → 类型化错误」这一承诺的实现细节。目录布局与依赖方向packages/llm/src/ schema/ 规范 Schema 模型按关注点拆分 ids.ts branded IDs、字面量类型、ProviderMetadata options.ts Generation/Provider/Http options、Limits、Model、cache policy messages.ts content parts、Message、ToolDefinition、LLMRequest events.ts Usage、各事件、LLMEvent、PreparedRequest、LLMResponse errors.ts 错误原因、LLMError、ToolFailure index.ts barrel llm.ts 请求构造器与便捷 helper route/ index.ts opencode-ai/llm/route 高级 barrel client.ts Route.make LLMClient.prepare/stream/generate executor.ts RequestExecutor service transport 错误映射 protocol.ts Protocol 类型 Protocol.make endpoint.ts Endpoint 类型 Endpoint.path auth.ts Auth 类型 Auth.bearer / Auth.apiKeyHeader / Auth.passthrough auth-options.ts ProviderAuthOption 形状、AuthOptions.bearer、AtLeastOne helper framing.ts Framing 类型 Framing.sse transport/ transport 实现 index.ts Transport 类型 HttpTransport / WebSocketTransport 命名空间 http.ts HttpTransport.httpJson — POST framing websocket.ts WebSocketTransport.json WebSocketExecutor service protocols/ shared.ts 协议实现内使用的 ProviderShared 工具集 openai-chat.ts protocol route组合 OpenAIChat.protocol openai-responses.ts anthropic-messages.ts gemini.ts bedrock-converse.ts bedrock-event-stream.ts AWS event-stream 二进制帧的 framing openai-compatible-chat.ts 复用 OpenAIChat.protocol、无规范 URL 的 route utils/ 每协议 helperauth、cache、media、tool-stream 等 providers/ openai-compatible.ts 通用兼容 helper 家族模型 helper openai-compatible-profile.ts 家族默认值deepseek、togetherai 等 azure.ts / amazon-bedrock.ts / cloudflare.ts / github-copilot.ts / google.ts / xai.ts / openai.ts / anthropic.ts / openrouter.ts tool.ts 类型化 tool() helper tool-runtime.ts 窄化的单调用类型化工具调度器依赖箭头向下providers/*.ts导入协议路由与 auth-option 工具协议模块导入endpoint、auth、framing与 transport 部件。协议不导入 provider facade更底层的模块对 provider 目录元数据一无所知。ProviderShared协议实现的公共工具箱protocols/shared.ts 导出一个小工具集让协议实现聚焦于 provider 原生形状joinText(parts)—— 用换行连接TextPart数组或任何带.text的对象。协议把文本内容压平为单一字符串填 provider 字段时都用它parseToolInput(route, name, raw)—— 用规范错误消息 Invalid JSON input forroutetool callname 对工具调用参数串做 Schema 解码空输入按{}处理parseJson(route, raw, message)—— 非工具 body 的通用 JSON-via-Schema 解码eventError(route, message, ...)—— 流式解码失败时构造类型化InvalidProviderOutputvalidateWith(decoder)—— 把 Schema 解码错误映射为InvalidRequest。Route.make(...)用它做 body 校验低层路由可复用matchToolChoice(provider, choice, branches)—— 对LLMRequest[toolChoice]做 provider 专属降级分支。准则如果你发现自己在两个协议之间复制同一段 3~5 行的片段把它提升到ProviderShared与上述 helper 并排放置而不是重复实现。时间序列 System 更新LLMRequest.system是初始的特权提示词作用于整段对话之前。而Message.system(...)是另一回事它是LLMRequest.messages中一个独立的、provider 中立的时间序列操作者更新只从其所在位置起向后生效且只接受文本内容。原生时间序列 system 消息是 route/model 相关的Anthropic Messages 对 Claude Opus 4.8claude-opus-4-8做原生降级。其他路由与模型刻意把更新就地降级为普通 user 兼容文本使用稳定的转义表示system-update ... /system-update这条 wrapped-user 回退在降低权限外观的同时保持顺序。绝不要把裸的时间序列role: system消息穿过可能拒绝它的路由也不要把检索到的原始文档、工具输出或 web 内容塞进特权时间序列 system 更新——不可信内容留在普通 user/tool 通道。工具循环与类型化工具调度工具循环用公共消息和事件表示const call ToolCallPart.make({ id: call_1, name: lookup, input: { query: weather } }) const result Message.tool({ id: call_1, name: lookup, result: { forecast: sunny } }) const followUp LLM.request({ model, messages: [Message.user(Weather?), Message.assistant([call]), result], })路由把这些降级为 provider 原生的 assistant 工具调用消息与工具结果消息。流式 provider 应在参数到达期间发出tool-input-delta事件随后发出带解析后 input 的最终tool-call事件。ToolRuntime.dispatch只跑一个 provider turnLLM.stream(request)与LLM.generate(request)各执行恰好一个provider turn。把工具 schema 通过Tool.toDefinitions(tools)加进request.tools当调用方想要包提供的类型化单调用执行行为时把每个规范的本地tool-call事件传给ToolRuntime.dispatch(tools, call)const get_weather tool({ description: Get current weather for a city, parameters: Schema.Struct({ city: Schema.String }), success: Schema.Struct({ temperature: Schema.Number, condition: Schema.String }), execute: ({ city }) Effect.gen(function* () { // city: string — 由 parameters Schema 推导类型 const data yield* WeatherApi.fetch(city) return { temperature: data.temp, condition: data.cond } // 返回类型相对 success Schema 被检查 }), }) const tools { get_weather, get_time, ... } const events yield* LLM.stream( LLM.updateRequest(request, { tools: Tool.toDefinitions(tools) }), ).pipe(Stream.runCollect) const call Array.from(events).find(LLMEvent.is.toolCall) if (call !call.providerExecuted) { const dispatched yield* ToolRuntime.dispatch(tools, call) // 持久化 call dispatched.result然后显式构造下一个请求。 }tool-runtime.ts 中的调度器职责边界非常窄dispatch的实现可以逐行核对对tool-call按名字查工具用parametersSchema 解码 input分派到类型化execute用successSchema 编码结果返回规范的tool-result事件不流式读 provider、不构造 Session 事件、不调度 fiber、不追加历史、不数步数、不继续模型回合持久化与继续continuation留给外层产品流程。handler 依赖services、permissions、plugin hooks、abort 处理由消费方在工具构造时闭包捕获。建议在Effect.gen内一次性构建 tools 记录并在多次 dispatch 间复用。错误必须表达为ToolFailure。运行时捕获它并发出tool-error事件随后是一条type: error的tool-result模型可以在下一步自我纠正。任何非ToolFailure的东西都被视为缺陷defect使整个流失败。源码中三条可恢复错误路径都会产出tool-error事件模型调用了未知工具名Unknown tool: ...input 未通过parametersSchemaInvalid tool input: ...handler 返回了ToolFailure。此外 tool-runtime.ts 还处理了execute缺失与 success schema 编码失败Tool returned an invalid value for its success schema——前者产生错误结果后者同样折叠为ToolFailure。Provider 定义/托管工具直通Anthropic 的web_search/code_execution/web_fetchOpenAI Responses 的web_search_call/file_search_call/code_interpreter_call/mcp_call/local_shell_call/image_generation_call/computer_use_call在运行时原样穿过路由把模型的调用作为providerExecuted: true的tool-call事件呈现把 provider 结果作为匹配的providerExecuted: truetool-result事件呈现调用方在tool-call上检测providerExecuted并跳过本地分派——不调 handler也不为「未知工具」抛tool-errorprovider 已经执行过了继续对话的调用方在协议要求时应在显式历史中保留两个事件Anthropic 把它们编码回server_tool_useweb_search_tool_result或code_execution_tool_result/web_fetch_tool_result块OpenAI Responses 调用方通常使用previous_response_id而不是重发 hosted-tool 条目。把 provider 定义工具加进request.tools不需要运行时条目。匹配的路由必须知道如何把工具定义降级为 provider 原生形状当前 Anthropic 接受web_search/code_execution/web_fetchOpenAI Responses 接受上述托管工具名。协议文件风格让文件互相「长得像」协议文件应当彼此自相似。provider 怪癖应藏在具名 helper 后面使得评审一个新路由时可以跨文件比对相同章节。章节顺序每个协议模块使用这个顺序公共模型输入请求 body schema流事件 schema解析器状态请求 body 构造fromRequest流解析step与逐事件 handlerProtocol 与 route协议路由导出规则协议文件聚焦于协议本身。provider 专属投影、签名、媒体归一化或其他臃肿转换移入src/protocols/utils/*请求 body 构造入口用Effect.fn(Provider.fromRequest)yield effect 的事件 handler 用Effect.fn(...)纯同步 handler 保持为普通函数、返回StepResult由调度器经Effect.succeed(...)提升解析器状态拥有终止信息状态机记录 finish reason、usage 与挂起工具调用每个完成的响应恰好发出一个终止finish事件或provider-error。若 provider 把 reason 和 usage 拆在不同事件里在 flush 前于解析器状态中合并对完成的响应恰好发一个终止finish事件通常在匹配的step-finish之后。provider 有完成哨兵时用stream.terminal停止读取当最终事件必须在帧流结束后 flush 时用stream.onHalt。对应地client.ts 中streamPrepared的实现正是Stream.mapAccumEffect(() protocol.stream.initial(request), protocol.stream.step, ...)并在有terminal时套Stream.takeUntil重复的协议策略文本拼接、usage 汇总、JSON 解析、工具调用累积使用共享 helper。ToolStreamprotocols/utils/tool-stream.ts统一累积流式工具调用参数有意的 provider 差异要在 helper 名或注释里显式表达。如果两个协议文件视觉上有差异原因应当从命名上就能看明白优先用从一个小顶层stepswitch 分派出来的逐事件 handleronMessageStart、onContentBlockDelta等而不是长 if 链。分派器让事件面一目了然测试与协议保持同一概念顺序基础 prepare、工具 prepare、不支持的降级、文本/usage 解析、工具流、finish reasons、provider 错误。评审清单能否与openai-chat.ts并排快速扫读而不必翻找对应章节provider 怪癖是否被命名、隔离并有聚焦测试覆盖请求 body 构造是否在协议边界校验不支持的公共内容流解析是否发出稳定的公共事件而不把 provider 事件顺序泄漏给调用方toolChoice: none的行为读起来是否「有意为之」测试体系Effect 层测试与 cassette 录制单元测试层面需要 Effect layer 的测试统一使用 test/lib/effect.ts 中的testEffect(...)provider 测试保持 fixture-first真实的 provider 调用必须留在RECORDtrue与必需 API key 检查之后。录制测试使用每场景一个 cassette 文件。cassette 保存一个有序{ request, response }交互数组因此多步流程工具循环、重试、轮询都录制进同一个文件。用recordedTests({ prefix, requires })让 helper 从测试名派生 cassette 名const recorded recordedTests({ prefix: openai-chat, requires: [OPENAI_API_KEY] }) recorded.effect(streams text, () Effect.gen(function* () { // 测试主体 }), )replay 是默认模式RECORDtrue录制新 cassette 并要求所列环境变量。cassette 以 pretty-printed JSON 写出多交互 diff 可评审。给recordedTests(...)/recorded.effect.with(...)传provider、protocol与可选tags让 cassette 携带可搜索元数据。录制过滤器用于不重写整个文件就 replay 或录制窄子集RECORDED_PROVIDERopenai—— 匹配打了provider:openai标签的测试支持逗号分隔多值RECORDED_PREFIXopenai-chat—— 按recordedTests({ prefix })匹配 cassette 组支持逗号分隔RECORDED_TAGStool—— 要求所列标签全部存在如RECORDED_TAGSprovider:togetherai,toolRECORDed_TESTstreams text—— 按测试名、kebab-case 测试 id 或 cassette 路径匹配即RECORDED_TESTstreams text。过滤器在 replay 与 record 模式下都生效配合RECORDtrue即可只刷新一个 provider 或一个场景。二进制响应体大多数 provider 流式返回文本SSE、JSON。录制器把已知的文本型 media typetext/*、JSON/XML 结构化类型、JavaScript、表单、YAML、SVG当文本处理其余响应以bodyEncoding: base64存储为 base64——这让 AWS event-stream 帧等二进制格式免于有损的 UTF-8 往返。匹配策略replay 通过内部游标按录制顺序遍历 cassette——第 N 个运行时请求由第 N 个录制的交互提供并逐一校验 method、URL、白名单 header 与规范化 JSON body。这统一地支持工具循环每一轮请求因历史增长而不同与重试/轮询场景逐字节相同请求、不同响应。如果测试重排了请求顺序需要重新录制 cassette。test/lib/http.ts 中的scriptedResponses是不需要真实 provider 的确定性对等物按顺序脚本化响应 body不从磁盘读取。纪律新增一个 cassette 时不要整体重录整个测试文件。RECORDtrue会重写每个运行到的录制用例而 provider 流里包含易变 id、时间戳、指纹与混淆字段。应删除那一个打算刷新的 cassette或只运行注册目标场景的聚焦测试模式除非请求形状或期望行为变了保持既有稳定 cassette 不变。仓库内集成点与边界该包刻意保持独立于 session 关注点。session 鉴权、权限、插件、遥测头与运行时选择都属于 opencode 侧。主要集成点packages/opencode/src/session/llm.ts —— session 拥有的编排层决定某次请求走 AI SDK 还是本包的原生 route runtimenative-request.ts —— 把 opencode 的 session/AI SDK 形状数据降级为本包LLMRequest模型的适配器native-runtime.ts —— 调用裸LLMClient.stream(request)、通过本包的类型化分派器桥接 opencode 工具调用一个 provider turn 的执行适配器ai-sdk.ts —— 把 AI SDK 流部件转换为本包共享LLMEvent保持默认 AI SDK 路径兼容。这条边界意味着在packages/llm内写代码时永远不要把 session 级概念鉴权上下文、权限检查、telemetry 头注入带进来它们属于 session 编排层及其本地适配器。小结opencode-ai/llm的设计可以用三句话概括src/schema/的 Schema 类是唯一运行时数据模型llm.ts 的便捷函数只是返回同一批 Schema 类实例的薄构造器一条路由由 Protocol、Endpoint、Auth、Framing 四个正交部件经Route.make(...)组合提供商差异被压缩为 5~15 行的配置调用而工具调度器tool-runtime.ts只负责「解码输入 → 执行 → 编码输出 → 产出事件」这一窄窄的一段把流读取、持久化与对话继续全部留给外层。配套的类型化错误LLMError/ToolFailure、fixture-first 的 cassette 录制测试与「协议文件互相像」的风格清单则共同保证了这套多协议体系在扩张时仍然可评审、可推理。可运行的端到端示例见 example/tutorial.ts协议层测试见packages/llm/test/下的*.test.tsfixture 优先与*.recorded.test.tslive cassette。【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

相关新闻

计算机单片机毕设实战-基于 STM32 与 ESP‑01S 的室内空气质量监测平台设计与实现 基于 STM32 单片机的温湿度甲醛烟雾粉尘检测系统设计(010107)

计算机单片机毕设实战-基于 STM32 与 ESP‑01S 的室内空气质量监测平台设计与实现 基于 STM32 单片机的温湿度甲醛烟雾粉尘检测系统设计(010107)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/9/7 16:48:49
【单片机课程设计/毕业设计】基于 STM32 单片机的 OLED 实时环境数据显示系统设计与开发 基于 STM32 的室内环境多维度监测与智能联动控制系统设计(010107)

【单片机课程设计/毕业设计】基于 STM32 单片机的 OLED 实时环境数据显示系统设计与开发 基于 STM32 的室内环境多维度监测与智能联动控制系统设计(010107)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/9/7 16:48:49
CS-Notes 剑指 Offer 动态规划:跳台阶问题的递推建模与 O(1) 空间解法

CS-Notes 剑指 Offer 动态规划:跳台阶问题的递推建模与 O(1) 空间解法

CS-Notes 剑指 Offer 动态规划:跳台阶问题的递推建模与 O(1) 空间解法 【免费下载链接】CS-Notes :books: 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计 项目地址: https://gitcode.com/GitHub_Trending/cs/CS-Notes 本文基于 CS-Notes 仓库中…

2026/9/7 16:48:49

最新新闻

MCP+A2A双协议驱动企业级多智能体Agent集群实战

MCP+A2A双协议驱动企业级多智能体Agent集群实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/7 17:23:52
DMA工作原理与实战:从数据搬运到串口接收、ADC采样

DMA工作原理与实战:从数据搬运到串口接收、ADC采样

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/7 17:23:52
猫抓 Cat-Catch 完整指南:从安装到 M3U8 下载,网页视频嗅探一次讲清

猫抓 Cat-Catch 完整指南:从安装到 M3U8 下载,网页视频嗅探一次讲清

猫抓 Cat-Catch 完整指南:从安装到 M3U8 下载,网页视频嗅探一次讲清 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 在视频…

2026/9/7 17:23:52
Strapi 自定义字段(Custom Fields)机制深度解析:从 RFC 设计到源码实现

Strapi 自定义字段(Custom Fields)机制深度解析:从 RFC 设计到源码实现

Strapi 自定义字段(Custom Fields)机制深度解析:从 RFC 设计到源码实现 【免费下载链接】strapi 🚀 Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-fir…

2026/9/7 17:23:52
CS-Notes 剑指 Offer 46:把数字翻译成字符串——用动态规划计算数字串的解码方案数

CS-Notes 剑指 Offer 46:把数字翻译成字符串——用动态规划计算数字串的解码方案数

CS-Notes 剑指 Offer 46:把数字翻译成字符串——用动态规划计算数字串的解码方案数 【免费下载链接】CS-Notes :books: 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计 项目地址: https://gitcode.com/GitHub_Trending/cs/CS-Notes …

2026/9/7 17:23:52
系统重装避坑完全指南:UEFI引导、驱动恢复与双系统实战

系统重装避坑完全指南:UEFI引导、驱动恢复与双系统实战

说句实话,干了这么多年电脑维护,系统重装这活儿我闭着眼都能操作,但每次在群里看到有人问“装完Win10网卡驱动怎么打不上”“Ubuntu装完开机直接进Windows了”,我就知道这活儿看着简单,里面全是细节。你看着别人三五分…

2026/9/7 17:18:51