OpenCode CLI 配 TaoToken:智能体会话管理一条 Key 走通 opencode CLI 的智能体会话管理用 Python 包一层挺省事create_session 里拼一次opencode runsend_message 带上会话 ID 续一次export 把历史会话落成文本。麻烦的是每跑一次都要先确认这次走哪条模型通道、用哪把 Key。这次把接入选型收拢到 TaoToken打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建一把 Key写进 opencode.json 的 provider之后 session list、run、export 三条路径共用同一把 Key 走兼容通道。下面按原文的推进顺序走先看封装脚本里通道和 Key 散在哪些位置再把 Key 申请出来接着是 opencode.json 里 provider 块的写法、Python 侧的最小改动、跑通后的验证动作最后是几个 opencode 侧特有的报错。全程只改配置和几行 Python不重写业务逻辑。1. Python 封装 opencode 之后通道和 Key 被写进了每个函数调用1.1 原文那套 create_session / send_message 卡在哪原文的封装思路很直接用 subprocess 调 opencode CLI一个 Python 函数对应一类会话操作。create_session 起一个新会话send_message 带着会话 ID 继续聊export 把某次会话的完整记录写出来。这三条路径本身没问题问题在于它们各自都揣着一份「接入信息」而且往往是不同的写法。def create_session(workdir: str) - str: return subprocess.run( [opencode, run, --model, SOME_MODEL_ID, --dir, workdir, 先梳理改动点], capture_outputTrue, textTrue, ).stdout def send_message(session_id: str, text: str) - str: return subprocess.run( [opencode, run, --session, session_id, text], capture_outputTrue, textTrue, ).stdout两个函数摆在一起问题就露出来了第一个显式指定了--model第二个什么都没写靠的是 opencode 的默认模型。于是「这次到底走哪条通道」取决于运行时的环境变量、当前目录下的配置文件、以及上一次会话记录里存了什么三者任意一个变了行为就不一样。换台机器、换个终端、换个 workdir都可能把同一条 prompt 送到一个你没预期的通道上。再往下还有第二层麻烦。会话列表那一步通常靠扫本地会话存储目录拿到 session ID而导出那一步是把会话记录文件读出来做格式化。如果存储记录里保存的模型名和实际调用时用的模型名对不上一个是裸 ID一个是带 provider 前缀的写法导出报表里同一次会话会被切成两类统计。这类问题不会报错只会让你在某天对账时发现数字很怪。1.2 把「接入选型」从业务代码里剥出来修法不是给每个函数都补一遍参数而是分层Python 层只负责编排——什么时候开会话、什么时候续聊、什么时候导出模型通道交给 opencode 自己的配置文件去决定。opencode 支持在配置里声明自定义 provider把 baseURL、apiKey、模型列表写进去opencode run每次启动读一次配置之后所有子命令都吃同一份。这么改之后Python 里那些--model参数可以整批删掉。只有需要临时跨模型做 A/B 对比时才用命令行参数覆盖一次属于例外而非常态。判断标准很简单如果你的封装函数里出现了模型名、URL、Key 中的任何一个就说明这一层管得太多了。2. 去 TaoToken 建一把 Key再想清楚它放在哪一层2.1 申请密钥这一步顺手把模型 ID 抄下来打开 TaoToken 注册登录进控制台创建 API Key。Key 通常只在创建时完整显示一次复制之后先放进密码管理器或者本机一个不进版本库的私有文件里。别把它写进.env.example、别写进 README 的示例片段、更别直接 commit——这些位置后来都会被截图发出去。创建完 Key旁边就是模型广场把准备给 opencode 用的模型 ID 原样抄下来。这个字符串后面要一模一样地写进 opencode.json大小写、连字符、版本后缀都别自己改也不要凭记忆拼一个「看起来差不多」的名字。模型广场里的列表就是当时的可用清单以它为准不要拿网上旧文章里的 ID 直接套。另外值得顺手确认一遍这个 Key 打算给几个项目共用。如果只是本机跑 opencode 做会话管理一把就够如果 CI 里也要跑建议另建一把方便单独吊销和单独看用量。2.2 Key 的三种放法选最不容易漏的那种放法换机器成本泄漏风险适用场景写死在 Python 常量改代码高容易跟着提交走不建议放.env用 python-dotenv 读复制文件中取决于 .gitignore单人临时调试放 shell 环境变量配置里用{env:...}引用重新导出一次低长期使用推荐推荐第三种的原因不是洁癖而是它同时解决了「Python 子进程」和「opencode 本体」两个执行入口。opencode 由 subprocess 拉起来时环境变量天然被继承你手动在终端敲opencode run调试时同一份变量也在。配置里不出现明文 Key仓库里也就不会出现。# 追加到 ~/.zshrc 或 ~/.bashrc export TAOTOKEN_API_KEYYOUR_API_KEY写完记得新开一个终端窗口或者source ~/.zshrc生效一次。用 IDE 内置终端的人尤其要注意有些 IDE 是从图形界面启动的不会加载你的 shell rc 文件变量在里面可能是空的这是后面 401 报错最常见的来源。3. opencode.json 的 provider 块baseURL 只写 https://taotoken.net/api3.1 一份能直接照抄的配置opencode 的配置可以放全局也可以放项目根目录。先给一份完整可用的 provider 声明路径取~/.config/opencode/opencode.json{ $schema: https://opencode.ai/config.json, provider: { taotoken: { npm: ai-sdk/openai-compatible, name: TaoToken, options: { baseURL: https://taotoken.net/api, apiKey: {env:TAOTOKEN_API_KEY} }, models: { YOUR_MODEL_ID: { name: YOUR_MODEL_ID } } } }, model: taotoken/YOUR_MODEL_ID }三个地方值得单独说。第一options.baseURL填https://taotoken.net/api末尾不要加/v1SDK 会自己在后面拼具体路径多写一层会直接 404。第二apiKey用{env:TAOTOKEN_API_KEY}这种引用写法不要贴明文。第三models里的 key 必须是模型广场上真实存在的 IDname字段是给人看的显示名写一样就行。要区分两类地址上面这个https://taotoken.net/api是填进工具、给程序调用的注册账号、创建 Key、看模型广场和用量走的是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 两边的用途不要混着填。3.2 provider 名、model 前缀、模型 ID 三者要对上最容易错的是前缀。上面配置里 provider 的 key 叫taotoken那么顶层的model就必须写成taotoken/YOUR_MODEL_ID斜杠前面是 provider 名后面是模型 ID。有人把 provider 改名成tt却忘了同步改 model 前缀结果 opencode 报找不到 provider——它不是在说网络有问题是在说这个名字在配置里没定义。模型 ID 同理。如果你在模型广场看到的是带版本或带命名空间的长字符串就整段抄进去别只取后半截。opencode 会把provider/model拆开发给对应的 SDK拆错了就是一个 unknown model。3.3 全局配置和项目配置怎么选常用的两三个模型写进全局配置这样任何目录下敲opencode run都有默认通道。某个仓库需要固定用另一个模型就在那个仓库根目录放一份opencode.json只覆盖model字段。项目级配置优先级更高合并规则以 opencode 当前版本的文档为准。这里有个隐蔽的坑opencode 是按当前工作目录向上找配置的而你的 Python 封装里如果给subprocess.run传了cwd/some/other/path读到的就是那个路径下的配置。会话跑得「像是换了模型」但你又没改过配置时先检查cwd再检查配置。4. 让 session list、run、export 共用同一把 Key4.1 opencode run 子进程到底继承了什么subprocess.run的行为规则很简单也很关键不传env参数时子进程完整继承父进程的环境变量一旦传了env就是整体替换不是合并。很多封装代码为了「干净」传了一个手工拼的字典里面只有 PATH结果TAOTOKEN_API_KEY直接消失opencode 启动时配置里的{env:TAOTOKEN_API_KEY}解析成空字符串最终表现为 401。会话列表和导出这两步虽然不发起模型调用但它们读的是同一个会话存储目录。只要 run 那一步稳定走同一条通道后两步拿到的记录就是一致的不需要额外配 Key。4.2 Python 封装里最小改动改动集中在两处去掉所有--model参数以及确保环境变量被继承。import os import subprocess WORKDIR /path/to/your/repo def _env() - dict: # 用 copy()不要手拼一个只含 PATH 的空字典 return os.environ.copy() def run_opencode(prompt: str, session_id: str | None None) - str: cmd [opencode, run, --dir, WORKDIR] if session_id: cmd [--session, session_id] cmd.append(prompt) result subprocess.run(cmd, capture_outputTrue, textTrue, env_env()) if result.returncode ! 0: raise RuntimeError(result.stderr.strip() or opencode run failed) return result.stdout.strip()create_session就是不传session_id调一次run_opencodesend_message就是带上会话 ID 再调一次。会话 ID 从哪来、会话列表怎么列不同 opencode 版本给出的子命令名称不完全一样用opencode --help和opencode run --help确认你本机这一版支持哪些旗标别照抄某个旧版本的写法。4.3 续聊和导出不需要再指定模型续聊之所以不用带模型是因为 opencode 会把会话当时的 provider 和 model 记在会话记录里--session恢复时按记录走。这正好是你想要的效果一次会话中途不会因为默认模型变了而漂到别的通道上。导出时唯一要注意的是分组键。记录里存下来的模型名很可能是taotoken/YOUR_MODEL_ID这种带前缀的形式。做用量统计、成本归集或者简单的会话分类时按这个完整字符串分组如果按裸模型名分组同一批会话可能被算进两个桶里而两边的数量都「看起来合理」很难发现。5. 验证一次 opencode run 是不是真走了 TaoToken 兼容通道5.1 先在模型对话里发一条配置改完别急着跑完整会话先做一次最小验证。打开 模型对话用同一把 Key 和同一个模型 ID 发一条短消息。这一步能把「Key 是否有效」「模型 ID 是否写对」两件事单独摘出来确认避免它们和 opencode 的配置问题混在一起排查。如果这里就失败先别碰 opencode.json去看 Key 有没有复制完整、有没有多余空格、模型 ID 是不是从模型广场复制的。这一步通过之后再往下走后面的问题范围就小很多。5.2 CLI 侧跑一条最短命令回到终端用最快的方式验证配置链路opencode run --dir /path/to/your/repo 只回复两个字收到命令能返回内容说明 baseURL、Key 引用、provider 前缀这三关都过了。接着跑一次你的 Python 封装确认create_session与send_message用的是同一条通道——比较两次返回的模型标识或者去看 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 控制台里的调用记录两次调用应该挂在同一个模型名下。注意每次新建会话都会产生一次真实调用验证阶段用短提示词就够别拿一段几千行的代码去测连通性。6. opencode 侧报错对照401、模型名对不上、子进程丢环境6.1 401 与 api key is required这类报错基本只有一个原因配置里的{env:TAOTOKEN_API_KEY}没解析出值。按顺序查三点——echo $TAOTOKEN_API_KEY在当前终端里是否有输出变量名和配置里花括号内的名字是否完全一致多一个下划线、少一个字母都算不一致当前终端是不是从图形界面启动的、压根没加载 shell rc 文件。三点都过了还是 401就回控制台确认 Key 是否被吊销或过期必要时重新创建一把。6.2 Unknown model / provider not found先看拼写顶层model的斜杠前缀必须等于provider下的那个 key。再看模型 ID 是否与模型广场当前列表一致旧的 ID 可能已经下线。最后看有没有顺手在 baseURL 末尾加了/v1——这种写法不会报「地址错」而是走到一个不存在的路径上最终以 404 或模型不存在的形式出现很容易被误判成模型名写错。6.3 subprocess 里环境变量被清空表现是终端里手敲opencode run一切正常从 Python 调用就 401。这就是env参数被显式传入导致的整体替换。改回os.environ.copy()或者干脆不传env。另外顺手确认cwd如果子进程在另一个目录里启动读到的是那边的 opencode.json配置内容可能完全不同。7. 会话越攒越多之前把 Key 和套餐定下来会话管理这套脚本跑顺之后会话记录会慢慢堆起来调用也会从「偶尔试一次」变成「每天都在跑」。这时候值得回头把两件事固定下来一是 Key 的归属按项目或按环境分开放别让测试脚本和正式会话共用一把二是模型 ID 的写法团队里统一从模型广场取值避免同一个模型在三份配置里写成三种样子。想先手动感受一下通道是否顺手可以直接在 模型对话 里用同一把 Key 试几轮如果 opencode 会话是长期高频使用去 Coding Plan 对一下额度是否够用需要给 CI 或第二台机器再建 Key直接在 控制台 API Keys 里创建然后把同一个 opencode.json 复制过去、导出一次环境变量即可。全部配完之后回到控制台看一眼刚才那次opencode run有没有记上账——记上了说明这条会话链路是真的通了。