
OmniRoute context-relay 组合策略跨账号配额轮换时的会话连续性接力机制【免费下载链接】OmniRouteNever stop coding. Free MIT AI gateway: one endpoint, 352 providers (150 free), 1200 models Kimi, Claude, GPT, Gemini, GLM, DeepSeek, MiniMax. Works with Claude Code, Codex, Cursor, OpenCode, Cline Copilot. Quota-aware auto-fallback, RTKCaveman compression saves 15-95% tokens, MCP/A2A, Desktop/PWA. Built by 550 contributors项目地址: https://gitcode.com/GitHub_Trending/om/OmniRoute导读context-relay是 OmniRoute 中的一种组合Combo路由策略其核心目标是在同一提供商的多个账号之间按配额轮换时保证会话上下文不中断。当活跃账号即将耗尽配额时OmniRoute 会在后台生成一份紧凑的结构化交接摘要认证层将同一会话的下一次请求路由到另一个账号后这份摘要会以系统消息的形式注入新请求让新账号无缝续接任务。读完本文你将掌握该策略的触发阈值与运行时分层流程、交接数据Handoff Payload的持久化结构与注入机制、全部配置项及推荐使用模式以及生成端与注入端在源码中的分工原理。一、什么是 context-relay优先路由之上的接力层在 OmniRoute 的组合策略体系里context-relay的定位非常明确它并不取代优先级路由priority routing对模型的选择逻辑而是在其之上叠加一层会话交接handoff处理。从当前运行时行为看它表现为三层协作在活跃账号配额耗尽之前OmniRoute 会生成一份紧凑的结构化摘要不是完整对话回放而是延续所需的最小上下文认证authentication为同一会话选中了不同的账号后OmniRoute 将这份摘要作为系统消息注入到下一次请求中交接摘要被成功消费注入后的请求成功返回后它会从存储中删除。这一机制在 open-sse/services/contextHandoff.ts 中实现持久化层位于 src/lib/db/contextHandoffs.ts。什么时候该用它原文档给出了三个同时满足才推荐使用的条件组合预期会在同一提供商的多个账号之间轮换丢失短期的对话连续性会损害任务质量提供商暴露了足够的配额信息能够提前预测账号即将触达上限。这类场景最常见于长期运行的编码或研究型会话——它们往往比单个账号的配额窗口活得更久。例如一条组合挂载了同一提供商的 3 个账号会话进行到深夜仍没结束此时就需要接力机制保证换账号后模型依然记得之前做了哪些决策。二、运行时分层流程按配额使用率划分的四个阶段原文档将 context-relay 的运行时行为刻意拆分为生成与注入两层后文架构说明会详解拆分原因并按配额使用率划分成四个阶段。阶段一配额使用 0%84%——不生成任何交接请求行为与普通优先级路由完全一致不产生任何额外开销。这里对应源码中的预警阈值HANDOFF_WARNING_THRESHOLD 0.85见 open-sse/services/contextHandoff.ts。只有当percentUsed handoffThreshold时maybeGenerateHandoff才会进入生成路径。阶段二配额使用 85%94%——后台预生成交接摘要如果当前活跃提供商在handoffProviders白名单内OmniRoute 会在账号尚未完全耗尽时于后台生成结构化交接摘要。原文档强调的细节如下参数值说明默认预警阈值0.85低于该值不生成摘要生成硬停止线0.95达到或超过后不再调度新摘要请求并发限制每个sessionId comboName仅允许 1 个在途生成避免同一会话重复发起摘要请求去重若该会话/组合已存在活跃交接则不重复生成直接跳过这些规则在源码中有完整对应。maybeGenerateHandoffopen-sse/services/contextHandoff.ts依次检查if (relayConfig.handoffProviders.length 0) return; // 白名单为空则禁用 if (options.percentUsed relayConfig.handoffThreshold) return; // 未到预警线 if (options.percentUsed HANDOFF_EXHAUSTION_THRESHOLD) return; // 已到 0.95 硬停线 cleanupExpiredHandoffs(); if (hasActiveHandoff(sessionId, comboName)) return; // 已存在活跃交接 if (inflightHandoffGenerations.has(key)) return; // 已在途生成其中inflightHandoffGenerations是一个Setstring以${sessionId}::${comboName}为键getInflightKey生成结束后通过finally移除从而保证同一会话同一组合最多一个在途摘要请求。生成动作通过setImmediate放到事件循环后台执行不阻塞主请求链路。阶段三配额使用 ≥95%——不再生成新摘要此时系统已处于或接近耗尽状态运行时避免再调度一次摘要请求以免把宝贵的剩余配额浪费在整理交接信息而不是完成用户任务上。源码中的硬停止线HANDOFF_EXHAUSTION_THRESHOLD 0.95正是此边界open-sse/services/contextHandoff.ts。阶段四账号切换之后——仅在实际切换发生时注入当同一会话的下一次请求被认证层解析到另一个已认证账号时OmniRoute 将存储的交接以系统消息形式前置prepend到请求体中。注入只发生在真实账号切换被确认之后——这是整个机制安全性的关键如果请求最终仍落在原账号上注入反而会污染上下文。三、Handoff Payload交接数据的结构与持久化存储位置与字段交接数据持久化在context_handoffs表中字段与类型由迁移脚本 src/lib/db/migrations/019_context_handoffs.sql 定义CREATE TABLE IF NOT EXISTS context_handoffs ( id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))), session_id TEXT NOT NULL, combo_name TEXT NOT NULL, from_account TEXT NOT NULL, summary TEXT NOT NULL, key_decisions TEXT NOT NULL DEFAULT [], task_progress TEXT NOT NULL DEFAULT , active_entities TEXT NOT NULL DEFAULT [], message_count INTEGER NOT NULL DEFAULT 0, model TEXT NOT NULL DEFAULT , warning_threshold_pct REAL NOT NULL DEFAULT 0.85, generated_at TEXT NOT NULL, expires_at TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime(%Y-%m-%dT%H:%M:%SZ, now)) ); CREATE INDEX IF NOT EXISTS idx_context_handoffs_session ON context_handoffs(session_id, expires_at); CREATE INDEX IF NOT EXISTS idx_context_handoffs_expires ON context_handoffs(expires_at); CREATE UNIQUE INDEX IF NOT EXISTS idx_context_handoffs_session_combo ON context_handoffs(session_id, combo_name);原文档列出的 Payload 字段在代码层的HandoffPayload接口src/lib/db/contextHandoffs.ts中一一对应sessionId、comboName交接的作用域键联合唯一索引(session_id, combo_name)保证每个会话/组合只有一份交接fromAccount摘要来自哪个账号连接注入时用于判断是否真的切换了账号summary延续所需的密集摘要keyDecisions已做的关键决策列表taskProgress已完成、待办与下一步activeEntities活跃上下文实体如文件、功能、提供商messageCount参与摘要的消息数量model生成摘要所用的模型warningThresholdPct生成时的预警阈值写入时来自relayConfig.handoffThresholdgeneratedAt/expiresAt生成时间与过期时间。写入、读取与清理写入upsertHandoffsrc/lib/db/contextHandoffs.ts使用INSERT ... ON CONFLICT(session_id, combo_name) DO UPDATE SET ...天然幂等——同一会话/组合的新摘要会覆盖旧摘要读取getHandoff只返回expires_at now的未过期记录按created_at DESC取最新一条过期清理cleanupExpiredHandoffs删除所有expires_at now的记录且带 30 分钟节流CLEANUP_THROTTLE_MS避免频繁全表扫描消费后删除deleteHandoff在注入成功后被调用详见第四节。摘要 JSON 结构生成端通过提示词约束摘要模型返回如下结构的 JSON 对象即原文档给出的结构{ summary: Dense summary of what matters for continuity, keyDecisions: [Decision 1, Decision 2], taskProgress: What is done, what is pending, and the next step, activeEntities: [fileA.ts, feature X, provider Y] }源码中的提示词模板HANDOFF_PROMPT_TEMPLATEopen-sse/services/contextHandoff.ts要求模型只返回 JSON不要 markdown、不要解释并在解析侧做了完整防御MAX_SUMMARY_LENGTH 2000、MAX_TASK_PROGRESS_LENGTH 1200截断超长字段MAX_DECISIONS 8、MAX_ENTITIES 10限制数组元素数量parseHandoffJSONopen-sse/services/contextHandoff.ts会先剥离 markdown 代码围栏stripMarkdownCodeFence与omniModel标签再用firstBrace/lastBrace截取 JSON 候选段解析失败summary为空则整体视为不可用摘要请求体固定temperature: 0.1、max_tokens: 800并携带_omnirouteSkipContextRelay: true与_omnirouteInternalRequest: context-handoff标记防止摘要请求自身再触发交接逻辑造成递归。注入时的系统消息形态在注入时刻OmniRoute 将持久化 Payload 转换为context_handoff系统消息buildHandoffSystemMessageopen-sse/services/contextHandoff.ts其形态大致如下context_handoff transfer_reasonAccount quota transfer - continuing from previous session/transfer_reason session_summary…/session_summary task_progress…/task_progress key_decisions - Decision 1 - Decision 2 /key_decisions active_contextfileA.ts, feature X, provider Y/active_context messages_processed12/messages_processed /context_handoff You are continuing a conversation that was transferred from another account due to quota limits. The context above contains a concise summary of the prior work. Continue seamlessly from where the session left off.所有字段在拼接前都经过 XML 转义escapeXml避免对话内容中的、等字符破坏消息结构。注入时injectHandoffIntoBodyopen-sse/services/contextHandoff.ts会同时兼容两种请求形态Chat Completions 形态把系统消息前置到messages数组与 Responses 形态把交接内容拼进instructions字段从而覆盖 Codex/OpenAI 风格的不同上游协议。四、配置项全局默认与组合级覆盖context-relay支持以下配置字段配置字段含义默认值说明handoffThreshold触发摘要生成的预警阈值0.85取值范围需 0且 0.95硬停线否则回落默认值handoffModel仅用于生成摘要的可选模型覆盖空使用当前请求模型可用于选择更廉价/更快的模型做摘要handoffProviders允许触发交接生成的白名单提供商[codex]不配置时默认只有 codex配置空数组则禁用该策略maxMessagesForSummary参与摘要的最大消息数30源码限制在 5100 之间relayMode交接模式standardschema-locked时摘要采样不携带 system 消息且长度约束更严格全局默认值可以在Settings设置页面配置组合级专属值可以在Combos组合页面覆盖它们——这正是resolveUniversalHandoffConfig中组合配置优先、全局配置兜底的解析逻辑open-sse/services/contextHandoff.ts布尔、字符串、数字、字符串数组均按先查 combo 再查 global 最后回落默认值的优先级合并。配置解析的具体规则在resolveContextRelayConfigopen-sse/services/contextHandoff.ts中实现几个容易踩坑的细节handoffThreshold若非法非数字、≤0 或 ≥0.95静默回落HANDOFF_WARNING_THRESHOLD0.85handoffProviders未显式配置时默认[codex]——这与当前实现以 codex 配额轮换为中心的限制相呼应提供商字符串会统一trim().toLowerCase()后过滤空值避免大小写不一致导致白名单失效。摘要生成的取材策略selectMessagesForSummaryopen-sse/services/contextHandoff.ts决定哪些历史消息进入摘要提示词默认模式standard保留 system/developer 消息 最近maxMessagesForSummary条非系统消息schema-locked模式丢弃 system 消息只取最近消息适用于不允许自定义 system 提示词的协议无论哪种模式最终历史文本都受MAX_HISTORY_TOKENS_FOR_SUMMARY 8000token 上限约束超限时从旧消息开始裁剪确保摘要请求本身不产生过大的上下文开销。五、架构说明为什么没有一个独立的 handler原文档特别澄清当前实现并没有一个独立的handleContextRelayCombo处理器而是刻意把职责拆成两半open-sse/services/combo.ts决定某次成功回合是否应该生成交接src/sse/handlers/chat.ts只在认证解析出本次请求实际使用的账号后才注入交接。这种拆分是有意的组合循环combo loop本身无法可靠判断请求是否停留在同一账号——账号的选择发生在认证auth内部组合层根本看不到。因此生成放在组合层它掌握配额信息注入放在认证之后它掌握真实的账号切换结果。生成端combo.ts 的调用链在 open-sse/services/combo.ts 附近context-relay组合的成功回合会通过fetchCodexQuota(connectionId)获取配额信息然后调用maybeGenerateHandoff({ sessionId: relayOptions.sessionId, comboName: combo.name, connectionId, percentUsed: quotaInfo.percentUsed, messages: handoffSourceMessages, model: modelStr, expiresAt: resetCandidates[0] || null, config: relayConfig, handleSingleModel: handleSingleModelWithTimeout, });注意expiresAt取自配额重置时间quotaInfo.windows?.session?.resetAt、weekly?.resetAt、resetAt中最早的一个——交接的有效期与配额窗口对齐账号配额一旦重置旧交接也随之过期。若配额接口不可用catch(() null)则跳过本次生成不影响主链路。注入端chat.ts 的切换检测与消费在 src/sse/handlers/chat.ts认证路由解析出credentials.connectionId之后if ( comboStrategy context-relay comboName runtimeOptions.sessionId body?._omnirouteSkipContextRelay ! true ) { const handoff getHandoff(runtimeOptions.sessionId, comboName); if (handoff handoff.fromAccount ! credentials.connectionId) { requestBody injectHandoffIntoBody(requestBody, handoff); injectedHandoff handoff; // log.info(CONTEXT_RELAY, Injecting handoff for session ...) } }核心判据是handoff.fromAccount ! credentials.connectionId——只有交接来源账号与本次实际路由到的账号不同才注入同一账号继续处理时交接保持沉睡、不注入。而消费侧src/sse/handlers/chat.ts在请求成功返回后立即if (injectedHandoff runtimeOptions.sessionId comboName) { deleteHandoff(runtimeOptions.sessionId, comboName); }即一次成功消费、随即删除——交接是一次性的避免被同一会话后续请求重复注入。六、限制与边界Limitations原文档明确列出以下限制均可在源码中找到对应佐证运行时支持目前以codex配额轮换为中心默认handoffProviders就是[codex]配额获取走fetchCodexQuotaopen-sse/services/combo.tshandoffProviders虽然已建模为可配置面但真实的交接生成仍依赖各提供商各自的配额管道quota plumbing——非 codex 提供商即使加入白名单也可能因为没有配额读取能力而无法触发摘要是紧凑、基于近期历史的不是完整对话回放transcript replay机制——selectMessagesForSummary有 8000 token 上限且默认只取最近 30 条消息交接以sessionId comboName为作用域并自动过期——expiresAt通常对齐配额重置时间最坏情况也有 5 小时默认 TTLDEFAULT_TTL_MS 5 * 60 * 60 * 1000如果会话没有切换账号已存储的交接不会被注入——这是注入端fromAccount ! connectionId判据的直接推论。此外open-sse/services/contextHandoff.ts 还为交接失败设计了退避机制若摘要模型返回内容无法解析unparseable会对该(session, combo)施加指数退避冷却初始 5 分钟、上限 1 小时避免在模型高频切换的组合上反复浪费上游摘要调用。七、推荐使用模式Recommended Usage Pattern结合原文档与源码落地context-relay的推荐做法如下使用同一提供商的多个账号——这是策略生效的前提单账号场景下交接永远不会被注入在整个会话中保持稳定的sessionId——交接的存储、去重、注入都以sessionId comboName为键sessionId 变化会导致交接对不上号把handoffThreshold设置得足够早——默认 0.85 意味着要留出 15% 的配额余量给后台摘要请求如果摘要模型较慢或历史较长可适当调低阈值但必须 0.95把该特性视为连续性辅助而非持久记忆的替代品——紧凑摘要无法承载完整记忆关键信息仍应依赖 OmniRoute 的持久记忆/上下文管理能力如 docs/frameworks/MEMORY.md 描述的记忆体系为摘要指定更经济的模型——通过handoffModel用一个廉价模型承担摘要生成避免占用主力模型配额。八、测试佐证与延伸阅读仓库为context-relay提供了完整的测试覆盖可直接用于验证本文描述的行为tests/unit/combo-context-relay.test.ts组合层生成决策的单元测试tests/unit/chat-context-relay.test.tschat 处理器注入逻辑的单元测试tests/integration/combo-matrix/context-relay-handoff.test.ts交接生成与注入的集成测试tests/integration/combo-matrix/context-relay-codex.test.tscodex 配额轮换场景的端到端验证。核心源码入口按阅读顺序建议为src/lib/db/migrations/019_context_handoffs.sql——先看表结构与索引理解持久化边界src/lib/db/contextHandoffs.ts——再看 CRUD 与清理逻辑open-sse/services/contextHandoff.ts——重点研读生成决策maybeGenerateHandoff、摘要解析parseHandoffJSON与注入injectHandoffIntoBodyopen-sse/services/combo.ts——确认生成端的配额读取调用src/sse/handlers/chat.ts——确认注入端真实切换才注入、成功后即删除的完整闭环。原文档英文版本可通过 docs/i18n/cs/docs/features/context-relay.md 顶部的语言切换链接访问对应翻译本文内容即以此文档为骨架、结合上述源码与迁移脚本整理而成。【免费下载链接】OmniRouteNever stop coding. Free MIT AI gateway: one endpoint, 352 providers (150 free), 1200 models Kimi, Claude, GPT, Gemini, GLM, DeepSeek, MiniMax. Works with Claude Code, Codex, Cursor, OpenCode, Cline Copilot. Quota-aware auto-fallback, RTKCaveman compression saves 15-95% tokens, MCP/A2A, Desktop/PWA. Built by 550 contributors项目地址: https://gitcode.com/GitHub_Trending/om/OmniRoute创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考