
OmniRoute Context Relay: Quota-Aware Session Continuity Across Multi-Account Combo Rotation【免费下载链接】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本文是 OmniRoute 中context-relay组合策略combo strategy的技术实战指南。该策略解决的是一个非常具体的分布式幻觉问题当同一个会话session在多个同一 Provider 账户之间轮换、而对话尚未结束时如何把短期对话连续性从旧账户平滑移交到新账户。读完本文你将掌握context-relay的运行时机、阈值逻辑、交接载荷handoff payload格式、完整配置项及其源码级实现原理并能据此为自己的长时编码/研究会话设计出可用的多账户轮换方案。一、Context Relay 是什么在 OmniRoute 的 Combo 体系里组合策略如 priority、round-robin、weighted 等负责在多个 Provider/模型/账户之间选择目标。context-relay是其中一种特殊的组合策略它在账户轮换前预先生成一份紧凑的结构化摘要并在账户真正切换后把这份摘要作为 system message 注入下一次请求从而保住会话连续性。context-relay的行为可以概括为先按优先级路由选择模型再叠加一层交接handoff逻辑其核心三步为提前生成在活跃账户配额耗尽之前OmniRoute 在后台生成一份紧凑的结构化会话摘要切换后注入当认证环节为同一会话选中了另一个账户时OmniRoute 将这份摘要以 system message 形式注入下一次请求消费后清理一旦交接成功被消费对应存储中的摘要即被删除。这套机制的实际运行范围目前以codex配额轮换为中心但配置面handoffProviders等已经模型化为通用能力见下文配置与限制小节。二、何时使用 Context Relay文档给出明确的使用前提——以下条件全部成立时context-relay才有价值该 Combo 预期会在同一 Provider 的多个账户之间轮换丢失短期对话连续性会明显损害任务质量例如多轮编码、连续研究任务Provider 能暴露足够多的配额信息让运行时能够预测账户额度即将耗尽。从源码看第三个前提在 Codex 上已经落地生成侧调用fetchCodexQuota(connectionId)获取quotaInfo.percentUsed并综合quotaInfo.windows?.session?.resetAt、quotaInfo.windows?.weekly?.resetAt、quotaInfo.resetAt三个候选重置时间取最早者作为交接过期时间见 executeTargetAttempt.ts。也就是说如果某个 Provider 无法提供percentUsed这样的配额读数context-relay就无法判断何时该生成摘要。最典型的适用场景是可能跨越单个账户额度窗口的长时编码或研究会话——一个会话的生命周期长于单个账户的可用窗口必须在窗口边界上无缝移交。三、运行时流程三个配额区间 切换后注入context-relay有意地把行为拆分为两个运行时层生成层与注入层并依据配额使用率划分了四个阶段。源码中的两个核心阈值常量定义在 contextHandoff.tsexport const HANDOFF_WARNING_THRESHOLD 0.85; // 警告阈值开始生成摘要 export const HANDOFF_EXHAUSTION_THRESHOLD 0.95; // 硬停止不再安排新摘要配额使用 0% ~ 84%不生成任何交接摘要。请求行为与普通优先级路由完全一致。对应源码判断options.percentUsed relayConfig.handoffThreshold直接返回见maybeGenerateHandoff。配额使用 85% ~ 94%警告窗口如果活跃 Provider 出现在handoffProviders允许列表中OmniRoute 会在账户真正耗尽之前在后台生成结构化交接摘要。这个阶段的关键细节默认警告阈值是0.85HANDOFF_WARNING_THRESHOLD可被配置覆盖需满足0 handoffThreshold 0.95才生效否则回退到 0.85见 contextHandoff.ts生成硬停止线是0.95HANDOFF_EXHAUSTION_THRESHOLD达到或超过该值即不再生成每个sessionId comboName只允许一个在途in-flight摘要生成任务maybeGenerateHandoff会先检查hasActiveHandoff(sessionId, comboName)库里已有未过期交接与inflightHandoffGenerations集合该键已存在生成任务两者任一命中就直接返回避免重复生成见 contextHandoff.ts摘要生成是异步、非阻塞的maybeGenerateHandoff本身同步返回实际生成通过setImmediate调度到后台执行绝不阻塞当前请求的响应路径见 contextHandoff.ts。配额使用 ≥ 95%不再生成新的交接摘要。此时系统已经处于或接近配额耗尽状态运行时避免再为本次会话安排一次额外的摘要请求那会进一步消耗所剩无几的配额。账户轮换之后注入阶段当同一会话的下一次请求经认证解析到不同的已认证账户时OmniRoute 会把已存储的交接摘要前置prepend为一条 system message。注入只发生在真实账户切换已被确认之后——这正是本策略把生成与注入拆到两个运行时层的根本原因详见架构说明。注入侧的核心判断逻辑在 chat.tsif ( comboStrategy context-relay comboName runtimeOptions.sessionId body?._omnirouteSkipContextRelay ! true ) { const handoff getHandoff(runtimeOptions.sessionId, comboName); if (handoff handoff.fromAccount ! credentials.connectionId) { // Inject only after a real account switch. The combo loop itself cannot // reliably detect this because account selection happens inside auth. requestBody injectHandoffIntoBody(requestBody, handoff); injectedHandoff handoff; // log: Injecting handoff for session ... fromAccount - connectionId } }注意两个细节handoff.fromAccount ! credentials.connectionId确保只有真实发生账户切换才注入如果下一次请求仍落在同一账户交接不会被打进去_omnirouteSkipContextRelay标记用于跳过注入——摘要生成请求自身会携带_omnirouteSkipContextRelay: true防止摘要生成请求被再一次注入交接造成递归见 contextHandoff.ts。注入成功后一旦该请求成功完成result.success存储中的交接会被一次性消费删除见 chat.tsif (injectedHandoff runtimeOptions.sessionId comboName) { deleteHandoff(runtimeOptions.sessionId, comboName); }四、交接载荷Handoff Payload持久化字段交接摘要持久化在context_handoffs数据表中HandoffPayload接口定义于 src/lib/db/contextHandoffs.ts包含字段含义sessionId会话标识交接的作用域维度之一comboName组合名称与sessionId共同构成交接作用域fromAccount生成交接时正在使用的账户connectionId用于切换判定summary连续性所需的核心密集摘要keyDecisions关键决策列表taskProgress已完成 / 待办 / 下一步activeEntities活跃实体文件、特性、主题等messageCount参与摘要的消息条数model生成摘要所用模型warningThresholdPct生成时生效的警告阈值generatedAt/expiresAt生成时间 / 过期时间存储层使用ON CONFLICT(session_id, combo_name) DO UPDATE做 upsert保证同一会话组合只保留一份最新交接读取时过滤expires_at now过期记录自动失效见 contextHandoffs.ts。默认 TTL 为 5 小时DEFAULT_TTL_MS 5 * 60 * 60 * 1000见 contextHandoff.ts过期清理有 30 分钟的节流CLEANUP_THROTTLE_MS。摘要模型被要求返回的 JSON摘要模型handoffModel未配置则用当前请求模型被要求严格返回如下结构的 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_TEMPLATE完全一致见 contextHandoff.ts。解析侧parseHandoffJSON做了充分防御自动剥离 Markdown 代码围栏stripMarkdownCodeFence与omniModel标签若整体不是合法 JSON则截取首个{到末尾}之间的内容再解析各字段有硬性上限summary最多 2000 字符、taskProgress最多 1200 字符、keyDecisions最多 8 项、activeEntities最多 10 项且summary缺失则整体判为不可用见 contextHandoff.ts。注入时的context_handoff系统消息注入时OmniRoute 把上述载荷转换为一条context_handoff系统消息让新账户带着正确的本地上下文继续工作buildHandoffSystemMessage见 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_processed42/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转义 避免内容破坏消息结构。针对不同请求协议注入方式也有区分对于 Chat Completions 形态的请求context_handoff作为role: system的首条消息前置对于 Responses API 形态的请求body 中含input/instructions则并入instructions字段见injectHandoffIntoBodycontextHandoff.ts。五、配置项与默认值context-relay支持的配置字段如下文档原文 源码实现配置字段类型默认值说明handoffThresholdnumber0.85摘要生成的警告阈值配额使用比例合法范围(0, 0.95)越界或非数字则回退 0.85handoffModelstring空 使用当前请求模型仅用于摘要生成的模型覆盖handoffProvidersstring[][codex]允许触发摘要生成的 Provider 允许列表显式传空数组会保留为空禁用生成未显式配置时默认[codex]maxMessagesForSummarynumber30参与摘要的最近消息条数上限合法范围[5, 100]越界回退 30relayModeschema-locked \| standardstandard消息选择模式schema-locked只取最近消息且可跳过 system/developer 消息standard始终保留 system/developer 消息关键解析逻辑见resolveContextRelayConfigcontextHandoff.ts与DEFAULT_COMBO_CONFIGcomboConfig.ts。注意handoffThreshold的校验写死为必须小于HANDOFF_EXHAUSTION_THRESHOLD0.95这是出于警告线必须早于硬停止线的一致性约束。配置层级Global → Provider → Combo全局默认值在Settings页面配置Combo 专属值可在Combos页面覆盖全局组合解析遵循Global Defaults → Provider Overrides → Per-Combo Config三层级联、越具体越优先的规则见 comboConfig.ts 的文件头注释。摘要请求的参数细节摘要生成请求本身是一个非流式stream: false的内部请求参数如下见 contextHandoff.tsconst summaryBody { model: summaryModel, // handoffModel 或当前请求模型 messages: [{ role: user, content: summaryPrompt }], stream: false, max_tokens: 800, // DEFAULT_SUMMARY_RESPONSE_TOKENS temperature: 0.1, // 低随机性保证摘要稳定 _omnirouteSkipContextRelay: true, // 防止自身被再次注入交接 _omnirouteInternalRequest: context-handoff, };此外参与摘要的对话历史会被裁剪到约 8000 token 以内MAX_HISTORY_TOKENS_FOR_SUMMARYselectMessagesForSummary先取最近的maxMessagesForSummary条消息若 token 估算仍超限则逐步丢弃更早的消息standard模式下始终保留 system/developer 消息直到满足上限见 contextHandoff.ts。这也解释了文档中摘要是刻意紧凑、基于近期历史的不是完整转录回放的设计。六、架构说明为什么生成与注入要分两层文档明确指出当前实现并不存在一个独立的handleContextRelayCombo处理器。职责被有意拆分到两个运行时层open-sse/services/combo.ts实际调用点在 executeTargetAttempt.ts决定一次成功的轮次是否应当生成交接仅在策略为context-relay、handoffProviders命中且 provider 为codex时拉取配额信息并调用maybeGenerateHandoff见 executeTargetAttempt.tssrc/sse/handlers/chat.ts 只在认证解析出实际用于本次请求的账户之后才注入交接因为账户选择发生在认证内部组合循环combo loop本身无从得知本次请求是停留在同一账户还是真的切了账户见 chat.ts 的注释。这种拆分在当前代码库中是刻意的注入必须以真实账户切换为充分条件而这一信息只有认证层知道。若由组合循环统一处理就无法可靠区分同账户续聊与跨账户移交从而可能把无意义的交接摘要注入到未切换的请求里。顺带一提代码库中还演化出了一套更通用的通用交接universal handoff机制maybeGenerateUniversalHandoff/shouldGenerateUniversalHandoff它不再依赖配额阈值而是基于模型切换previousModel ! currentModel触发受UNIVERSAL_CONTEXT_HANDOFF_ENABLED特性开关与trigger: always | on-switch | on-error控制见 contextHandoff.ts。这属于context-relay的姊妹能力不在本文核心范围。七、源码与测试佐证context-relay的行为有完整的单元测试覆盖核心测试文件为 tests/unit/context-handoff.test.ts其中与本文相关的关键断言包括阈值以下不生成maybeGenerateHandoff skips below the warning threshold——percentUsed: 0.7 0.85时handleSingleModel不应被调用数据库中也无交接记录L133-L153达到阈值后持久化结构化交接maybeGenerateHandoff persists a structured handoff once the threshold is reached——percentUsed: 0.88时生成并保存断言fromAccount、summary、keyDecisions以及摘要请求体携带_omnirouteSkipContextRelay: true与_omnirouteInternalRequest: context-handoffL155-L203同一会话并发在途生成去重maybeGenerateHandoff deduplicates concurrent in-flight generations for the same sessionL205 起配置解析resolveContextRelayConfig preserves explicit empty handoffProviders显式handoffProviders: []与handoffThreshold: 0.9被保留L121-L131JSON 解析健壮性parseHandoffJSON accepts fenced JSON and normalizes fields自动剥离 json 围栏并规范化字段L106-L115以及无效输入返回 null 而不抛异常L117-L119。另一份测试 context-handoff-native-passthrough-shape 对应的回归修复也说明交接注入对原生透传请求形态Responses API 的input/instructions结构有专门的适配逻辑验证见tests/unit/context-handoff-native-passthrough-bug.test.ts。八、限制与边界有效运行时支持目前集中于codex配额轮换生成侧硬编码了provider codex检查与fetchCodexQuota调用executeTargetAttempt.tshandoffProviders已模型化为配置面但真实的摘要生成仍依赖 Provider 专属的配额管道——其他 Provider 若没有等价的percentUsed读数则无法驱动本机制摘要是刻意紧凑、基于近期历史的不是完整转录回放历史裁剪到约 8000 token、最多 30 条消息因此无法替代完整记忆交接按sessionId comboName作用域隔离并自动过期默认 TTL 5 小时若会话始终未切换账户已存储的交接不会被注入fromAccount ! credentials.connectionId不成立也不会被误用。九、推荐使用模式文档给出的最佳实践如下使用同一 Provider 的多个账户这是触发账户轮换的前提在整个会话期间保持sessionId稳定交接的作用域是sessionId comboNamesessionId 不稳定则交接永远匹配不上把handoffThreshold设置得足够早例如 0.8 而非 0.85为后台摘要请求留出余量——记住摘要是异步生成且非阻塞的但阈值太接近 0.95 硬停止线会压缩生成窗口把该特性视为连续性辅助而非持久记忆的替代品它保住的是短期对话上下文不是长期记忆。综合来看context-relay是 OmniRoute 组合路由体系中配额感知的会话交接能力的落地实现由组合层在配额窗口边界提前产出结构化摘要由认证层在确认账户真正切换后注入并消费。理解它的阈值区间、载荷格式与双层架构就能在你自己的多账户 Combo 上正确启用并调优这一连续性保障。【免费下载链接】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),仅供参考