关于 LLM CLI Gateway
LLM CLI Gateway 是供单个用户通过 MCP 操作受支持的本地编码 Agent 和已配置 HTTP API 提供方的控制平面。它结合持久异步任务、工作区限制、审批门槛、会话延续和审查流程,并将提供方凭据保留在用户自有设备或部署卷中。
主要功能
- 01统一的同步与异步 Agent 工具
- 02支持恢复与取消的持久任务
- 03按工作区限制提供方执行
- 04会话跟踪与 CLI 恢复路径
- 05审批门槛与验证回执
- 06工作流技能与确定性计划
适用场景
- 01从 MCP 客户端委派编码任务
- 02让 Agent 在已注册工作区中运行
- 03断连后恢复或收取长时间运行的任务
- 04协调实现与多 Agent 审查
MCP 相关
网关通过 stdio 或配置的远程 Web 连接器提供单一 MCP 端点,让受支持的客户端操作提供方 CLI 和 HTTP API 提供方。它提供同步与异步工具、持久任务、会话和审查工具,以及按工作区限制的执行。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
28收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| focus | string | 否 | 审核者应重点关注的内容。 |
| models | array | 否 | 要咨询的提供方。默认值为 Claude 和 Codex。 |
| question | string | 是 | 要验证的问题或内容。 |
| judgeModel | any | 否 | 可选的提供方,用于运行显式的评审综合任务。 |
详细介绍
llm-cli-gateway
“不先商议,所谋无效;谋士众多,所谋乃成。” — 箴言 15:22(LSB)
面向 AI 编码 Agent 的安全本地控制平面。
llm-cli-gateway 让受支持的 MCP 客户端通过一个由用户掌控的网关操作 Claude Code、Codex、Gemini/Antigravity、Grok Build、Mistral Vibe、Cognition Devin、Cursor Agent 以及已配置的 HTTP API 提供商,同时保留原生 CLI 会话、本地凭据、持久异步任务、验证回执和审查工作流。
开发者尝试它的理由: 使用你当前所在的客户端,将工作委派给本地编码 Agent;将远程执行限制在已注册的工作区;管控高风险操作;在断开连接后继续执行;并收集可审计的审查证据,而无需将这些 Agent 变成通用聊天代理。
当前状态: main 上的 CI 和安全工作流均已通过,OpenSSF Scorecard 已发布,OpenSSF Best Practices 已通过,发行版采用 Sigstore 签名,且该软件包采用 MIT 许可证。
快速开始
npm install -g llm-cli-gateway
也可以在 MCP 客户端中直接使用 npx:
{
"mcpServers": {
"llm-gateway": {
"command": "npx",
"args": ["-y", "llm-cli-gateway"]
}
}
}
当前提供的功能
llm-cli-gateway 是一个面向单用户的 MCP 控制平面,可通过受支持的本地或远程客户端操作 AI 编码 Agent。它不只是一个简单的 CLI 包装器:
- 通过一致的同步和异步 MCP 工具运行已注册的提供商 CLI 和已配置的 HTTP API 提供商。
- 持久化长时间运行的任务,支持在重启后安全地收集结果、去重、取消,以及从同步模式延迟到异步模式。
- 跟踪会话、实际的 CLI 恢复路径、结构化响应元数据和缓存遥测数据。
- 支持支持缓存的
promptParts,包括在选择启用时显式设置 Claudecache_control。 - 可在网关管理的 Git worktree 中运行受支持的提供商请求,用于隔离的多 Agent 审查和实现循环;可使用本地文件型或 PostgreSQL 会话管理器。即使 PostgreSQL 在多个主机间共享会话记录,会话绑定的复用和清理仍仅限于拥有该文件系统工件的主机。删除会话、执行
session_clear_all或发生文件型 TTL 过期后,两种会话管理器都会保留清理失败的记录,供拥有该 worktree 的主机重试。从其他主机处理删除时不会移除任何 worktree,并会保留该记录。数据库端的cleanup_expired_sessions函数会暂存同一条记录,而不是删除带有 worktree 的会话;该函数不会调用网关观察器,因此自身不会尝试移除 worktree。Grok、Devin 和 Mistral 要求为网关 worktree 显式提供原生sessionId;仅使用新建、createNewSession或resumeLatest的 worktree 请求会因无法持久地重新选择该会话而安全失败。物化 worktree 时会禁用仓库级、系统级和全局 Git hook、已配置的 clean、smudge 和 process 检出过滤器、稀疏检出及延迟对象获取。依赖过滤器的内容(例如 Git LFS)会保留为仓库中的表示形式,而不会执行主机命令。 - 提供个人设备的设置界面:支持带 Bearer 令牌身份验证的 HTTP 传输、
doctor --json、设置 UI 工件、提供商设置代码片段、Docker 备用方案和经过验证的发布包。 - 远程 Web 连接器使用 MCP OAuth 发现和授权码流程进行设置,并支持静态客户端或共享密钥门控。客户端密钥在本地生成,仅以哈希形式存储,且只有在显式执行一次性复制命令时才会打印。
- 提供商 CLI 请求可通过
workspace使用别名选择已注册的工作区;每个 HTTP/隧道请求都必须在执行提供商操作前使用已注册的别名、会话工作区或[workspaces].default。本地不受限制的文件系统访问仅适用于 stdio 传输。
工作流资源
该代码库在 .agents/skills 下提供适用于 Agent 的工作流技能,涵盖异步编排、会话连续性、多 LLM 审查、实现-审查-修复循环、回顾性证据梳理、安全审批门控调度,以及 Personal Agent Config Kit 操作。发布的 npm 包中包含 9 个面向调用方的技能:async-job-orchestration、multi-llm-review、session-workflow、secure-orchestration、implement-review-fix、retrospective-walk、public-demo-session、least-cost-routing 和 personal-agent-config-kit。对于需要确定性排序和验证门控的工作流,可在 docs/plans 和 setup/install-plan.dag.toml 下找到机器可读的 DAG-TOML 计划。
无需等待核心 npm 版本发布,即可更新技能包:只需将技能目录放入由本地操作员控制的路径。网关首先加载内置技能,然后加载 [skills].paths,接着加载 LLM_GATEWAY_SKILLS_PATH,最后加载存在时的 ~/.llm-cli-gateway/skills;后加载的根目录会覆盖同名技能。每个技能都位于一个包含 SKILL.md 的目录中。根目录还可以包含 skill-pack.json,用于固定预期的 SKILL.md 哈希值:
[skills]
paths = ["/opt/llm-cli-gateway/skills"]
export LLM_GATEWAY_SKILLS_PATH="/opt/team-skill-pack:/opt/incident-skill-pack"
{
"name": "team-pack",
"version": "1.0.0",
"skills": [
{
"name": "incident-retrospective",
"sha256": "<sha256 of incident-retrospective/SKILL.md>"
}
]
}
加载器有意仅限本地操作:启动时绝不会获取远程 Markdown。要更新技能包,请通过包管理器或部署系统安装或替换文件,然后重启网关,以刷新公布的 skills://... 资源。
接下来的文档重点是为每个出站 CLI 和 API 提供商系列编写特定于提供商的技能和 DAG-TOML 配对:Claude、Codex、Gemini/Antigravity、Grok、Mistral Vibe、Devin、Cursor Agent、OpenAI 兼容端点、Anthropic Messages 和 xAI Responses。实施计划跟踪于 docs/plans/provider-workflow-assets.dag.toml;预期每项提供商资源都涵盖安装/登录检查或令牌环境变量检查、会话行为、审批模式、缓存/遥测界面、故障模式和冒烟测试门控。
信任与供应链
- CI 会运行构建、lint、格式检查、测试、包检查和 npm audit。
- 安全 CI 会运行 actionlint、zizmor、shellcheck、typos、osv-scanner、gitleaks 和 lychee。
- GitHub 发布安装程序构件会进行校验和检查,并使用 Sigstore 无密钥签名。
- npm 发布会使用生成的、仅包含生产依赖的 shrinkwrap 文件,并进行发布安全审计;GitHub Actions Trusted Publishing 会将作业的 OIDC 身份交换为短期 npm 发布凭据。
- npm 包有意附带生成的、仅包含生产依赖的
npm-shrinkwrap.json,以确保从注册表安装时解析到经过审计的发布依赖树。发布门控会从package-lock.json重新生成该文件、比较两者是否一致,并在发布前运行一次符合注册表实际情况的使用方安装。 - Socket 行为警报记录在
socket.yml和下文的“安全注意事项”中。对于这款 CLI 设备,shellAccess和shrinkwrap是经过审查的包功能/配置,并非隐藏的安装行为。
个人 MCP 设备
个人设备契约有意将这一范围保持得很窄:一名可信用户在自己拥有的机器或卷上运行网关,连接一个 MCP 端点,并让受支持的客户端通过限定工作区、经审批门控且可审计的请求,操作本地编码 Agent。 产品契约记录在 docs/personal-mcp/PRODUCT_CONTRACT.md 中。它定义了单用户适用范围、安全态势、目标支持矩阵和提供商支持验证门槛。在相应的提供商/客户端路径通过验证之前,公开设置指南不得宣称支持 ChatGPT、Claude web、Claude Desktop、Codex、Gemini CLI、Gemini web 或 Grok 的入站请求。
本项目不提供托管式多租户凭证保管服务。提供商凭证保存在用户的机器或用户拥有的部署卷中。
对于在多台工作站和多个代码库上工作的单个开发者,Personal Agent Config Kit 指南介绍了通过 Git 同步的个人基线、代码库叠加配置、不可变上下文戳记,以及确保工作站间提供商连续性的模型。基线目录必须位于本地 home 目录下、且不经过符号链接的后代目录中;每次发布或同步时,都会重新验证其配置的 origin 获取和推送 URL 是否符合仅限 HTTPS/SSH 的策略。Kit 仅供本地调用者使用;Kit 提供商执行以及对未获准尝试的恢复,都需要健康且持久的 SQLite 或 PostgreSQL 异步任务准入。Kit 模式支持 Claude、Codex 和 Mistral;启用时,其他所有提供商都会安全地拒绝请求。Kit 的作用范围绝不会继承网关进程的 cwd:Claude 和 Mistral 需要注册的工作区选择或配置的默认值,而 Codex 还可以使用显式的绝对 workingDir。相对路径形式的 Kit workingDir 会在检查文件系统或 Git 之前被拒绝。启用时,Kit 会禁用跨模型验证工具;它不是 HTTP/OAuth 配置功能,也不是远程执行功能。
对于非 Kit 的 Claude MCP 请求配置(其保留期限已固定),请遵循本地专用的同主机恢复流程。该流程不接受 SQL 或任意路径覆盖。有效的恢复调用会输出 JSON,成功后以 0 退出;安全拒绝时以 2 退出;用法无效或持久存储不可用时以 1 退出。
发布就绪历史记录在 docs/personal-mcp/RELEASE_READINESS.md 中。试用验证证据(哪些目标大模型指导了设置、记录了哪些不安全建议,以及哪些发现从最初的个人设备式部署中延后处理)位于 docs/personal-mcp/DOGFOODING_RESULTS.md 中。
当前的个人设备式部署产物包括:
- Streamable HTTP 启动:
LLM_GATEWAY_AUTH_TOKEN=<token> npm run start:http - 机器可读的诊断信息:
npm run doctor - Go 引导程序:
installer/,包含setup、doctor --json、start、stop、status、repair、upgrade、uninstall、print-client-config和经过验证的捆绑包下载命令。 - 发布打包:在公共镜像中,发布工作流会在 GitHub 托管的
ubuntu-latest上构建 Linux 二进制文件,并在 GitHub 托管的平台运行器上构建 Windows/macOS 二进制文件。私有上游使用其内部自托管的 Linux 运行器。每次发布都会提供带校验和的平台捆绑包,其中包含网关、生产依赖项和受管理的 Node 运行时;参见 installer/packaging/README.md。 - Docker Compose 备用方案:docker/personal.compose.yml + docker/Dockerfile.personal,适用于已自行管理容器的用户。
- 本地设置界面制品:setup/ui/index.html
- 提供商设置片段:setup/providers/
- 交叉验证工具:
review_changes、validate_with_models、second_opinion、compare_answers、red_team_review、consensus_check、ask_model、synthesize_validation、job_status、job_result和validation_receipt(以及validation-receipt://{validationId}资源)。review_changes和持久化回执需要 SQLite 或 PostgreSQL 验证运行存储,在 Personal Agent Config Kit 模式下不可用。
安装 / 升级 / 卸载(单个二进制文件)
Windows PowerShell:
$Version = '<version>'
$Base = "https://github.com/verivus-oss/llm-cli-gateway/releases/download/v$Version"
$InstallDir = Join-Path (Join-Path $env:LOCALAPPDATA 'Programs') 'llm-cli-gateway'
$ExeName = "llm-cli-gateway-$Version-windows-amd64.exe"
$BundleName = "llm-cli-gateway-bundle-$Version-windows-amd64.tar.gz"
$Exe = Join-Path $InstallDir 'llm-cli-gateway.exe'
$Checksums = Join-Path $InstallDir 'SHA256SUMS'
$ChecksumBundle = Join-Path $InstallDir 'SHA256SUMS.sigstore.json'
New-Item -ItemType Directory -Force $InstallDir | Out-Null
Invoke-WebRequest -UseBasicParsing "$Base/$ExeName" -OutFile $Exe
Invoke-WebRequest -UseBasicParsing "$Base/SHA256SUMS" -OutFile $Checksums
Invoke-WebRequest -UseBasicParsing "$Base/SHA256SUMS.sigstore.json" -OutFile $ChecksumBundle
cosign verify-blob $Checksums --bundle $ChecksumBundle --certificate-identity "https://github.com/verivus-oss/llm-cli-gateway/.github/workflows/release-installer.yml@refs/tags/v$Version" --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
if ($LASTEXITCODE -ne 0) { throw "Sigstore verification failed for SHA256SUMS" }
function Get-ReleaseSha256($Name) {
$line = Select-String -Path $Checksums -Pattern "^[a-fA-F0-9]{64}\s+$([regex]::Escape($Name))$" | Select-Object -First 1
if (-not $line) { throw "No SHA256SUMS entry found for $Name" }
return (($line.Line -split "\s+")[0]).ToLowerInvariant()
}
if ((Get-FileHash $Exe -Algorithm SHA256).Hash.ToLowerInvariant() -ne (Get-ReleaseSha256 $ExeName)) { throw "Checksum mismatch for $ExeName" }
$env:RVWR_GATEWAY_BUNDLE_URL = "$Base/$BundleName"
$env:RVWR_GATEWAY_BUNDLE_SHA256 = Get-ReleaseSha256 $BundleName
& $Exe setup
& $Exe stop
& $Exe install-bundle
& $Exe start
& $Exe status
& $Exe doctor
Windows 安装程序会在
%LOCALAPPDATA%\Programs\llm-cli-gateway 中保留稳定的 llm-cli-gateway.exe 命令,并将该目录添加到用户
PATH。安装后请勿在脚本中使用带发布版本号的 exe 名称。
# After downloading the binary that matches your OS/arch from a release:
cosign verify-blob SHA256SUMS --bundle SHA256SUMS.sigstore.json \
--certificate-identity "https://github.com/verivus-oss/llm-cli-gateway/.github/workflows/release-installer.yml@refs/tags/v<version>" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
sha256sum --check SHA256SUMS # verify before run (or `shasum -a 256 --check` on macOS)
chmod +x llm-cli-gateway-<ver>-<os>-<arch>
./llm-cli-gateway-<ver>-<os>-<arch> setup
./llm-cli-gateway-<ver>-<os>-<arch> install-bundle # uses the platform bundle URL/SHA256
./llm-cli-gateway-<ver>-<os>-<arch> start
./llm-cli-gateway-<ver>-<os>-<arch> doctor
# Upgrade: replace the binary, set the new bundle env vars, run upgrade.
./llm-cli-gateway-<new>-<os>-<arch> upgrade
# Uninstall: dry-run first, then run with --yes.
./llm-cli-gateway-<ver>-<os>-<arch> uninstall
./llm-cli-gateway-<ver>-<os>-<arch> uninstall --yes
Docker 备用方案:
LLM_GATEWAY_AUTH_TOKEN=$(openssl rand -hex 32) \
docker compose -f docker/personal.compose.yml up -d
docker compose -f docker/personal.compose.yml run --rm doctor
功能
核心能力
- 多大模型编排:为 Claude Code、Codex、Gemini、Grok、Mistral (Vibe)、Devin 和 Cursor Agent 命令行界面提供统一接口
- 会话管理:通过持久化存储跟踪网关会话元数据和特定提供方的连续性
- 由网关管理的工作树:在托管的 git 工作树中运行受支持的同步或异步提供方请求,可使用本地文件存储或 PostgreSQL 会话管理器。同一会话复用需要由同一主机持久拥有,同时存在匹配的 Git 注册和网关分支;管理器级别的命名路径冲突会安全失败。共享的 PostgreSQL 会话行不会将其文件系统本地工作树的所有权转移给另一台主机。Grok、Devin 和 Mistral 必须显式提供原生的
sessionId;仅使用全新会话、createNewSession或resumeLatest的工作树请求会安全失败。工作树需要一个已注册的工作区,该工作区必须通过显式选择、调用方拥有的会话元数据或配置的默认值来选定;工作树不会继承进程 cwd,也不能与workingDir、addDir或includeDirs组合使用。物化工作树时会停用仓库、系统和全局 Git 钩子,配置的 clean、smudge 和进程检出过滤器、稀疏检出以及延迟对象获取。依赖过滤器的内容(例如 Git LFS)会保留其仓库表示形式,而不会执行主机命令。在任一会话管理器下,删除会话、调用session_clear_all以及基于文件的 TTL 淘汰,都会在清理期间隐藏由网关持久拥有的工作树会话。如果 Git 移除失败,管理器会保留持久的待清理墓碑,阻止复用,并在所属主机上注册管理器时重试清理。只有 Git 不再注册该工作树后才会最终完成记录;系统会回读 Git 状态,而不会根据记录路径不存在就推断已移除:已移动的工作树,或无法读取其所有者标记的工作树,都不算已移除。由另一台主机处理删除时,不会移除任何工作树,并会保留所属主机上的记录。数据库端的cleanup_expired_sessions函数会暂存相同的墓碑,而不是删除包含工作树的会话;它不会调用任何网关观察器,因此自身不会尝试移除工作树。墓碑不受保留期限限制。 - 令牌优化:可选择通过
optimizePrompt/optimizeResponse压缩提示词和响应,两者默认均为 false。44% / 37% 是在特定语料上测得的 v1.0.0 数据,并非保证值:src/optimizer.ts是一个确定性的短语删减器,运行时不会测量压缩率。 - 关联 ID 跟踪:全面追踪所有大模型交互中的请求
- 跨工具协作:大模型可通过 MCP 相互调用(已通过自用测试验证)
可观测性
- 持久化飞行记录器:提供方请求和响应遵循
[persistence].backend:SQLite 默认写入~/.llm-cli-gateway/logs.db,而 PostgreSQL 会与其他持久化子系统一起写入配置的数据库。PostgreSQL 故障会导致记录器不可用或降级,并在健康状态页面上以通用方式报告;它绝不会回退到 SQLite。更改后端时,不会迁移现有记录,也不会同时读取两个后端。记录包含关联 ID、耗时,以及提供方会发送的令牌用量(目前包括:使用stream-json/json的 claude、codex 和 API 提供方;grok、gemini、mistral、devin 和 cursor 的 CLI 协议不会发送用量信息)。有两项例外值得注意:跨 LLM 验证席位完全不会写入飞行记录器记录,因此llm_request_result无法通过关联 ID 读取记录;此外,按设计,仓库审查席位也被排除在外,因此审查证据不会保留在无过期时间的表中。retry_count和circuit_breaker_state列在异步路径(即生产路径)上都是常量;重试和断路器仅适用于直接执行回退路径。请通过llm_request_list和llm_request_result读取历史记录,以确保按主体进行的所有权检查仍然生效。对于需要人工浏览的 SQLite 部署:datasette ~/.llm-cli-gateway/logs.db - 结构化元数据:工具响应包含机器可读的
structuredContent(model、cli、correlationId、sessionId、durationMs、令牌计数) - 缓存可观测性资源:
cache-state://global、cache-state://session/{id}和cache-state://prefix/{hash}这些 MCP 资源会返回缓存命中/未命中/节省量汇总——仅包含令牌和哈希,不含提示词文本。如果会话有先前的请求,session_get会包含一个cacheState块。 - 提供方能力清单:
provider_tool_capabilities和provider-tools://catalog会公开网关请求字段、受支持/已降级的提供方控制项、本地技能/工具发现信息,以及针对 Claude Code、Codex CLI、Gemini/Antigravity、Grok CLI/API、Mistral Vibe、Cognition Devin 和 Cursor Agent 的安全配置面提示。doctor --json会提供精简的provider_capabilities摘要,供设置助手使用。
缓存感知操作
除 devin_request / devin_request_async 和 cursor_request / cursor_request_async 外,每个 *_request 和 *_request_async 工具都接受可选的 promptParts 字段,用于构建提示词结构,以提高缓存命中率(Devin 和 Cursor 的无头模式仅接受纯 prompt)。网关按规范顺序(system → tools → context → task)拼接各部分,使稳定前缀字节位于易变的任务尾部之前,并在每次调用中保持不变,从而让各提供商的自动提示词缓存每次都命中相同的内容哈希。
{
"promptParts": {
"system": "You are a helpful code reviewer.",
"tools": "You have access to Read, Grep, Bash.",
"context": "<long stable context block — file dumps, etc.>",
"task": "Review the changes in src/foo.ts for security issues."
}
}
prompt 和 promptParts 不能同时使用——请且只传入其中一个。
各 CLI 的能力矩阵(除没有 promptParts 接口的 Devin 和 Cursor 外,所有提供商都会通过 promptParts 自动遵守前缀规范;显式控制项因提供商而异):
| CLI | 前缀规范 | 显式控制项 |
|---|---|---|
| claude | 是 | promptParts.cacheControl + outputFormat: "stream-json"(在稳定块上设置 Anthropic cache_control 断点;强制 ttl="1h") |
| codex | 是 | 无(OpenAI 隐式处理) |
| gemini | 是 | 无(服务端隐式处理) |
| grok | 是 | compactionMode / compactionDetail(上下文压缩:`summary |
| mistral | 是 | 无(隐式处理) |
| devin | 否 | 仅纯 prompt |
| cursor | 否 | 仅纯 prompt |
Claude 示例(显式设置 cacheControl)
claude_request({
promptParts: {
system: "You are a helpful code reviewer.",
context: "<long stable file dump>",
task: "Review the diff.",
cacheControl: { system: true, context: true }, // task is never marked
},
outputFormat: "stream-json",
});
网关通过 stream-json 标准输入路径输出内容,且仅在标记的块上设置 cache_control: {type:"ephemeral", ttl:"1h"}。
Grok 示例(压缩)
grok_request({
promptParts: { system: "...", context: "...", task: "..." },
compactionMode: "segments",
compactionDetail: "balanced",
});
输出 --compaction-mode segments --compaction-detail balanced。
完整接口、遥测差异(例如 Grok -p 与 ACP)、确切的 stream-json 负载结构,以及跨大模型评审说明,请参阅 docs/personal-mcp/PROVIDER_CACHE_SURFACES.md。
选择启用的标志(默认均关闭)位于 ~/.llm-cli-gateway/config.toml 的 [cache_awareness] 下。
可靠性与性能
- 重试逻辑:对瞬时故障采用指数退避和断路器
- 原子文件写入:使用进程专属临时文件和 fsync,确保数据完整性
- 主机保护背压:有界 HTTP 会话生命周期(最大会话数 + 空闲回收器)、全局及按提供商设置的作业执行限制(带有界 FIFO 队列),以及可配置的单作业输出上限(默认 50MB)。请参阅主机保护限制。
- NVM 路径缓存:消除每次请求中的 I/O 开销
- 长时间运行的作业:通过
*_request_async和轮询工具实现不受时间限制的异步执行
安全性与质量
- 全面测试:1,700 多项测试,涵盖单元、集成和回归场景,并通过真实 CLI 执行
- 输入验证:Zod 架构可防止注入攻击
- 杜绝密钥泄漏:仅使用通用会话描述(文件权限为 0o600)
- 杜绝 ReDoS:有界正则表达式模式可防止灾难性回溯
- 类型安全:采用严格的 TypeScript,并进行全面的错误处理
- 供应链加固:专用的
.github/workflows/security.yml会在每次推送和 PR 时运行 actionlint、zizmor、shellcheck、typos、osv-scanner、gitleaks 和 lychee(威胁模型请参阅SECURITY.md)
提供商能力范围
每个提供商都可通过相同的请求、会话、任务和验证机制访问,但底层 CLI 原生支持的功能各不相同。该表记录了各提供商实际发布的功能;可在运行时通过 provider_tool_capabilities、list_models 和 provider-acp://<provider> / provider-tools://<provider> 资源发现当前可用的功能。
| 提供商 | CLI 请求工具 | 原生 ACP | 实时模型发现 | 管理界面 |
|---|---|---|---|---|
Claude Code(claude) | claude_request / _async | 无(以 CLI 为先;其受跟踪版本没有 ACP 入口点) | 模型别名、推理力度级别、回退模型 | 通过 provider_admin_list / provider_admin_run 只读 |
OpenAI Codex(codex) | codex_request / _async、codex_fork_session | 无(codex-cli 声明支持 mcp-server / app-server 传输,而非原生 ACP) | codex debug models | 通过 provider_admin_list / provider_admin_run 只读 |
Gemini / Antigravity(gemini、agy) | gemini_request / _async | 无(agy 未提供 ACP 入口点;旧版 Gemini CLI 的 ACP 证据不适用) | agy models | 通过 provider_admin_list / provider_admin_run 只读 |
xAI Grok(grok) | grok_request(同步 transport: "acp")/ _async | 原生支持,使用 grok agent stdio | grok models + ~/.grok/config.toml | 通过 provider_admin_list / provider_admin_run 只读 |
Mistral Vibe(mistral) | mistral_request(同步 transport: "acp")/ _async | 原生支持,使用 vibe-acp | Vibe 配置,以及 VIBE_ACTIVE_MODEL 活动模型和 Agent 配置文件 | 通过 provider_admin_list / provider_admin_run 只读 |
Cognition Devin(devin) | devin_request(同步 transport: "acp",agentType: summarizer|review)/ _async | 原生支持,使用 devin acp | --model / DEVIN_MODEL | 通过 provider_admin_list / provider_admin_run 只读 |
Cursor Agent(cursor) | cursor_request(同步 transport: "acp")/ _async | 原生支持,使用 cursor-agent acp(由配套组件提供) | 模型别名 | 通过 provider_admin_list / provider_admin_run 只读 |
- 如实报告原生 ACP 支持情况。
grok、mistral、devin和cursor提供原生 ACP 入口点,因此provider-acp://<provider>会携带协商后的initialize能力集和派生出的会话方法可用性;同步*_request接受transport: "acp"(除非设置了[acp]和提供商的runtime_enabled门控,否则会安全失败)。ACP 路由仅支持同步调用:*_request_async变体始终运行 CLI 传输,且不接受transport: "acp"(也不接受 Devin 的agentType);异步 ACP 对等支持将在后续阶段实现。ACP 工作区由网关负责选择:显式指定的 ACPworkspace必须是已注册别名。新的远程 ACP 请求使用该别名或[workspaces].default;远程续接请求固定使用记录中的规范别名和 cwd,其他工作区或未绑定的工作区都会被拒绝。本地 ACP 可以省略workspace;每个未指定工作区的进程都会获得一个全新的私有0o700中性目录,并在进程退出后删除,绝不会使用可预测的共享临时路径。目标 CLI 版本中的claude、codex和gemini没有原生 ACP 入口点;它们的provider-acp://记录会报告native: false且不含方法,不会将适配器伪装成原生支持,也不会提供transport: "acp"选择器。 - 目前仅 Claude 支持托管审批。 只有 Claude CLI 适配器可以执行
approvalStrategy:"mcp_managed";它会使用按请求生成的 MCP 配置启动 Claude,并附带--strict-mcp-config。此模式只允许已配置且由网关管理的 MCP 定义,并拒绝动态npx、环境PATH和 Codex 配置覆盖。Codex、Gemini、Grok、Mistral、Devin 和 Cursor 会在启动提供商前拒绝mcp_managed,因为它们当前的适配器无法隔离环境中的 MCP 配置。对于这些适配器,请使用approvalStrategy:"legacy";approvalPolicy不起作用。 - ACP 有自己的权限桥接机制。 选择
transport:"acp"时,系统会拒绝approvalStrategy:"mcp_managed"和任何approvalPolicy。如需托管审批,请使用 Claude CLI 传输。ACP 主机服务会安全失败,并由 Agent 声明的工具调用类别控制:除非设置了allow_write_host_services,否则拒绝write;除非设置了allow_terminal_host_services,否则拒绝execute;所有其他类别一律拒绝,包括fetch和每种无法识别的类型,因此上游新增的工具类型不会被默认自动批准。审批只能通过选择 Agent 提供的一次性允许选项来表达;如果 Agent 只提供持久的allow_always授权,则会被拒绝,因此 ACP 绝不会将原始 CLI 绕过输入映射为长期权限授权。请注意,此边界就是类别门控:它限制 Agent 可以要求网关执行的操作,而非 Agent 在自身进程中的行为。 - 资源为每个 CLI 提供商根据提供商注册表生成:
models://<provider>、sessions://<provider>、provider-acp://<provider>、provider-tools://<provider>和provider-subcommands://<provider>。 - 模型发现是实时且感知账户的:上述发现功能会访问
models://<provider>和list_models;如果无法进行实时探测,则会退回到静态注册表信息(读取资源不会启动 CLI)。 - 管理功能由发现结果驱动,且会对输出进行脱敏。对于所有提供商,
provider_admin_list和provider_admin_run均为只读操作。状态变更类管理操作仅通过provider_admin_mutate提供,并受[admin] allow_mutating_cli_admin_ops、远程cli:admin作用域、审批门控和审计记录的控制。变更 ACP 会话的操作同样受[acp] allow_mutating_session_ops控制。 - 验证命令适用于所有提供商:
review_changes、validate_with_models、second_opinion、compare_answers、red_team_review、consensus_check、ask_model和synthesize_validation;不可变回执采用规范化哈希,可通过validation_receipt和validation-receipt://{validationId}资源获取。review_changes会捕获完整且经过哈希处理的 Git 工件,并启动绑定到仓库的只读审查器。
前提条件
需要 Node.js >= 24.4.0(package.json 中的 engines.node)。SQLite 后端使用 Node 内置的 node:sqlite 模块,因此无需编译原生绑定,也不会运行安装脚本。24.4 是 allowBareNamedParameters 默认值变为 true 的最低版本,SQLite 持久化层依赖这一默认值。
运行源代码树发布审计也需要 Bash 和来自 util-linux 的 flock。此要求已在提交
242e7669565ecc7c71183f5f7133791a1384ca7c 上验证。发布自动化会在 Ubuntu 上运行审计。在 macOS 上,运行
npm run security:audit 或完整的 npm run check 门禁前,请先安装兼容的
flock。
使用此网关前,需要先安装你想使用的 CLI 工具:
Claude Code CLI
# Installation instructions for Claude Code
# Visit: https://docs.anthropic.com/claude-code
npm install -g @anthropic-ai/claude-code
Codex CLI
npm install -g @openai/codex
codex login
Gemini(Google Antigravity CLI)
Gemini 提供方通过 Google Antigravity CLI(agy)运行。
curl -fsSL https://antigravity.google/cli/install.sh | bash
# Docs: https://antigravity.google/docs
Grok Build CLI(xAI)
curl -fsSL https://x.ai/cli/install.sh | bash
grok login # OAuth flow; for headless auth, set XAI_API_KEY
# Docs: https://docs.x.ai/build/overview
Mistral Vibe CLI
# Pick one — the gateway's cli_upgrade auto-detects which one you used.
curl -LsSf https://mistral.ai/vibe/install.sh | bash
pip install mistral-vibe
uv tool install mistral-vibe
brew install mistral-vibe
vibe --setup
# Complete the API-key setup locally. Do not paste the key into a chat.
# Current Vibe defaults session logging to enabled. If an older config disabled it,
# edit ~/.vibe/config.toml and set:
# [session_logging]
# enabled = true
Vibe 专属说明:
- 模型选择通过
VIBE_ACTIVE_MODEL环境变量进行 — Vibe 没有--model标志。网关会发现~/.vibe/config.toml/VIBE_MODELS,仅在明确请求模型或需要恢复 Vibe 配置时注入VIBE_ACTIVE_MODEL,并在模型未找到时刷新发现结果后重试一次。 permissionMode是 Vibe 的--agent名称。 内置选项包括default | plan | accept-edits | auto-approve;Vibe 也接受需要安装后才能使用的 内置选项(例如lean)以及来自~/.vibe/agents的自定义 Agent。请求会将 所选名称传递给 Vibe 进行验证。Vibe 不支持mcp_managed。- 工具控制使用 Vibe 的原生标志。 网关会为每个
allowedTools条目生成一个--enabled-tools <tool>标志,并为每个disallowedTools条目生成一个--disabled-tools <tool>标志。Vibe 会在启用工具筛选后应用禁用工具设置。 - 使用情况遥测尽力提供。 Vibe 不会在程序化 stdout 中输出令牌或费用数据。
当网关知道 Vibe 的原生会话 UUID 时,会读取
~/.vibe/logs/session/session_<...>/meta.json获取使用情况和费用。 如果会话日志缺失、格式错误或尚不可知,这些字段将留空。 - 不支持自我更新:
cli_upgrade --cli mistral会检测你使用的是 pip / uv / brew,并调用相应的升级命令。不存在vibe update这种命令。
安装
作为 MCP 服务(npm)
npm install -g llm-cli-gateway
或者直接使用 npx:
{
"mcpServers": {
"llm-gateway": {
"command": "npx",
"args": ["-y", "llm-cli-gateway"]
}
}
}
从源代码安装
git clone https://github.com/verivus-oss/llm-cli-gateway.git
cd llm-cli-gateway
npm install
npm run build
使用方法
作为 MCP 服务
对于已支持本地 stdio MCP 服务的客户端,请添加如下配置:
{
"mcpServers": {
"llm-cli-gateway": {
"command": "node",
"args": ["/path/to/llm-cli-gateway/dist/index.js"]
}
}
}
对于不受限制的本机开发访问,推荐使用 stdio 路径。HTTP MCP(包括 localhost HTTP 和隧道 HTTPS)在提供方执行方面被视为支持远程访问:启动 CLI 前,提供方工具必须解析出已注册的工作区别名、会话工作区或 [workspaces].default。远程客户端应传入所选工作区内的相对 workingDir、addDir 和包含目录值,并且只能恢复其拥有的、由网关跟踪的会话。原生提供方会话 ID 仅限本地使用。禁用身份验证或使用无身份验证的连接器路径,并不意味着可以绕过文件系统限制。
对于没有已解析 workingDir、已注册 workspace 或网关管理的 worktree 的本地 CLI 请求,子进程会在全新的私有 0o700 临时目录中运行,并在进程退出后删除该目录。它绝不会继承网关仓库的 cwd 或其提供方原生指令上下文。网关会规范化临时根目录;如果其任一祖先目录包含 .git、AGENTS.md、AGENTS.override.md、Agents.md、AGENT.md、CLAUDE.md、Claude.md、CLAUDE.local.md、.claude/CLAUDE.md、.claude/rules/、.cursor/rules/、.cursorrules、GEMINI.md 或 .vibe/config.toml,则会拒绝使用该目录或将其移至其他位置;通过符号链接或自定义 TMPDIR 指向该上下文的情况也包括在内。此列表涵盖提供方从其 cwd 向上遍历时会发现的条目。特意不包括用户级配置,例如 ~/.claude/settings.json:无论 cwd 为何,这些配置都会在每次调用时加载,因此移动工作区无法将其隔离。
因此,使用 cwd 作用域的最新会话指针的提供方原生 resumeLatest 操作,必须指定 workingDir、workspace 或已配置的默认工作区;如果这些都不可用,则会安全失败。当多个仓库同时处于活动状态时,请显式选择目标。
CLI 请求架构允许最多 100,000 个字符的提示词,但操作系统还会限制单个 argv 元素的字节数。Codex 新建和恢复会话的请求会通过 stdin 传输完整提示词。codex_fork_session 仍受 argv 限制;如果 UTF-8 提示词过大,则会在启动进程前将其拒绝,并返回不可重试的 input_too_large。当前 CLI 契约要求通过 argv 传递提示词的其他提供方也使用相同的准入规则。最终编码后的所有其他调用方控制的 argv 值也会进行检查,包括序列化后的 Agent/架构 JSON、合并后的工具列表、指令覆盖项、路径、模型名称和原生会话 ID。最终启动边界会检查每个 argv 元素,以及解析后的命令行总长度,并采用保守的平台专用字节预算和 2,048 个元素上限。总字节预算不包括环境变量,但会为其预留空间;在 Windows 上,预解析准入会先按较小的 npm .cmd/.bat 包装程序限制进行,直到命令解析确认使用的是原生可执行文件。非 Kit 请求的原生会话和恢复标志,会在工作区、会话、提供方工件传递或持久化作业产生任何副作用之前计入检查。Claude Kit 会在生成编译后的上下文工件或分配持久化 Kit 会话之前,先预估最终 argv。
如果命令或任何 argv 元素中包含 NUL 字节,会在启动进程前将其拒绝,并返回不可重试的 invalid_input。面向调用方的结果、长期作业内存、持久化作业参数和异步执行记录都会使用固定的无效 argv 标记;不会保留可选的重复持久化载荷,也不会保留被拒绝的向量或 Node 会回显值的原生错误。
原生 E2BIG 错误(包括由环境变量导致的失败)会经过规范化处理,不会保留原生 spawnargs。网关绝不会为了适配限制而截断指令或其他值。对于由 stdin 提供数据的请求,只有在完整载荷的写入回调成功后,才会接受提供方的正常退出。管道关闭或仍未完成时,会返回固定且不含敏感信息的传输不完整失败;超时、取消和提供方非零退出仍具有优先效力。
此通用 stdio 示例不能证明对 Personal MCP Appliance 的提供方支持情况。ChatGPT、Claude web、Claude Desktop、Codex、Gemini CLI、Gemini web 和 Grok 的客户端专用设置指南,仍受 docs/personal-mcp/PRODUCT_CONTRACT.md 中提供方支持矩阵的限制。
可用工具
跨大模型验证工具
个人设备接口为非开发者客户端提供简化的验证工具。这些工具通过持久化异步作业管理器启动提供方 CLI 作业,并返回标准化的提供方状态以及原始作业引用。
validate_with_models:请两个或更多提供方独立验证一个问题。review_changes:捕获一份完整的 Git 审查产物,将仓库内容标记为不可信数据,并启动只读的独立审查。请参阅仓库变更审查。second_opinion:请一个提供方审查一份答案。red_team_review:针对一个计划、答案或文档,找出风险和失效模式。consensus_check:检查各提供方是否认同某个主张。ask_model:通过简化接口向一个提供方提问。synthesize_validation:在收集提供方结果后,运行一个显式指定的评审模型。常规验证要求调用方提供问题和已完成的标准化结果。review_changes运行则会通过validationId重新加载其确切的、归属自身的持久化结果;调用方提供的问题和结果会被忽略。list_available_models:列出各提供方 CLI 通过简化接口提供的模型。job_status和job_result:轮询并收集验证作业输出。validation_receipt:通过validationId获取终结的跨大模型验证运行的规范哈希不可变回执(返回minted | pending | verification_failed | expired_unminted | not_found,仅返回自身回执,否则返回未找到)。verification_failed表示已存储的回执与其持久化运行不一致,这是需要调查的缺陷;expired_unminted始终仅表示不存在回执。format: "markdown"会渲染易读报告;如果关联作业仍能提供已验证身份的输出,includeRawResponses会内嵌完整的提供方答案文本。仅当附加的作业存储提供持久化验证运行存储能力(sqlite和postgres)时才会注册。
同一回执也会作为 validation-receipt://{validationId} MCP 资源提供(具有相同的持久化门控和仅返回自身回执的所有者范围限制)。
验证报告会保留各提供方之间的分歧。可选的评审综合结果会明确指出哪个提供方生成了评审作业。
仓库变更审查
review_changes 接受绝对路径的本地 workingDir 或已注册的
workspace,然后解析 scope: "auto" | "uncommitted" | "branch" | "commit"。它可以接受显式的 Git base、字面量仓库相对路径
paths、stance: "standard" | "adversarial"、审查者 models、可选的
judgeModel、trustCursorWorkspace,以及失败时关闭的产物/提示词字节
上限。
Cursor 拒绝审查它不信任的目录,因此只有当待审查目录是已注册的 [[workspaces.repos]] 路径,且其 providers 包含 cursor 时,才会为 cursor 席位授予 --trust(位于某个网关工作树下的目录也算,且由最近的上级注册项决定)。对于其他目录,系统会跳过 cursor 席位并给出可执行的原因,其他审查人员仍会继续工作。trustCursorWorkspace: true 表示接受在一次持久化审查运行中授予信任。此授权适用于该次运行审查人员名单中的 Cursor 席位,也适用于稍后 synthesize_validation 启动的计划内 Cursor 裁判。授权绑定到运行所有者、仓库和计划内裁判,因此其他主体或审查运行无法重放该授权。这是一个真正的决定,而非形式流程:受信任的文件夹也正是 cursor 加载项目规则和 AGENTS.md 的位置,因此被审查的仓库会对自己的审查者产生一定影响,而这正是围栏式审查提示词原本禁止的。生成的产物会分别保留已提交、已暂存、未暂存以及未被常规忽略规则忽略的未跟踪文件证据。即使仓库内的属性将已跟踪文件标记为不可生成差异,它也会强制保留这些文件的可读差异。review-evidence.v2 产物会分别公开 committedPatch、stagedPatch 和 unstagedPatch;每个片段都包含排序后的路径清单、编码、精确字节长度、SHA-256 标识和内容。这样可以防止索引中的更改与仅存在于工作树中的反向更改相互抵消。该产物设有冲突防护,按字节计数,以 SHA-256 标识,经过竞态检查,且绝不截断。在 auto 模式下,分支发生分叉时会从合并基点进行审查,并纳入工作树证据。否则,脏工作树会选择未提交的更改,而干净工作树则回退到最近一次提交(HEAD^..HEAD),且不包含工作树证据。遇到不安全的未跟踪文件类型,或采集期间仓库发生变更时,系统会拒绝继续。
该工具会启动异步提供方任务,并返回 validationId、精确的产物和提示词标识、文件清单,以及每位审查人员对应的一个 rawJobReference。使用验证的 job_status 轮询这些引用,并使用验证的 job_result 收集结果,而不要使用名称相似的 llm_job_* 工具。如果请求了裁判,请等待每位审查人员都进入终态,然后使用 validationId 和相同的 workingDir 或 workspace 选择器调用 synthesize_validation。继续收集结果以查看进度并供人查看,但不要将其作为审查证据传入:对于 review_changes 运行,网关会忽略调用方提供的 question 和 providerResults,重新加载确切的、由当前运行所有者拥有且已关联的终态任务,并将已请求但不可用的席位重建为已跳过。常规验证综合仍要求调用方提供问题和已终态的规范化结果。
审查界面仅在使用持久化 SQLite 或 PostgreSQL 作业和验证运行存储时注册。每个 CLI 审查作业都会在受过期时间约束的 payload_json 中保留完整的围栏提示词;其持久化的 argv 仅包含哈希标记。非过期飞行记录器不会接收仓库审查提示词。配置的 HTTP/API 审查席位需要明确设置 allowApiUpload:true,因为完整工件会离开本地 CLI 边界。远程 HTTP/OAuth 工作区审查即使设置了该标志,也会拒绝 API 审查上传。请将持久化作业存储视为敏感数据。其保留期限默认不受限制;需要限制记录时,操作员可以设置 [persistence.retention].jobs。
当 judgeModel 是 HTTP/API 提供方时,review_changes 会将该明确同意、裁判提供方、解析后的仓库以及调用者身份绑定到持久化的 validationId。后续调用 synthesize_validation 时,必须提供该 ID 和相同的仓库选择器。存储的裁判、仓库、所有者和上传同意均具有权威性。网关会以原子方式认领已计划的裁判,因此并发或重复合成不会启动第二个裁判。后续参数无法授予或覆盖上传同意。
LLM 请求工具
claude_request
执行 Claude Code 请求,并支持可选的会话管理。
参数:
prompt(字符串,可选*):要发送的提示词(1-100,000 个字符)。*必须且只能提供prompt或promptParts其中之一(二者互斥)model(字符串,可选):模型名称或别名(使用list_models查看可用值;支持latest)outputFormat(字符串,可选):输出格式(text|json|stream-json),默认值:stream-json— 网关会解析 NDJSON 使用情况事件,以便观测令牌和成本;只有在需要未解析的 stdout 时才覆盖为textsessionId(字符串,可选):要使用的特定会话 ID。在mcp_managed下,原生续接属于高风险输入,因为它可能继承未经验证的提供方配置;需要审批和LLM_GATEWAY_APPROVAL_ALLOW_BYPASS=1,但不会选择完全权限配置文件。continueSession(布尔值,可选):继续当前活动会话。其托管审批要求与sessionId相同。由于 Claude 的--continue会根据 cwd 进行选择,因此需要通过workingDir、显式选择的已注册工作区、由调用方提供的会话元数据或配置的默认值来指定工作区。该工作区还可以选择提供网关工作树。如果没有任何选择提供稳定的 cwd,请求将安全失败。createNewSession(布尔值,可选):始终创建新会话forkSession(布尔值,可选):分叉续接的会话,而非向其中追加内容。在mcp_managed下,这是需要审批和LLM_GATEWAY_APPROVAL_ALLOW_BYPASS=1的高风险原生分叉输入,但权限仍受限。allowedTools(字符串数组,可选):将 Claude 工具限制在此允许列表内。非空允许列表属于高风险托管输入,因为它可能改变工具权限配置。disallowedTools(字符串数组,可选):明确拒绝列出的 Claude 工具permissionMode(字符串,可选):Claude 权限模式(default|acceptEdits|plan|auto|dontAsk|bypassPermissions);优先于dangerouslySkipPermissions。在mcp_managed下,bypassPermissions是直接请求完全权限。dangerouslySkipPermissions(布尔值,可选):已弃用,映射到permissionMode: "bypassPermissions";两者同时设置时以permissionMode为准。在mcp_managed下,这是直接请求完全权限。agent(字符串,可选):要运行的命名子 Agent。非空值属于高风险托管输入,因为它可能改变工具和权限配置。agents(字符串,可选):内联 Agent 定义 JSON。出于同样的原因,非空值属于高风险托管输入。systemPrompt/appendSystemPrompt(字符串,可选):替换或扩展系统提示词。非空值属于高风险托管输入。systemPromptFile/appendSystemPromptFile(字符串,可选):从文件中替换或扩展系统提示词。非空文件路径属于高风险托管输入。safeMode(布尔值,可选):启动 Claude 时禁用本地自定义项,包括CLAUDE.md、技能、插件、钩子、MCP、命令和 Agent。true属于高风险托管输入。bare(布尔值,可选):以最简模式启动 Claude,跳过本地自定义项发现。true属于高风险托管输入。debugFile(字符串,可选):将 Claude 调试输出写入文件。非空路径属于高风险托管输入。maxBudgetUsd(数字,可选):请求的美元预算上限maxTurns(整数,可选):Agent 循环轮数上限effort(字符串,可选):推理力度(low|medium|high|xhigh|max)fallbackModel(字符串,可选):默认模型过载时自动回退使用的模型jsonSchema(字符串,可选):用于约束结构化输出的 JSON Schema 字面量addDir(字符串数组,可选):其他工作区目录。非空值属于高风险托管输入。noSessionPersistence(布尔值,可选):临时会话(不保存到磁盘)settingSources/settings/tools(可选):要加载的设置来源、设置 JSON 路径/字面量、内置工具限制。非空设置来源、设置或工具选择属于高风险托管输入。pluginDir/pluginUrl(字符串数组,可选):从本地目录或 URL 加载 Claude 插件。非空值属于高风险托管输入。excludeDynamicSystemPromptSections(布尔值,可选):精简动态系统提示词部分approvalStrategy(字符串,可选):"legacy"(默认值)或"mcp_managed"。托管模式默认使用acceptEdits,并强制设置strictMcpConfig:true,因此 Claude 仅使用网关生成的 MCP 配置。直接请求完全权限必须同时满足:调用方明确请求、审批管理器批准,以及设置LLM_GATEWAY_APPROVAL_ALLOW_BYPASS=1。其他高风险输入也需要审批和运维方设置,但权限仍受限,且本身不会选择完全权限。approvalPolicy(字符串,可选):"strict"、"balanced"或"permissive"mcpServers(字符串数组,可选):要向 Claude 开放的 MCP 服务名称(默认值:无)。传统请求会从本地注册表或 Codex MCP 配置中解析名称;未知名称会报告为不可用。在mcp_managed下,Claude 仅使用生成的配置,并且只有明确配置为网关自有本地命令的注册表条目才符合条件。动态npx启动器、环境 PATH 命令和 Codex 配置覆盖均会被拒绝。请在网关环境中配置并部署托管条目。strictMcpConfig(布尔值,可选):在传统模式下,默认值为false;设置为true可要求仅使用生成的 MCP 配置,并在请求的服务不可用时失败。在mcp_managed下,网关会强制将其设为true,调用方提供的false不能削弱此边界。optimizePrompt(布尔值,可选):优化提示词以提高令牌效率(减少 44%),默认值:falseoptimizeResponse(布尔值,可选):优化响应以提高令牌效率(减少 37%),默认值:falsecorrelationId(字符串,可选):请求跟踪 ID(未提供时自动生成)idleTimeoutMs(整数,可选):输出无活动后终止卡住的进程;30,000 至 3,600,000 ms。空闲超时仅在outputFormat为stream-json时生效;对于 text/json 则会忽略,因为运行完成前不会产生输出worktree(布尔值|对象,可选):在网关自有的 Git 工作树中运行(slice λ)。工作树需要通过显式选择、调用方提供的会话元数据或配置的默认值来指定已注册工作区;它绝不会继承进程 cwd,也不会与workingDir、addDir或includeDirs组合使用。物化过程中会禁用仓库、系统和全局 Git 钩子,以及配置的 clean、smudge 和进程检出过滤器,因此 Git LFS 等依赖过滤器的内容会保持其仓库中的表示形式,而不会执行主机命令。请求工作树属于需要审批和LLM_GATEWAY_APPROVAL_ALLOW_BYPASS=1的高风险托管输入,但权限仍受限。promptParts(对象,可选):支持缓存的结构化提示词{ system?, tools?, context?, task };与prompt互斥forceRefresh(布尔值,可选):绕过去重并强制执行新的 CLI 运行,默认值:false 工作区边界:stdio 调用方可以直接使用本机路径。HTTP/隧道调用方必须传入workspace,或使用已配置的默认工作区/会话工作区;随后会根据该工作区验证路径字段。[workspaces].allow_unregistered_working_dir是一个无效的旧版键:仍会接受它,以便旧配置能够继续加载,但无论其值为何,都不会读取它;现在设置该键会在启动时记录一条警告。它从未允许任意 HTTP 工作目录或其他目录。
响应额外字段:
approval:当approvalStrategy="mcp_managed"时的审批决策记录mcpServers:本次调用请求、启用或缺失的 MCP 服务
示例:
{
"prompt": "Write a Python function to calculate fibonacci numbers",
"model": "sonnet",
"continueSession": true,
"optimizePrompt": true,
"optimizeResponse": true
}
codex_request
执行 Codex 请求,并可选择跟踪会话。
参数:
-
prompt(字符串,可选*):要发送的提示词(1-100,000 个字符)。*必须且只能指定prompt或promptParts中的一个(两者互斥) -
model(字符串,可选):模型名称或别名(使用list_models查看可用值;支持latest,推荐:gpt-5.5) -
fullAuto(布尔值,可选):已弃用——仅展开为--sandbox workspace-write(当前 Codex 不再接受审批策略标志);建议改用sandboxMode -
sandboxMode(字符串,可选):Codex 沙箱(read-only|workspace-write|danger-full-access)。 -
dangerouslyBypassApprovalsAndSandbox(布尔值,可选):请求完全绕过 Codex 的审批和沙箱限制。 -
dangerouslyBypassHookTrust(布尔值,可选):请求绕过 Codex 的钩子信任检查。 -
approvalStrategy(字符串,可选):"legacy"是唯一可执行的策略。由于适配器无法隔离环境中的 MCP 配置,Codex 启动前会拒绝"mcp_managed"。 -
approvalPolicy(字符串,可选):由于mcp_managed不可用,此项对 Codex 无效。 -
mcpServers(字符串数组,可选):仅为元数据。不会配置或隔离 Codex MCP 服务。 -
sessionId(字符串,可选):用于跟踪的会话标识符。 -
resumeLatest(布尔值,可选):恢复之前的 Codex 会话(codex exec resume --last)。不要依赖--last选择的会话或恢复后的工作目录(#258):上游会根据当前工作目录筛选--last,且子进程仍会使用网关解析出的工作目录启动。请验证目标会话;若必须确保无误,请启动新会话。设置sessionId时会忽略此项。 -
createNewSession(布尔值,可选):始终创建新会话 -
forceRefresh(布尔值,可选):绕过去重并强制启动新的 CLI 进程,默认值:false -
outputFormat(字符串,可选):text(默认值)或json(--json输出 JSONL 事件,以提取令牌用量) -
outputSchema(字符串|对象,可选):Codex--output-schema,可以是路径或内联 JSON Schema。 -
workingDir(字符串,可选):此会话的工作根目录(-C/--cd;仅适用于新会话)。个人 Agent Config Kit 模式要求使用绝对路径。 -
addDir(字符串数组,可选):其他可写工作区目录(每个条目对应一个--add-dir;仅适用于新会话)。 -
ephemeral(布尔值,可选):Codex--ephemeral(不保留会话) -
images(字符串数组,可选):图像附件(每个条目对应一个-i <path>)。 -
profile(字符串,可选):Codex--profile <name>(仅适用于新会话;恢复会话时会忽略,并记录警告)。 -
configOverrides(对象,可选):Codex-c key=value覆盖项。仅供本地调用方使用;会拒绝远程 HTTP/OAuth 请求。 -
enable/disable(字符串数组,可选):Codex--enable/--disable功能覆盖项。它们等同于-c features.*,也仅限本地使用。 -
ignoreRules/ignoreUserConfig(布尔值,可选):Codex--ignore-rules/--ignore-user-config。 -
outputLastMessage(字符串,可选):Codex--output-last-message <path>。 -
oss(布尔值,可选):Codex--oss,选择开源提供方。 -
localProvider(字符串,可选):Codex--local-provider <name>。 -
worktree(布尔值|对象,可选):在网关管理的 Git 工作树中运行(切片 λ)。工作树必须使用已注册的工作区,并通过显式选择、由调用方管理的会话元数据或配置的默认值来指定;它不会继承进程工作目录,也不能与workingDir、addDir或includeDirs组合使用。创建工作树时会禁止执行仓库、系统和全局 Git 钩子,以及配置的 clean、smudge 和 process 检出过滤器,因此 Git LFS 等依赖过滤器的内容会保留为其仓库表示形式,而不会执行主机命令。 -
promptParts(对象,可选):具备缓存感知能力的结构化提示词{ system?, tools?, context?, task };与prompt互斥 -
optimizePrompt(布尔值,可选):优化提示词以提高令牌效率,默认值:false -
optimizeResponse(布尔值,可选):优化响应以提高令牌效率,默认值:false -
correlationId(字符串,可选):请求跟踪 ID(若省略则自动生成) -
idleTimeoutMs(整数,可选):输出无活动时终止卡住的 Codex 进程;范围为 30,000 至 3,600,000 ms 响应附加信息: -
mcpServers:此调用请求的 MCP 服务元数据
示例:
{
"prompt": "Create a REST API endpoint",
"model": "gpt-5.5",
"sandboxMode": "workspace-write",
"optimizePrompt": true
}
codex_fork_session
将现有 Codex 会话分叉到新分支(codex fork <SESSION_ID|--last> <prompt>),保留原始会话历史,同时让分叉会话独立发展。与 Codex 新建和恢复请求不同,此命令仍受 argv 限制,会将超出大小限制的 UTF-8 提示词作为不可重试的 input_too_large 拒绝。
参数:
prompt(字符串,必填):分叉会话的提示词文本(1-100,000 个字符)sessionId(字符串,可选):要从中分叉的 Codex 会话 UUID(与forkLast互斥)。forkLast(布尔值,可选):分叉最近的 Codex 会话,而不是指定某个会话。model(字符串,可选):模型名称或别名(例如,gpt-5.5、latest)sandboxMode(字符串,可选):Codex 沙盒(read-only|workspace-write|danger-full-access)。approvalStrategy(字符串,可选):"legacy"是唯一可执行的策略。由于适配器无法隔离环境中的 MCP 配置,Codex 启动前会拒绝"mcp_managed"。approvalPolicy(字符串,可选):由于mcp_managed不可用,此项对 Codex 不起作用。correlationId(字符串,可选):请求跟踪 ID(省略时自动生成)idleTimeoutMs(数字,可选):空闲超时时间(毫秒,30 秒至 1 小时;省略则使用 CLI 默认值)
gemini_request
执行 Google Antigravity CLI(agy)请求,并支持会话。
参数:
prompt(字符串,可选*):要发送的提示词(1–100,000 个字符)。*必须且只能提供prompt或promptParts其中之一(二者互斥)model(字符串,可选):模型名称或别名(使用list_models查看可用值;支持latest、pro、flash)sessionId(字符串,可选):要恢复的会话 ID。resumeLatest(布尔值,可选):自动恢复最新会话。createNewSession(布尔值,可选):始终创建新会话approvalMode(字符串,可选):旧版模式下的 Antigravity 审批模式:default会让 agy 提示确认,auto_edit会传入--mode accept-edits,plan会传入--mode plan,yolo会传入--dangerously-skip-permissions。approvalStrategy(字符串,可选):"legacy"是唯一可执行的策略。"mcp_managed"会在 Antigravity 启动前被拒绝,因为适配器无法隔离环境中的 MCP 配置。approvalPolicy(字符串,可选):对 Antigravity 无效,因为mcp_managed不可用。includeDirs(字符串[],可选):其他工作区目录(通过--add-dir传入)。project(字符串,可选):为此会话选择 Antigravity 项目(--project <ID>);与newProject互斥。newProject(布尔值,可选):为此会话创建新的 Antigravity 项目(--new-project);与project互斥。sandbox(布尔值,可选):在沙盒模式下运行 Antigravity(--sandbox)workingDir(字符串,可选):本地 Antigravity 进程的工作目录。 Stdio/本地调用方可以直接传入本地路径;远程 HTTP/OAuth 调用方 必须使用所选已注册工作区内的相对路径。includeDirs会添加读取路径,但不会选择 cwd。workspace(字符串,可选):已注册的网关工作区别名,用于为远程 HTTP/OAuth 调用方选择 Antigravity 进程的 cwd。outputFormat(字符串,可选):text(默认)、json或stream-json。异步任务记录器会独立启动支持转录的stream-json通信流,不受所请求展示格式的影响。mcpServers(字符串[],可选):仅用于元数据。Antigravity 管理自己的 MCP 配置;此字段不会创建允许列表。allowedTools、policyFiles、adminPolicyFiles、attachments(字符串[],可选)和skipTrust(布尔值,可选):Antigravity CLI 不支持。非空值或skipTrust: true都会被拒绝,并返回说明性错误。yolo(布尔值,可选):自动批准所有操作;等同于approvalMode: "yolo"。在旧版模式下会传入--dangerously-skip-permissions。worktree(布尔值|对象,可选):在网关管理的 git 工作树中运行(切片 λ)。工作树必须使用已注册工作区,该工作区需由调用方显式选择、通过调用方拥有的会话元数据选择,或通过配置的默认值选择;它绝不会继承进程 cwd,也不会与workingDir、addDir或includeDirs组合使用。物化时会禁用仓库级、系统级和全局 Git 钩子,以及已配置的 clean、smudge 和进程检出过滤器,因此 Git LFS 等依赖过滤器的内容会保留在其仓库表示形式中,而不会执行主机命令。promptParts(对象,可选):支持缓存的结构化提示词{ system?, tools?, context?, task };与prompt互斥optimizePrompt(布尔值,可选):优化提示词以提高令牌效率,默认值:falseoptimizeResponse(布尔值,可选):优化响应以提高令牌效率,默认值:falsecorrelationId(字符串,可选):请求跟踪 ID(省略时自动生成)idleTimeoutMs(整数,可选):总运行时长上限,而非空闲计时器:此提供方在进程退出前不会输出任何内容,因此即使进程运行正常,达到此时长后也会被终止。范围为 30,000 至 3,600,000 ms,默认值为 3,600,000 msforceRefresh(布尔值,可选):绕过去重并强制重新运行 CLI,默认值:false
响应附加项:
mcpServers:此调用所请求的 MCP 服务元数据
示例:
{
"prompt": "Explain quantum computing",
"model": "latest",
"resumeLatest": true,
"optimizePrompt": true
}
grok_request
执行支持会话的 Grok CLI(xAI)请求。
参数:
prompt(string,可选*):要发送的提示词(1-100,000 个字符)。*必须且只能提供prompt或promptParts中的一个(二者互斥)model(string,可选):模型名称或别名(例如grok-4.6、grok-4.5)。建议通过list_models或models://grok实时发现模型:旧版grok-buildID 已从上游移除,现在会直接报错Invalid params: "unknown model id"。transport(string,可选):"cli"(默认)运行 Grok CLI;当启用[acp].enabled且设置了提供方的runtime_enabled时,"acp"会通过 Grok 原生的grok agent stdio传输进行路由(否则会安全失败)。两种传输方式都不接受approvalStrategy:"mcp_managed";approvalPolicy不起作用。仅限同步调用:grok_request_async始终使用 CLI 传输,且不接受transportoutputFormat(string,可选):"plain"(默认)、"json"或"streaming-json"sessionId(string,可选):要恢复的会话 ID(--resume <id>)。resumeLatest(boolean,可选):恢复当前 cwd 中最近的会话(--continue)。createNewSession(boolean,可选):始终创建新会话alwaysApprove(boolean,可选):在旧模式下自动批准所有工具执行(--always-approve)。permissionMode(string,可选):default|acceptEdits|auto|dontAsk|bypassPermissions|plan。effort(string,可选):low|medium|high|xhigh|maxreasoningEffort(string,可选):推理模型的推理力度approvalStrategy(string,可选):"legacy"是唯一可执行的策略。启动 Grok 前会拒绝"mcp_managed",因为适配器无法隔离环境中的 MCP 配置。approvalPolicy(string,可选):由于 Grok 不支持mcp_managed,因此此项不起作用。mcpServers(string[],可选):仅供元数据使用。Grok 通过grok mcp管理自己的 MCP 配置;此字段不会创建允许列表。allowedTools(string[],可选):允许使用的内置工具(以逗号分隔的列表传递给--tools)。disallowedTools(string[],可选):禁止使用的内置工具(以逗号分隔的列表传递给--disallowed-tools)maxTurns(integer,可选):Agent 循环迭代次数上限(--max-turns)workingDir(string,可选):此次调用的工作目录(--cwd)sandbox(string,可选):文件系统/网络访问的沙盒配置(--sandbox,自由格式;也可通过GROK_SANDBOX设置)。rules(string,可选):附加到系统提示词的额外规则(--rules;支持@file前缀)。systemPromptOverride(string,可选):完全替换 Agent 的系统提示词。allow/deny(string[],可选):权限允许/拒绝规则(每个条目对应一个--allow/--deny)。compactionMode(string,可选):summary(默认)|transcript|segmentscompactionDetail(string,可选):none|minimal|balanced|verbose(仅适用于 segments 模式)agent(string,可选):Agent 名称或定义文件路径。agents(string|object,可选):内联子 Agent 定义 JSON。disableWebSearch(boolean,可选):禁用网络搜索和远程检索工具todoGate(boolean,可选):启用运行时回合结束 TodoGate(仅限当前会话)verbatim(boolean,可选):按原样发送提示词(同时跳过网关提示词优化)promptFile/promptJson/single(可选):从文件 / JSON 块 / 字面量中提供单轮提示词。experimentalMemory/noMemory(boolean,可选):启用/禁用跨会话记忆。noAltScreen/noPlan/noSubagents(boolean,可选):禁用备用屏幕 / 计划模式 / 子 Agent 启动oauth(boolean,可选):认证时使用 OAuth。restoreCode(boolean,可选):恢复会话时检出原始会话提交。leaderSocket(string,可选):自定义 Leader 套接字路径(--leader-socket,Grok 0.2.32+;默认为~/.grok/leader.sock),用于指定独立的 Leader 进程,例如本地版或分支版 Grok。nativeWorktree(boolean|string,可选):Grok 自带的--worktree标志(true表示裸工作树,字符串表示命名工作树);与网关的worktree选项不同。worktreeRef(string,可选):原生工作树的基础分支/标签/提交(--worktree-ref);需要nativeWorktree。forkSession(boolean,可选):将恢复的会话分叉到新分支,而不是向原会话追加内容。jsonSchema(string|object,可选):用于约束结构化输出的 JSON Schema(字符串或对象)(--json-schema)worktree(boolean|object,可选):在网关管理的 git 工作树中运行(切片 λ)。Grok 要求显式提供原生的提供方sessionId;新建、createNewSession以及仅设置resumeLatest的工作树请求都会被拒绝,因为它们无法持久地重新选择工作树。工作树需要显式选定已注册的工作区,可通过调用方持有的会话元数据或配置的默认值进行选择;它不会继承进程 cwd,也不能与workingDir、addDir或includeDirs组合使用。物化工作树时会抑制仓库级、系统级和全局 Git 钩子,以及已配置的 clean、smudge 和 process 检出过滤器,因此 Git LFS 等依赖过滤器的内容会保持仓库中的表示形式,而不会执行主机命令。promptParts(object,可选):支持缓存的结构化提示词{ system?, tools?, context?, task };与prompt互斥optimizePrompt(boolean,可选):优化提示词以提高 token 使用效率,默认值:falseoptimizeResponse(boolean,可选):优化响应以提高 token 使用效率,默认值:falsecorrelationId(string,可选):请求追踪 ID(省略时自动生成)idleTimeoutMs(integer,可选):输出停止活动后,终止卡住的进程;取值范围为 30,000 至 3,600,000 msforceRefresh(boolean,可选):绕过去重并强制重新运行 CLI,默认值:false 示例:
{
"prompt": "Summarize the latest commit message in 1 sentence",
"model": "grok-4.6",
"effort": "low"
}
持久化作业结果与自动去重
每个异步作业在状态从运行中 → 已完成/失败/已取消时,都会写入作业存储。这使网关成为持久化的收集层:
- 重新发起请求是安全的。 在去重时段内(默认 1 小时)发起相同的
*_request/*_request_async调用时,会直接复用现有的运行中或已完成作业——调用方会收到相同的作业 ID,而不是启动重复运行。这直接解决了“Agent 轮询超时后重新发起请求,整个作业又从头开始”的故障模式。 - 重启网关后,
llm_job_status和llm_job_result仍可用。 作业行会一直保留,直到操作员配置了上限;即使内存缓存已将结果逐出,调用方仍可在很久之后收集结果。 - 只有在能够确证所属网关实例已消失时,作业才会标记为
orphaned,绝不会仅仅因为另一个实例重启而如此标记。每个实例都会持有定期心跳租约,并为其拥有的每个作业盖上时间戳;只有当作业自身的租约已过期时,恢复扫描才会将queued/running作业标记为孤立。在共享存储(backend = "postgres")上,这意味着新实例绝不会将另一个仍在运行的实例所持有的作业标记为孤立。真正成为孤立状态的作业,其已捕获的部分输出仍可读取;而先过期、后恢复的所有者如果之后完成作业,状态也会自行修复为正确的终态(issue #139)。 - 在任何请求工具上设置
forceRefresh: true,即可绕过去重并强制启动一次新的 CLI 运行。
持久化配置
持久化后端通过 ~/.llm-cli-gateway/config.toml 配置(可通过 LLM_GATEWAY_CONFIG=/path/to/config.toml 覆盖)。示例:
[persistence]
backend = "sqlite" # "sqlite" | "memory" | "postgres" | "none"
path = "~/.llm-cli-gateway/logs.db" # for sqlite
# dsn = "postgresql://user:pw@host/db" # for postgres
# retentionDays = 30 # optional legacy JOB-store bound; omitted means unbounded
dedupWindowMs = 3600000
acknowledgeEphemeral = false # required to enable async tools with memory backend
# One retention policy, over every subsystem. Every destructive bound is OFF
# unless you write a number, because deleting prompt, response, or transcript
# history is destructive and no upgrade should do it for you. Unknown keys are
# refused rather than silently applying no bound.
# `llm-cli-gateway doctor --json` -> .storage.retention reports what each bound
# would delete BEFORE you set it.
[persistence.retention]
# jobs = 30 # overrides retentionDays above
# requests = 90 # flight-recorder transcripts, incl. bodies
# wedgedValidationRuns = 30 # validation runs nothing can ever finalize
# sweepIntervalMs = 3600000 # how often the sweeper ticks
# Issue #139 durable orphan-recovery lease (defaults shown). Each instance
# advances a per-job lease on every heartbeat; the sweep orphans a job only
# after its own lease expires, so a fresh instance never orphans another live
# instance's jobs on a shared store. Validated: leaseTtl >= 2*heartbeat and
# httpJobGrace >= leaseTtl.
instanceHeartbeatMs = 15000 # heartbeat cadence
instanceLeaseTtlMs = 90000 # per-job lease TTL (6x heartbeat)
httpJobGraceMs = 300000 # extra grace for no-pid http jobs (5 min)
orphanSweepIntervalMs = 30000 # reaper cadence
instanceGcMs = 3600000 # gateway_instances GC horizon
# ownsOrphanRecovery = false # DEPRECATED (#139): superseded by the lease; parsed + warned, no longer used
# Optional, postgres only. One credential per class of work, for a deployment
# that has provisioned the RBAC in docs/plans/postgres-security-hardening.md.
# `app` is not a key here: the runtime credential is [persistence].dsn above.
# `migrate` is not a key either, and must not be held by a running gateway.
# A role left out degrades onto `app`, which llm_process_health reports as
# `persistence.roles.degraded` rather than implying separation is in force.
# [persistence.roles]
# reader = "postgresql://llmgw_reader@host/db" # transcript read-back
# analytics = "postgresql://llmgw_analytics@host/db" # aggregates, no body text
# retention = "postgresql://llmgw_retention@host/db" # job expiry
后端:
sqlite(默认)——持久化、基于文件。适用于单实例部署。postgres:PostgreSQL 是所有由后端管理的持久化子系统的权威数据源,包括异步作业、去重、孤立作业恢复、HTTP 作业、验证状态、会话元数据和飞行记录器。基于文件的操作员记录(例如审批、管理员审计和工作区注册表)不属于 SQLite/PostgreSQL 引擎选项。用于多实例或服务部署。网关旁还需安装可选的对等依赖pg。memory——进程内 Map。网关退出后数据会丢失。加载时必须设置acknowledgeEphemeral = true。适用于测试和临时 CI 网关。none——不使用存储。网关不会注册*_request_async、llm_job_status、llm_job_result和llm_job_cancel。 这是结构性不变量:如果 Agent 尝试在backend = "none"的网关上调用异步工具,会在连接时收到明确的“找不到工具”错误,而不是在 1 小时 TTL 后悄然丢失内存数据。可使用llm_process_health以编程方式检查已解析的持久化状态。
backend = "postgres" 是完整的引擎选择。 配置的 dsn 必须是 PostgreSQL URL。网关不会推断部署拓扑、解析主机、限制 PostgreSQL 只能指向本地目标,也不会在诊断信息中显示从 DSN 推导出的目标。如果迁移、连接或某项操作失败,受影响的 PostgreSQL 子系统会以失败关闭,网关不会回退到 SQLite。健康状态信息会标明所选引擎,并以通用方式报告 PostgreSQL 故障。
切换引擎不会迁移数据。 现有的 SQLite 和 PostgreSQL 行会留在原处:不会自动迁移、双写或跨引擎读取。如果需要通过网关继续访问旧历史记录,请在切换前另行规划并执行数据迁移。
backend = "none" 和 LLM_GATEWAY_LOGS_DB=none 是两个不同的开关。 前者会禁用异步任务持久化;后者会禁用请求历史记录。将 backend 设为 "none" 后,飞行记录器仍会写入;禁用记录器也不会影响任务存储。启动时,两者都会在 stderr 的 Storage: 区块中说明。llm_process_health 会报告所选引擎、记录器状态、角色配置和已弃用输入的处理决定,但不会显示已配置的 PostgreSQL 目标。
DATABASE_URL 已弃用,且绝不会覆盖 [persistence]。 当它是唯一的选择器时,它会作为已弃用的别名,等同于 backend = "postgres" 加上 dsn,因此会话、任务、验证状态和请求历史记录都会使用同一个引擎。若它与显式指定的 backend 冲突,或与 dsn 不一致,则会被忽略并说明原因:网关不会中止,因为结果完全确定,且与配置文件所描述的一致。
对于 PostgreSQL,请在启动仅具备 DML 权限的网关角色之前,使用架构所有者角色或专用迁移角色应用架构:
DATABASE_URL='postgresql://<user>:<password>@<host>/<database>' npm run migrate
迁移运行器会使用 advisory lock 串行执行迁移,并将每项迁移的 SHA-256
记录在 schema_migrations.checksum_sha256 中。后续运行时,如果已记录的校验和格式错误或不匹配,运行器会在计算
或应用待处理迁移之前停止。NULL 校验和表示明确标记的旧记录,即在记录校验和功能推出之前创建的记录。此类记录允许存在,但会发出警告,且绝不会回填,
因为当前源文件无法证明历史上实际执行过哪些 SQL。请勿编辑已发布的迁移文件,也不要手动填入账本校验和。运行器
会保留历史上的 002/003 SQL,并仅在这些旧版本之一仍待处理时应用兼容性处理;前向迁移 018 会修复
已记录的旧版会话/视图布局。发布检查会拒绝对已发布迁移文件的源代码修改。
旧版环境变量(已弃用;启动时会发出警告):
LLM_GATEWAY_LOGS_DB/LLM_GATEWAY_JOBS_DB:如果未显式配置 backend,none会选择backend = "none",其他任何值都会选择backend = "sqlite",并使用该路径。显式配置的[persistence].backend优先。LLM_GATEWAY_LOGS_DB在所有 backend 上仍会独立禁用记录器;当所选记录器引擎为 SQLite 时,也会为其指定路径。LLM_GATEWAY_JOB_RETENTION_DAYS— 覆盖retentionDays。LLM_GATEWAY_DEDUP_WINDOW_MS— 覆盖dedupWindowMs。LLM_GATEWAY_ACKNOWLEDGE_EPHEMERAL— 设为1/true/yes时,会将acknowledgeEphemeral设为true。
主机保护限制([http] 和 [limits])
网关会限制 HTTP 会话增长以及异步/同步作业执行,防止客户端或请求突增导致内存、进程、CPU 或服务提供商请求无限增长。所有键都位于同一个 ~/.llm-cli-gateway/config.toml 中;默认值较为保守,但不会给本地 stdio 开发带来意外。
[http] # HTTP MCP transport session lifecycle
max_sessions = 100 # max concurrent live sessions; excess initialize returns HTTP 429
session_idle_ttl_ms = 1800000 # 30 min: reap a session idle longer than this (no client DELETE needed)
session_reaper_interval_ms = 60000 # 1 min: how often the idle reaper sweeps
[limits] # async + sync job-execution backpressure (per gateway process)
max_running_jobs = 32 # global concurrent running jobs (process CLI + HTTP API)
max_running_jobs_per_provider = 16 # per-provider concurrent running jobs
max_queued_jobs = 128 # bounded wait queue; a full queue rejects new work
queue_timeout_ms = 120000 # 2 min: max time a job waits in the queue before failing
completed_job_memory_ttl_ms = 3600000 # 1 h: in-memory retention for finished jobs (durable rows kept separately)
max_job_output_bytes = 52428800 # 50 MB: per-job stdout+stderr cap
故障情形(均可确定性处理且可安全重试):
- 达到 HTTP 会话上限:初始化请求返回
429,并带有Retry-After: 5和结构化的{ error, code: "session_capacity", retryable: true }响应体。不会创建新会话。 - HTTP 会话空闲:会话空闲时间超过
session_idle_ttl_ms后,清理器会关闭会话(传输和网关服务器),无需等待客户端发送DELETE。正在处理请求的会话绝不会在请求处理中被清理。 - 作业限流器已饱和:达到运行上限且队列已满时,
*_request/*_request_async和直接同步回退路径会返回可重试的saturated错误(structuredContent.errorCategory = "saturated",retryable: true)。不会启动任何进程。队列有空位时,作业会按 FIFO 顺序等待(按服务提供商公平调度),最长等待queue_timeout_ms,之后会因相同类别的错误而失败。 - 同步直接执行:
SYNC_DEADLINE_MS=0以及无存储/backend="none"路径会在启动前获取相同的进程许可,因此没有任何执行路径可以绕过限流器。 - 输出溢出:如果作业的 stdout 与 stderr 合计超过
max_job_output_bytes,则作业会失败(退出代码 126),其进程会被终止,完成状态会被持久化,并释放其运行槽位。 - 内存与持久化保留:
completed_job_memory_ttl_ms只会将已完成的作业从内存映射中移除。默认情况下,持久化作业存储没有大小上限;可通过[persistence.retention].jobs或旧版[persistence].retentionDays设置上限,因此即使作业已从内存中移除,仍可通过llm_job_result/llm_request_result读取结果。
实时计数器可通过 GET /healthz(无需身份验证,HTTP 传输)和 llm_process_health 工具的 backpressure 区块获取:会话当前数/上限/最久空闲时长/空闲 TTL/饱和状态,全局及按服务提供商统计的运行与排队作业数,限流器饱和计数器,配置的 TTL/输出上限,以及父进程的 RSS/堆内存。这些接口只报告计数、时长和字节数,绝不报告提示词文本、响应内容、令牌、会话 ID、Bearer/OAuth 令牌、API 密钥或机器机密。
对于生产环境中的用户服务,应将上述进程内限制与 systemd 的外层防护措施配合使用,防止意外错误、服务提供商 CLI 泄漏或评测突增耗尽主机资源:
systemctl --user edit llm-cli-gateway.service
[Service]
MemoryMax=2G
TasksMax=512
请根据工作负载选择数值:MemoryMax 应能容纳网关进程、已配置的 max_running_jobs 数量的服务提供商子进程,以及正常的输出缓冲;TasksMax 应高于 max_running_jobs 所需的进程/线程数与 HTTP 服务器和 SQLite 工作线程数之和,但仍应远低于主机耗尽阈值。如果 systemd 因达到这些限制而终止服务,可在重启后检查持久化作业,并使用 llm_process_health.backpressure 一并调整 [http]、[limits]、MemoryMax 和 TasksMax。
按项目隔离
默认情况下,网关状态按用户全局共享,而不是按项目隔离。如果没有覆盖配置,每个 Claude Code 窗口在每个代码仓库中都会启动自己的网关子进程,但它们都会读取和写入相同的状态:
-
[persistence].backend = "sqlite"时使用~/.llm-cli-gateway/logs.db(异步任务和飞行记录器)。使用backend = "postgres"时,这些行会存储在配置的数据库中。所有破坏性保留期限限制默认关闭。[persistence.retention].jobs会清理完整的任务记录,[persistence.retention].requests会清理飞行记录器的requests和gateway_metadata表,而[persistence.retention].wedgedValidationRuns会清理无法完成的验证运行及其validation_run_jobs链接。操作员必须逐项启用这些限制。validation_receipts按设计不可变,永不清理;已完成的运行也不会被清理,因此回执永远不会成为孤立记录。删除行会释放 SQLite 页面,但不会减少文件占用的字节数,因此文件不会缩小。运行
llm-cli-gateway storage compact --yes可以释放空间,但必须先停止网关,因为VACUUM会在整个重写期间持有独占锁。doctor --json->.storage.retention会报告解析后的限制、清理操作将删除的内容,以及压缩可释放的字节数。参见docs/plans/durable-state-lifecycle.dag.toml。 -
~/.llm-cli-gateway/sessions.json(使用默认文件会话后端时的网关会话元数据) -
~/.llm-cli-gateway/config.toml(解析后的配置)
当 [persistence].backend = "postgres" 选择 PostgreSQL 会话管理器时,会话元数据会存储在 PostgreSQL 中,而不是 sessions.json 中。这通常正是你想要的效果:来自代码仓库 A 的 session_list 可以显示代码仓库 B 中的会话;在窗口 A 中启动的异步任务可以在窗口 B 中轮询;1 小时的去重窗口也能捕获不同窗口中的重复请求。由网关管理的工作树可以使用 PostgreSQL 会话存储,但其文件系统所有权仍仅限于本机:共享数据库的另一台主机无法复用或清理该工作树。工作树删除失败时,会保留记录以供所属主机重试;在其他位置处理的删除不会影响该记录。数据库端的 cleanup_expired_sessions 函数会暂存包含工作树的会话记录,而不是将其删除;它不会调用网关观察器,因此不会自行尝试删除工作树。SQLite WAL 模式会保护默认的任务/飞行记录器数据库,而文件会话管理器则使用带锁的原子写入。
按项目隔离持久化任务
如果不相关的代码仓库不应共享异步任务、飞行记录器行或去重状态,请为每个项目指定自己的持久化配置。在项目的 .claude/settings.local.json 中:
{
"mcpServers": {
"llm-gateway": {
"env": {
"LLM_GATEWAY_CONFIG": "${workspaceFolder}/.gateway/config.toml"
}
}
}
}
……并在代码仓库中添加项目专属的 config.toml:
[persistence]
backend = "sqlite"
path = "/srv/repos/.../my-repo/.gateway/logs.db"
现在,为_此_代码仓库的 Claude Code 窗口启动的每个网关子进程都会读取自己的配置,并将持久化任务、飞行记录器行和去重状态写入自己的 SQLite 文件。其他代码仓库继续使用全局默认配置。此 [persistence] 覆盖不会迁移默认的文件型 sessions.json,因此不会隔离会话列表。llm_process_health.persistence.sources.configFile 可让 Agent 确认当前实际使用的持久化配置。
可由 Agent 执行的规范(DAG-TOML)
如果你希望 LLM Agent 确定地完成此设置,而不是阅读上面的说明后自行猜测,请将以下 DAG-TOML 复制到仓库中(例如 docs/planning/per-project-gateway-isolation.toml),并让你的 Agent 参照它执行。该架构遵循 agent-assurance,template_kind = "implementation-dag"。Agent 必须按层级顺序执行各单元,不得跳过验证单元,并且必须将任何未通过的门禁视为阻塞项。
[meta]
schema_version = "1.0.0"
template_kind = "implementation-dag"
docs = "https://github.com/verivus-oss/agent-assurance/blob/main/SPEC.md"
confidentiality = "public"
title = "Per-project llm-cli-gateway durable-job isolation"
spec = "https://github.com/verivus-oss/llm-cli-gateway#per-project-durable-job-isolation"
created = "YYYY-MM-DD"
total_units = 5
tier1_units = ["U01","U02","U03","U04","U05"]
tier2_units = []
tier3_units = []
# ============================================================================
# [policy.agent] — persona for the agent performing the configuration.
# ============================================================================
[policy.agent]
name = "Gateway Persistence Isolator"
role = "Configuration Engineer"
purpose = "Configure the llm-cli-gateway MCP server so its SQLite durable state is scoped to THIS repository instead of the per-user default at ~/.llm-cli-gateway/. The default file-backed session metadata remains shared."
validation_type = "Structural + Runtime Verification"
workflow_initiator = false
description = "Writes a repo-local config.toml, registers an LLM_GATEWAY_CONFIG override in .claude/settings.local.json, restarts the MCP server, and confirms via llm_process_health that the gateway is now reading the repo-local config and writing to the repo-local SQLite path."
[policy.agent.orchestration]
consumes_events = ["PerProjectIsolationRequested"]
produces_events = ["PerProjectDurableJobIsolationComplete"]
[policy.agent.responsibilities]
items = [
"Create the repo-local gateway data directory and add it to .gitignore.",
"Write a config.toml that pins backend=sqlite to a repo-local path.",
"Register the LLM_GATEWAY_CONFIG env override in .claude/settings.local.json (NOT .mcp.json — that file is committed and shared).",
"Trigger an MCP server reconnect.",
"Verify via llm_process_health that the resolved configFile and dbPath are the repo-local values.",
]
# ============================================================================
# [policy.instance] — concrete paths the agent fills in for THIS repo.
# Agent MUST replace <REPO_ABS_PATH> with the absolute path to the repo
# before emitting any artefact. Relative paths in config.toml MUST be
# expanded to absolute — the gateway does not re-resolve them per cwd.
# ============================================================================
[policy.instance]
repo_abs_path = "<REPO_ABS_PATH>" # e.g. /srv/repos/me/my-project
gateway_data_dir_relative = ".gateway" # repo-relative directory
config_toml_relative = ".gateway/config.toml"
sqlite_db_relative = ".gateway/logs.db"
claude_local_settings_relative = ".claude/settings.local.json"
gitignore_relative = ".gitignore"
mcp_server_name = "llm-gateway" # must match the entry in .mcp.json
# ============================================================================
# [policy.gates] — blocking checks. Any failure stops the workflow.
# ============================================================================
[policy.gates]
gate_repo_abs_path_resolved = "policy.instance.repo_abs_path must NOT be the literal string '<REPO_ABS_PATH>' when U01 starts."
gate_config_is_committed = "policy.instance.config_toml_relative MAY be committed. policy.instance.claude_local_settings_relative MUST NOT be committed (it is per-developer). Agent MUST verify .gitignore covers .claude/settings.local.json if absent."
gate_no_legacy_env_leak = "Agent MUST grep the shell init files for LLM_GATEWAY_LOGS_DB / LLM_GATEWAY_JOBS_DB. An explicit [persistence].backend wins for job persistence, but LLM_GATEWAY_LOGS_DB still disables the recorder on every backend and paths it when SQLite is selected. Both variables remain deprecated. The agent reports either as a finding and asks the operator to unset it before proceeding."
gate_health_confirms_isolation = "U05 MUST observe llm_process_health.persistence.sources.configFile == policy.instance.repo_abs_path + '/' + policy.instance.config_toml_relative AND llm_process_health.persistence.path == policy.instance.repo_abs_path + '/' + policy.instance.sqlite_db_relative. Anything else means the override did not take effect."
# ============================================================================
# [policy.evidence] — what each unit must emit so the work is auditable.
# ============================================================================
[policy.evidence]
per_unit_required_fields = [
"unit_id", # U01..U05
"status", # "completed" | "failed"
"artefact_paths", # files written / modified
"stdout_tail", # last 20 lines of any command output
"verification_quote", # for U05, the verbatim llm_process_health.persistence block
]
findings_required_fields = [
"gate_id", # which gate failed
"observed",
"expected",
"remediation",
]
# ============================================================================
# Units. Execute in layer order. U01..U03 modify the working tree; U04
# triggers a reconnect; U05 is the verification gate that decides success.
# ============================================================================
[units.U01]
name = "create-repo-local-data-dir"
summary = "mkdir -p <repo>/.gateway and append /.gateway/ to .gitignore (creating .gitignore if missing). The gateway will write logs.db, logs.db-wal, logs.db-shm here — none should be committed."
layer = 0
tier = 1
status = "pending"
depends_on = []
blocks = ["U02"]
estimated_loc = 5
files_modify = [".gitignore"]
produces = ["ART:gateway-data-dir"]
consumes = []
[units.U02]
name = "write-config-toml"
summary = "Write <repo>/.gateway/config.toml with [persistence] backend='sqlite' and path=<absolute-path-to-repo>/.gateway/logs.db. Path MUST be absolute. Do NOT use ~ — the gateway expands ~ but [persistence].path is read literally if not prefixed with ~/, and Claude Code may launch the gateway with a HOME that surprises you."
layer = 1
tier = 1
status = "pending"
depends_on = ["U01"]
blocks = ["U03"]
estimated_loc = 10
files_modify = [".gateway/config.toml"]
produces = ["ART:gateway-config"]
consumes = ["ART:gateway-data-dir"]
[units.U03]
name = "register-llm-gateway-config-env-in-claude-local-settings"
summary = "Add (or merge) an mcpServers.<mcp_server_name>.env entry in .claude/settings.local.json that sets LLM_GATEWAY_CONFIG to the absolute path of .gateway/config.toml. Do NOT modify .mcp.json — that file is committed and the path would be wrong for every other developer. If .claude/settings.local.json already has an mcpServers.<mcp_server_name> entry, the agent MUST merge into the existing env map (preserving other keys), not overwrite the whole entry."
layer = 2
tier = 1
status = "pending"
depends_on = ["U02"]
blocks = ["U04"]
estimated_loc = 20
files_modify = [".claude/settings.local.json"]
produces = ["ART:claude-local-settings"]
consumes = ["ART:gateway-config"]
[units.U04]
name = "trigger-mcp-reconnect"
summary = "Ask the operator to run /mcp in Claude Code (or restart Claude Code) so the gateway subprocess is re-spawned under the new env. The agent cannot do this itself — MCP server lifecycle is owned by the host."
layer = 3
tier = 1
status = "pending"
depends_on = ["U03"]
blocks = ["U05"]
estimated_loc = 0
files_modify = []
produces = ["OUT:mcp-reconnected"]
consumes = ["ART:claude-local-settings"]
[units.U05]
name = "verify-via-llm-process-health"
summary = "Call llm_process_health and assert the returned persistence block satisfies policy.gates.gate_health_confirms_isolation. Quote the verbatim persistence block in evidence. If the assertion fails, the agent MUST NOT mark the workflow complete — it must emit a finding under policy.evidence.findings_required_fields, naming the observed vs. expected configFile/path, and stop."
layer = 4
tier = 1
status = "pending"
depends_on = ["U04"]
blocks = []
estimated_loc = 5
files_modify = []
produces = ["ART:durable-isolation-verification","OUT:per-project-durable-isolation-complete"]
consumes = ["OUT:mcp-reconnected"]
**这对 Agent 很重要:**网关有多个配置入口(TOML 文件、环境变量覆盖项、两个不同的 MCP 设置文件),而有一个很容易犯的错误:编辑已提交的 .mcp.json,而不是仅在本地使用的 .claude/settings.local.json。这会悄然破坏仓库中其他所有开发者的按项目持久化范围。上述 DAG 明确规定了正确的顺序、验证门禁和失败情形,因此 Agent 无需自行推断即可执行。它刻意没有声称能够隔离默认的文件存储会话。
mistral_request
运行 Mistral Vibe Agentic 编码请求。其结构与 grok_request 类似,但采用 Vibe 特有的接口:
model(字符串,可选):Vibe 模型别名(例如mistral-medium-3.5或latest)。解析后的值会通过VIBE_ACTIVE_MODEL环境变量注入;省略此项可让网关发现 Vibe 配置,避免使用过时的硬编码默认值。transport(字符串,可选):"cli"(默认)运行 Vibe CLI;当[acp].enabled和提供方的runtime_enabled均已设置时,"acp"会通过 Vibe 原生vibe-acp传输进行路由(否则会安全失败)。两种传输方式都拒绝approvalStrategy:"mcp_managed";approvalPolicy不起作用。仅限同步:mistral_request_async始终运行 CLI 传输,且不接受transportpermissionMode:Vibe 的--agent名称:内置值为default | plan | accept-edits | auto-approve,也可以是任何受安装限制的自定义 Agent。旧模式下,请求会发送所提供的名称。allowedTools(字符串数组,可选):每个条目对应一个--enabled-tools <tool>标志。disallowedTools(字符串数组,可选):每个条目对应一个--disabled-tools <tool>标志,在启用工具筛选器之后应用。outputFormat(字符串,可选):Vibe 2.x 的值为"text"、"json"或"streaming";旧版别名"plain"和"stream-json"也受支持,并会在启动前规范化。sessionId/resumeLatest/createNewSession:标准会话控制项。当前 Vibe 默认启用会话日志记录;如果旧配置中[session_logging] enabled = false,doctor --json会显示可执行的后续操作。trust(布尔值,可选):发送--trust,使 Vibe 仅在本次调用期间信任当前工作目录(不会持久保存;会跳过交互式信任提示)。maxTurns(整数,可选):Agent 循环迭代上限(仅限--max-turns程序化模式)maxPrice(数字,可选):累计费用超过此美元上限时中断(仅限--max-price程序化模式)maxTokens(整数,可选):累计提示词与补全令牌上限(仅限--max-tokens程序化模式)workingDir(字符串,可选):运行前切换到此目录(--workdir)addDir(字符串数组,可选):其他可写工作区目录(每个条目对应一个--add-dir)。approvalStrategy(字符串,可选):"legacy"是唯一可执行的策略。由于适配器无法隔离环境中的 MCP 配置,Vibe 启动前会拒绝"mcp_managed"。approvalPolicy(字符串,可选):由于 Vibe 不支持mcp_managed,此项不起作用。mcpServers(字符串数组,可选):仅作元数据使用。Vibe 从VIBE_HOME配置中读取其 MCP 配置;此字段不会创建允许列表。worktree(布尔值|对象,可选):在网关管理的 git 工作树中运行(slice λ)。Mistral 要求显式指定提供方原生sessionId;拒绝全新会话、createNewSession以及仅指定resumeLatest的工作树请求,因为这些请求无法持久地重新选择工作树。工作树必须使用已注册的工作区,并通过显式选择、调用方拥有的会话元数据或配置的默认值来指定;它绝不会继承进程当前工作目录,也不能与workingDir、addDir或includeDirs组合使用。物化过程中会禁用仓库、系统和全局 Git 钩子,以及已配置的 clean、smudge 和 process 检出过滤器,因此 Git LFS 等依赖过滤器的内容会保留在仓库表示形式中,而不会执行主机命令。promptParts(对象,可选):支持缓存的结构化提示词{ system?, tools?, context?, task };不能与prompt同时使用optimizePrompt/optimizeResponse(布尔值,可选):提高令牌效率的优化,默认值:falsecorrelationId(字符串,可选):请求跟踪 ID(省略时自动生成)idleTimeoutMs(整数,可选):总运行时限,而非空闲计时器:此提供方会在进程退出前不输出任何内容,因此即使进程运行正常,达到此时长后也会被终止。范围为 30,000 到 3,600,000 ms,默认值为 3,600,000 msforceRefresh(布尔值,可选):绕过去重并强制启动一次新的 CLI 运行,默认值:false
devin_request
同步运行 Cognition Devin CLI 请求(无头打印模式,devin -p)。启用异步任务时,超过同步期限会自动转为可轮询的任务。
参数:
prompt(字符串,可选*):Devin CLI 的提示词(1-100,000 个字符)。实际使用时必填;promptFile为附加内容model(字符串,可选):模型名称或别名(例如opus、latest)transport(字符串,可选):"cli"(默认)运行 Devin CLI;当[acp].enabled和提供方的runtime_enabled均已设置时,"acp"通过 Devin 原生的devin acp传输方式路由(否则会安全失败)。两种传输方式都拒绝approvalStrategy:"mcp_managed";approvalPolicy不起作用。仅同步支持:devin_request_async始终运行 CLI 传输方式,且不接受transport或agentTypeagentType(字符串,可选):transport: "acp"的 ACP Agent 变体(devin acp --agent-type):"summarizer"(不含工具,文本摘要)或"review"(只读,并提供 shell 代码审查);CLI 传输方式会忽略此参数permissionMode(字符串,可选):Devin CLI 权限模式(--permission-mode):auto(自动批准只读工具)、accept-edits(也自动批准工作区编辑)、smart(也自动运行快速模型判定为安全的操作)、dangerous(自动批准所有操作)。省略时使用 Devin 的无头默认值approvalStrategy(字符串,可选):"legacy"是唯一可执行的策略。由于适配器无法隔离环境中的 MCP 配置,Devin 启动前会拒绝"mcp_managed"approvalPolicy(字符串,可选):由于 Devin 不支持mcp_managed,此参数不起作用promptFile(字符串,可选):从文件加载初始提示词(--prompt-file)sessionId(字符串,可选):要恢复的 Devin 会话 ID(--resume <id>)。为全新会话生成的gw-*ID 无法通过sessionId恢复;请使用resumeLatest: true继续resumeLatest(布尔值,可选):恢复所选 cwd 中最近的 Devin 会话(--continue)。需要设置workingDir、workspace或已配置默认工作区。createNewSession(布尔值,可选):强制创建新会话workingDir(字符串,可选):本地 Devin 进程的 cwd。仅 CLI 传输方式支持。workspace(字符串,可选):已注册的网关工作区别名,用于选择 Devin 进程的 cwd。worktree(布尔值|对象,可选):在网关拥有的 Git 工作树中运行 Devin CLI 请求。Devin 要求显式指定提供方原生的sessionId;由于无法持久地重新选择工作树,因此拒绝仅使用全新会话、createNewSession或resumeLatest的工作树请求。工作树必须选择一个已注册工作区,该工作区可由调用方显式指定、通过调用方拥有的会话元数据指定,或由已配置的默认值指定;工作树不会继承进程 cwd,也不能与workingDir、addDir或includeDirs组合使用。物化工作树时会禁用仓库、系统和全局 Git 钩子,以及已配置的 clean、smudge 和 process 检出过滤器,因此 Git LFS 等依赖过滤器的内容会保留为仓库表示形式,而不会执行主机命令。optimizePrompt/optimizeResponse(布尔值,可选):提高 Token 效率的优化,默认值:falsecorrelationId(字符串,可选):请求跟踪 ID(省略时自动生成)idleTimeoutMs(整数,可选):总运行时限,并非空闲计时器:此提供方在进程退出前不会输出内容,因此即使进程运行正常,达到此时长后也会被终止。30,000 至 3,600,000 ms,默认值为 3,600,000 msforceRefresh(布尔值,可选):跳过去重并强制重新运行 CLI,默认值:false
cursor_request
同步运行 Cursor Agent 请求。默认 CLI 传输使用无头打印模式(cursor-agent --print),启用异步任务后,超过同步期限时会自动改为可轮询的任务。设置 transport: "acp" 可使用 Cursor 原生的 cursor-agent acp 传输,但需启用 [acp].enabled、[acp.providers.cursor].enabled 和 [acp.providers.cursor].runtime_enabled。ACP 接受 prompt、model、由网关管理的 ACP sessionId,以及已注册的网关 workspace 别名。两种 Cursor 传输都不接受 approvalStrategy:"mcp_managed";approvalPolicy 对 CLI 无效,在 ACP 上则会被拒绝。
参数:
prompt(字符串,必填):提供给 Cursor Agent CLI 的提示词(1–100,000 个字符)model(字符串,可选):模型名称或别名(例如gpt-5、sonnet-4-thinking或latest)mode(字符串,可选):Cursor 模式,"plan"或"ask"(--mode)outputFormat(字符串,可选):"text"(默认)、"json"或"stream-json"transport(字符串,可选):"cli"(默认)或"acp";除非启用了 ACP 全局开关和 Cursor 提供方开关,否则 ACP 会失败关闭。ACP 仅接受prompt、model、sessionId和已注册的workspace;非默认的 Cursor CLI 控制项(mode、非文本格式的outputFormat、workingDir、非空的addDir、force、autoReview、sandbox、trust、resumeLatest、createNewSession、优化或压缩、idleTimeoutMs和forceRefresh)均会被拒绝。仅同步模式:cursor_request_async始终使用 CLI 传输方式,且不接受transportforce(布尔值,可选):启用非交互模式时传入--force。autoReview(布尔值,可选):传入--auto-review。sandbox(字符串,可选):"enabled"或"disabled"(--sandbox)。trust(布尔值,可选):本次调用传入--trust。workspace(字符串,可选):使用transport: "cli"时,Cursor 工作区路径或名称(--workspace);远程 HTTP/OAuth 调用方必须传入已注册的工作区别名,本地 stdio 调用方则可传入路径。未注册的本地相对路径值会原样保留为提供方原生的已保存工作区名称,绝不会根据网关进程的 cwd 解析;传入绝对路径可将本地目录设为 cwd。使用transport: "acp"时,必须是已注册的网关工作区别名。全新的远程 ACP 请求使用提供的别名或[workspaces].default;远程 ACP 恢复请求则继续绑定到其记录的规范别名和 cwdworkingDir(字符串,可选):本地 Cursor Agent 进程的工作目录,仅适用于 CLI 传输方式。不同于workspace(Cursor 自身的选择器);如果传入的绝对workspace路径与workingDir不一致,请求将被拒绝,而不会静默地按优先级处理。transport: "acp"时会拒绝此参数,因为 ACP 会自行解析作用范围,而不是接受后丢弃。addDir(字符串数组,可选):其他工作区根目录(每个条目对应一个--add-dir);远程 HTTP/OAuth 调用方必须使用已注册的工作区根目录。sessionId(字符串,可选):使用transport: "cli"时,要恢复的 Cursor 聊天/会话 ID(--resume <id>)。为全新的网关会话生成的gw-*ID 无法通过 CLI 传输方式恢复;请使用resumeLatest: true继续。使用transport: "acp"时,传入先前 ACP 调用返回的、由网关管理的 ACP 会话 ID;Cursor 原生会话 ID 和 CLI 会话 ID 均会被拒绝resumeLatest(布尔值,可选):仅适用于 CLI:恢复最近一次 Cursor 聊天(--continue)。ACP 会拒绝true。createNewSession(布尔值,可选):仅适用于 CLI:强制创建新会话。ACP 会拒绝true。approvalStrategy(字符串,可选):"legacy"是唯一可执行的 CLI 策略。Cursor 启动前会拒绝"mcp_managed";ACP 也有自己的权限桥接,因此同样会拒绝该值。approvalPolicy(字符串,可选):由于 CLI 不支持mcp_managed,此参数对 CLI 无效。ACP 会拒绝该参数,因为 ACP 使用自己的权限桥接。optimizePrompt/optimizeResponse(布尔值,可选):用于提高令牌效率的优化,默认值:falsecorrelationId(字符串,可选):请求跟踪 ID(省略时自动生成)idleTimeoutMs(整数,可选):总运行时长上限,而非空闲计时器:此提供方会在进程退出前不输出任何内容,因此即使进程运行正常,超出此时长后也会被终止。范围为 30,000–3,600,000 ms,默认值为 3,600,000 ms。此规则适用于默认的文本调用;使用outputFormat: "stream-json"时,Cursor 会增量流式传输,因此同一计时器会作为真正的空闲时间窗口生效forceRefresh(布尔值,可选):绕过去重并强制重新运行 CLI,默认值:false
claude_request_async / codex_request_async / gemini_request_async / grok_request_async / mistral_request_async / devin_request_async / cursor_request_async
启动一个长时间运行的 Claude、Codex、Gemini、Grok、Mistral、Devin 或 Cursor 请求,无需在同一次 MCP 调用中等待其完成。
当分析或运行时间可能超过客户端工具调用限制时,请使用以下流程:
- 使用
*_request_async启动任务 - 使用
llm_job_status轮询 - 使用
llm_job_result读取输出 - 可选:使用
llm_job_cancel停止任务
异步请求工具接受与对应同步工具相同的策略字段:
claude_request_async支持approvalStrategy:"legacy"(默认)或"mcp_managed",以及approvalPolicy:"strict"|"balanced"|"permissive"。托管模式会强制启用strictMcpConfig:true,并仅使用生成的 MCP 配置启动 Claude。- 其他所有
*_request_async工具仅支持可执行的approvalStrategy:"legacy"。由于无法隔离环境中的 MCP 配置,启动提供方之前会拒绝mcp_managed;approvalPolicy不起作用。 mcpServers仅用于配置 Claude 的生成配置。对于其他 CLI 适配器,它仅作为元数据,不会创建 MCP 许可名单。
llm_job_status
返回异步任务的生命周期状态(queued、running、completed、failed、canceled、orphaned)、元数据,以及有界的规范化进度。afterProgressSeq 会返回序列号更大的事件,progressLimit 可选择按序列号正序排列的最多 64 个事件。使用 nextAfterSeq 继续获取;highWaterSeq(及其兼容别名 lastSeq)报告该任务已观察到的最高序列号,hasMore 则报告是否有下一页保留事件可立即获取。进度快照会报告 capability(structured、activity_only 或 lifecycle_only)、lastActivityAt、游标/最高序列号元数据、droppedCount,以及包含阶段、类型、时间戳、安全消息和来源的事件。Claude stream-JSON、Codex JSONL、Grok streaming-JSON、Gemini stream-JSON、Mistral streaming 和 Cursor stream-JSON 会公开结构化活动。Codex 验证和代码库审查调用不会请求 JSONL,因此会报告 activity_only。HTTP/API 任务会报告 lifecycle_only;其他进程输出模式仅公开隐私安全的活动/生命周期信号。原始推理内容、提供方提供的工具名称、工具参数、路径、提供方 ID 和输出文本不会复制到进度消息中。工具启动活动使用固定消息 Using a provider tool。
llm_job_watch
等待自有异步任务的规范化进度更新,最长等待 30 秒。将响应中的 nextAfterSeq 作为 afterProgressSeq 传入;响应使用与 llm_job_status 相同的快照结构。当 MCP 请求携带进度令牌时,网关仅会在该监视调用处于活动状态期间发送 notifications/progress。该工具会在整个等待过程中重新检查所有者访问权限,并返回与其他任务工具相同的“自有或未找到”结果。
llm_job_result
返回异步任务捕获的 stdout/stderr。默认返回适合展示的结果,maxChars 会将每次调用中 stdout 和 stderr 流的字符数分别限制在 1,000 到 2,000,000 之间(默认为 200,000)。
要完整、可恢复地获取大型提供方流,请设置 rawOutput:true。响应的 result 包含彼此独立的 stdoutOffsetChars、stdoutTotalChars 和 stdoutNextOffsetChars 字段,以及对应的 stderr* 字段。将两个偏移量都设为零,然后将每个非空的下一偏移量分别传回 stdoutOffsetChars 或 stderrOffsetChars,直到该流的下一偏移量为 null。原始分页不会进行展示解析或压缩。在本地 stdio 接口中,按流顺序拼接分页内容即可还原捕获的 stdout 或 stderr 流。远程调用方使用相同的偏移量协议,但返回分页前会对提供方会话 ID 的范围进行脱敏,包括跨越分页边界的 ID。请将远程 rawOutput:true 视为可恢复的脱敏输出,而不是与捕获的提供方输出逐字节一致的内容。
默认展示模式下不接受非零偏移量。展示模式可能会解析流式提供方格式、重建 Codex 回复或压缩文本,因此无法安全地从捕获流的某个切片继续读取,也无法将其与已展示的结果拼接。如果应用必须继续收集输出,请使用 rawOutput:true。
每个非 Kit CLI 任务都会记录实际启动时的 argv、解析后的 cwd 范围(如有)、仓库 HEAD、有界的 SHA-256 指令文件证据、捕获语法,以及转录内容是完整捕获、捕获至规定的 50 MiB 总上限,还是未捕获。Claude、Codex、Gemini、Grok、Mistral 和 Cursor 会通过其支持转录能力最强的接口启动。Devin 会通过网关管理的 ATIF 导出路径启动;进程退出后,常规的非符号链接导出文件会被复制到持久化任务记录中,随后删除源文件。本地 stdio 调用方可在原始模式下分页读取原生转录内容。远程 HTTP/OAuth 调用方无法获取原始任务结果、原生转录内容、指令路径或重放上下文。
llm_job_cancel
取消正在运行的异步任务。
approval_list
列出网关记录的近期 Claude MCP 管理的审批决定。
参数:
limit(number,可选):记录数上限(1-500),默认值:50cli(string,可选):按"claude"、"codex"、"gemini"、"grok"、"mistral"、"devin"或"cursor"筛选
审批记录会持久化到 ~/.llm-cli-gateway/approvals.jsonl。
llm_request_result
通过关联 ID 读取任何已持久化的请求(同步或异步)。每个响应都会在 structuredContent.correlationId 中回传其 ID;结果不再内联时,可将该 ID 传给此工具以取回已持久化的提示词/响应。此工具会读取飞行记录器,因此不依赖异步任务持久化即可工作(禁用飞行记录时会返回“未找到”)。
参数:
correlationId(string,必填):先前请求的关联 IDmaxChars(number,可选):要返回的已持久化响应的最大字符数(1,000-2,000,000)includePrompt(boolean,可选):是否包含完整的已持久化提示词文本,默认值:falseincludeJobRecord(boolean,可选):是否包含关联的原始提供方捕获内容和重放上下文;仅适用于本地 stdio 调用方,默认值:false。远程调用方无法获取转录内容或主机路径。
llm_request_list
按时间倒序列出近期已持久化的请求,不包含关联 ID,这是查找关联 ID 的方式。其他所有飞行记录器读取操作都需要一个 ID,该 ID 会以内联方式交给发起请求的调用方,因此,未发起调用的 Agent(或自上次调用后上下文已被压缩的 Agent)应从这里开始。仅返回元数据:将返回的 correlationId 传给 llm_request_result 获取请求正文,或将返回的 asyncJobId 传给 llm_job_status。调用方只能看到自己的请求。列表为空并不能证明没有运行过,也不代表没有留下工作记录:跨 LLM 验证席位不会在这里写入行,但会写入 validation_runs 和 validation_run_jobs,且每个记录都会关联一个作业行,其中包含已启动的 argv 和提供方输出(使用 validation_receipt 读取,然后使用 llm_job_result)。也可以禁用飞行记录。默认情况下,作业和请求都不设上限;操作员可以分别配置它们的上限。
参数:
limit(数字,可选):最大行数,1-200,默认值:25since(字符串,可选):ISO-8601 起始时间,例如2026-08-21T00:00:00Zcli(字符串,可选):仅包含记录中的某个提供方sessionId(字符串,可选):仅包含某个网关会话 ID
llm_process_health
报告网关进程健康状况:异步作业管理器状态、解析后的持久化配置,以及所配置引擎上的飞行记录器健康状况。当 backend = "postgres" 时,记录器会报告不透明的 postgresql 目标和通用 PostgreSQL 故障,且不会回退到 SQLite。其警告还会说明,之前引擎中遗留的行未迁移。显式的 LLM_GATEWAY_LOGS_DB=none 记录器关闭开关仍然独立;memory 和已禁用的 none 作业存储模式也仍按设计为临时模式。
upstream_contracts
返回网关声明的提供方 CLI 合约,可选择探测已安装的二进制文件以检查是否存在偏差。
参数:
cli(字符串,可选):筛选项(claude|codex|gemini|grok|mistral|devin|cursor)probeInstalled(布尔值,可选,默认值false):运行本地--help探测,并将公布的标志与声明的合约进行比较——强烈建议在提供方 CLI 升级后执行。探测会报告missingFlags、extraFlags、acknowledgedExtraFlags(从extraFlags中过滤掉的已知上游专有标志)、discoveredFlags和过时标记的warnings。
会话管理工具
session_create
为特定 CLI 创建新会话。
参数:
cli(字符串,必需):要为其创建会话的 CLI("claude"、"codex"、"gemini"、"grok"、"mistral"、"devin"、"cursor")description(字符串,可选):会话描述setAsActive(布尔值,可选):设为活动会话,默认值:true
示例:
{
"cli": "claude",
"description": "Code review session",
"setAsActive": true
}
session_list
列出所有会话,可选择按 CLI 筛选。
参数:
cli(字符串,可选):按 CLI 筛选("claude"、"codex"、"gemini"、"grok"、"mistral"、"devin"、"cursor")
响应包含:
- 会话总数
- 会话详情(ID、CLI、描述、时间戳、活动状态)
- 每个 CLI 的活动会话 ID
session_set_active
为特定 CLI 设置活动会话。
参数:
cli(字符串,必需):要为其设置活动会话的 CLIsessionId(字符串,必需):要激活的会话 ID(或传入 null 以清除)
session_get
获取特定会话的详情。
参数:
sessionId(字符串,必需):要获取的会话 ID
session_delete
删除特定会话。
参数:
sessionId(字符串,必需):要删除的会话 ID
session_clear_all
清除所有会话,可选择仅清除特定 CLI 的会话。
参数:
cli(字符串,可选):仅清除特定 CLI 的会话
实用工具
list_models
列出每个 CLI 可用的模型。
参数:
cli(字符串,可选):要列出模型的特定提供方("claude"、"codex"、"gemini"、"grok"、"mistral"、"devin"、"cursor",或已启用的 API 提供方名称)。启用一个或多个[providers.<name>]API 提供方时,未筛选的响应还会包含apiProviders数组(每个条目都带有providerKind: "api"标记);参见 API 提供方(HTTP)。
响应包含:
- 模型名称和描述
- 每个模型的最佳使用场景
- CLI 专属信息
- 显式配置默认模型时的
defaultModel和defaultModelSource - 带有来源/置信度(
fallback、config、env、observed)的modelMetadata - 配置了
aliases和warnings时,或发现过程降级运行时的aliases和warnings
注册表将显式配置视为权威来源。随附的回退模型只是低置信度提示;从本地会话历史中观察到的 Gemini 模型只会作为低置信度条目合并,不会成为默认模型。
模型注册表环境变量覆盖项:
# Explicit defaults
CLAUDE_DEFAULT_MODEL=haiku
CODEX_DEFAULT_MODEL=<codex-model-id>
GEMINI_DEFAULT_MODEL=gemini-2.5-flash
# Additional models: comma/newline list, JSON array, or JSON object of model->description
GEMINI_MODELS='{"gemini-team-default":"Team-approved Gemini model"}'
# Aliases
GEMINI_MODEL_ALIASES='team=gemini-team-default'
LLM_GATEWAY_MODEL_ALIASES='codex.fast=gpt-5.3-codex-spark,gemini.fast=gemini-team-default'
# Deterministic config/discovery paths
CODEX_CONFIG_PATH=/path/to/config.toml
CLAUDE_SETTINGS_PATH=/path/to/settings.json
CLAUDE_SETTINGS_LOCAL_PATH=/path/to/settings.local.json
GEMINI_SETTINGS_PATH=/path/to/settings.json
GEMINI_HISTORY_ROOT=/path/to/.gemini/tmp
# Disable local model-history discovery
LLM_GATEWAY_DISABLE_MODEL_DISCOVERY=1
provider_tool_capabilities
报告提供方工具和功能能力目录。在编排提供方专属请求之前使用此工具,以便调用方区分受支持的控制项、由提供方管理的配置、被忽略的兼容性字段和不受支持的输入。
参数:
cli(字符串,可选):提供方筛选项("claude"、"codex"、"gemini"、"grok"、"mistral"、"devin"、"cursor"、"grok_api",或已启用的 API 提供方名称)includeSkills(布尔值,默认值为true):包括受限的本地技能发现结果includeProviderTools(布尔值,默认值为true):包括从已发现技能中提取的提供方原生工具includeUnsupported(布尔值,默认值为true):包括明确标记为不受支持/降级的输入记录includePaths(布尔值,默认值为false):在发现结果中包括原始本地文件系统路径refresh(布尔值,默认值为false):绕过短时能力缓存
响应架构为 provider-tool-capabilities.v2。能力发现是只读且范围受限的;除非明确将 includePaths 设为 true,否则原始本地路径会被隐藏,且不会读取包含密钥的身份验证文件。
等效的 MCP 资源:
provider-tools://catalog:完整的提供方目录provider-tools://claudeprovider-tools://codexprovider-tools://geminiprovider-tools://grokprovider-tools://grok_apiprovider-tools://mistralprovider-tools://devinprovider-tools://cursorprovider-tools://<api-provider>:每个已启用的[providers.<name>]API 提供方对应一个
doctor --json 也会输出一个精简的 provider_capabilities 区块,包含相同的架构版本、各提供方的请求工具名称、受支持的功能名称、不受支持的输入名称、配置界面计数、发现计数和资源 URI。此区块面向设置助手,使其无需获取本地技能正文或原始路径即可获得简明的能力摘要。启用 API 提供方时,doctor --json 还会输出 api_providers 健康状态区块;参见 API 提供方(HTTP)。
cli_versions
报告已安装的 CLI 版本。
参数:
cli(字符串,可选):要检查的特定 CLI("claude"、"codex"、"gemini"、"grok"、"mistral"、"devin"、"cursor")
cli_upgrade
规划或执行单个 CLI 的升级。
参数:
cli(字符串,必填):要升级的 CLI(“claude”、“codex”、“gemini”、“grok”、“mistral”、“devin”、“cursor”)target(字符串,可选):软件包标签/版本/目标,默认为:latestdryRun(布尔值,可选):仅返回升级计划,不执行升级,默认为:truetimeoutMs(数字,可选):当dryRun=false时的升级超时时间
升级策略:
- Claude 最新版:
claude update - Claude 指定目标:
claude install <target> - Codex 最新版:
codex update - Codex 指定目标:
npm install -g @openai/codex@<target> - Gemini 最新版:
agy update(Antigravity 自行更新;不支持指定版本目标) - Grok 最新版:
grok update - Grok 指定目标:
grok update --version <target> - Mistral(Vibe):调用检测到的安装程序(
pip/uv/brew);未检测到安装程序时会报错并提供指引(Vibe 没有自更新命令) - Devin 最新版:
devin update(自行更新;不支持指定版本目标) - Cursor 最新版:
cursor-agent update(自行更新;不支持指定版本目标)
试运行示例:
{
"cli": "gemini",
"target": "latest",
"dryRun": true
}
API 提供商(HTTP)
除了可启动的 CLI 工具外,网关还可以将请求路由到一流的 HTTP/API 提供商(OpenRouter 和其他兼容 OpenAI 的端点、Anthropic Messages API,以及 xAI Responses API)。这些提供商使用 Node 内置的 HTTP 客户端(node:https;对于环回端点则使用 node:http),而不是启动 CLI,并且会经过与 CLI 工具相同的请求/作业/验证/飞行记录器机制,因此在会话、异步作业、去重、重试、使用量/成本记录和跨大模型验证方面均能达到同等水平。
配置提供商
API 提供商在 ~/.llm-cli-gateway/config.toml 中以 [providers.<name>] 块声明(可通过 LLM_GATEWAY_CONFIG 覆盖)。<name> 会成为该提供商在所有工具和资源中的标识,且 不得 与可启动的 CLI 名称(claude、codex、gemini、grok、mistral、devin、cursor)冲突;如有冲突,将发出警告并禁用该提供商。
# OpenRouter (OpenAI-compatible). The key is read from the named env var at
# request time and is never written to config, logs, the flight recorder, or
# the dedup key.
[providers.openrouter]
kind = "openai-compatible" # "openai-compatible" | "anthropic" | "xai-responses"
base_url = "https://openrouter.ai/api/v1"
api_key_env = "OPENROUTER_API_KEY" # env var NAME, not the key itself
default_model = "x-ai/grok-2"
models = ["x-ai/grok-2", "anthropic/claude-sonnet-4.6"] # optional allowlist: an explicit model must be listed (omit model to use default_model)
usage_include = true # OpenRouter token/cost reporting (usage:{include:true})
# Keyless-local: an openai-compatible provider on a loopback base_url (Ollama,
# llama.cpp) is enabled with no api_key_env at all.
[providers.ollama]
kind = "openai-compatible"
base_url = "http://127.0.0.1:11434/v1"
default_model = "qwen2.5"
当 api_key_env 解析为非空值,或该提供商是环回 base_url 上无需密钥的本地 openai-compatible 提供商时,该提供商处于 启用 状态。base_url 必须使用 https,除非目标是 localhost/环回地址。单个 [providers.<name>] 块若 架构无效,只会禁用该块自身(并发出警告),其他提供商不受影响;若文件任意位置存在 TOML 语法错误,则情况不同,整个配置会回退为默认值。
既有的 [providers.xai] 块保留其专用的 grok_api_request 工具和 xAI 标识。与其他已启用的提供商一样,它也会通过通用接口提供,因此配置了 xAI 密钥后会同时注册 grok_api_request 和通用的 api_xai_request(并且 xai 条目会出现在 apiProviders、models://xai 等位置)。
请求工具
对于每个已启用的提供商,网关都会注册 api_<name>_request(启用异步任务时还会注册 api_<name>_request_async)。它们接受与 CLI 请求工具相同的结构:
prompt(或感知缓存的promptParts{ system?, tools?, context?, task },两者互斥),可选的systemmodel(省略时使用default_model;如果显式指定的值不在配置的models允许列表中,则会被拒绝)、maxOutputTokens、temperature、topPreasoningEffort(none|low|medium|high):仅由xai-responses适配器转发;其他类型会接受该参数但将其忽略timeoutMs、optimizePrompt、optimizeResponse、forceRefresh、correlationId- 用于同步
api_<name>_request保持连续性的sessionId/createNewSession(见下文);目前异步变体会忽略这些参数
响应(以及 50 MB 输出上限、去重窗口、取消和保留)与 CLI 工具完全一致;HTTP 请求也会像其他所有请求一样,连同状态、用量和费用记录到飞行记录器中。
连续性
连续性能力按提供商类型区分,且绝不会在会话记录中存储对话内容(网关遵循会话中不存储对话记录的约束):
xai-responses使用真正的服务端续接:网关会在会话元数据中持久化提供商的previous_response_id,并在恢复会话时将其传回;遇到过期句柄导致的 404 时会自动修复。openai-compatible和anthropic使用无状态重发:会话会跟踪活动状态和所有者状态,以隔离主体,但调用方必须重新发送先前的上下文(不存在服务端对话句柄)。
发现入口
已启用的 API 提供商会与 CLI 提供商一同显示在各个发现入口中。以下模型、能力、诊断和资源入口在未启用任何 API 提供商时,会完全省略其 API 字段,因此输出与之前逐字节相同;例外是 llm_process_health(它始终包含 apiProviders 数组,未启用任何 API 提供商时该数组为空):
list_models/list_available_models:包含带有providerKind: "api"标签的apiProviders数组,其中包含defaultModel和可选的允许列表;未启用任何 API 提供商时会完全省略该数组。llm_process_health:始终存在的outboundProviders.apiProviders数组(未启用任何 API 提供商时为空),包含相同的投影信息以及每个提供商的熔断器状态。provider_tool_capabilities:提供按api类型区分的能力元数据。支持模型、采样,以及(对xai-responses而言)推理和连续性;不支持允许/拒绝工具列表、MCP 服务、本地技能以及 workspace/worktree 控制(这些仅适用于 CLI)。doctor --json:包含api_providers健康状态区块(类型、base_url、default_model、模型、密钥环境变量名称、密钥是否存在以及登录指引);未启用任何 API 提供商时会完全省略该区块。可选标志doctor --json --probe-api-providers会添加每个提供商的端点可达性结果(直接发送GET,任何 HTTP 响应都视为可达)。该探测默认关闭,须主动启用:正常运行doctor不会建立网络连接,也不会消耗令牌(reachable保持为null)。- MCP 资源:每个已启用的提供商都对应
models://<provider>,支持连续性的类型还对应sessions://<provider>,此外还有provider-tools://<provider>。provider-subcommands://资源仅适用于 CLI(API 提供商没有子命令)。
任何这些入口都不会输出 API 密钥值(只会显示环境变量名称和密钥是否存在)。由于 base_url 由配置提供,且可能合法地包含 URL 用户信息,因此诊断入口(doctor、登录指引)会在显示前对其中嵌入的凭据进行脱敏;实际请求路径和可达性探测仍使用原始配置 URL。
安全说明
解析后的 API key 不会写入 payloadJson、去重键、日志或飞行记录器。对于普通的非 Kit 异步任务,请求提示词会以明文形式持久化在异步任务存储中(SQLite 位于 [persistence].path,默认为 ~/.llm-cli-gateway/logs.db;当 backend = "postgres" 时则存储在 Postgres 行中),且不会进行密钥脱敏。这与 CLI 工具的行为一致:只要提示词是作为命令参数传递,而不是通过 stdin 流式传入,就会持久化在 argsJson 中。review_changes 会有意在受有效期限制的 payloadJson 中保留完整的带围栏 CLI 提示词,在 argsJson 中仅存储哈希标记,并且不会将该提示词复制到飞行记录器中。Personal Agent Config Kit 的持久化行使用独立的隐私边界:不会持久化编译后的指令或请求参数,也不会保留提供方输出和错误,只保留 Kit 所需的恢复与完整性状态。请将任务存储视为静态敏感数据。请参阅安全注意事项和 Personal Agent Config Kit 指南。
会话管理
工作原理
- 网关元数据,而非对话记录:会话记录跟踪所有权、时间戳、活动指针和提供方元数据,不存储对话记录。
- 存储后端:会话管理器遵循
config.toml中的[persistence].backend:sqlite/none使用~/.llm-cli-gateway/sessions.json,postgres则选择 PostgreSQL 会话管理器。DATABASE_URL是 DEPRECATED 覆盖项,仅为兼容性而保留;它会发出一次警告,并且当其值与显式设置的[persistence].dsn不一致时会被直接拒绝。请注意,PostgreSQL 会话存储没有 TTL 淘汰机制,而文件存储会在 30 天后清理;详见 3.1.0 说明。 - 提供方原生的连续性:网关会话 ID 是跟踪元数据,不会自动成为提供方原生的恢复 ID。原生行为因提供方而异:
claude_request配合continueSession:true时,会在稳定的已选工作目录中使用 Claude 的最新对话;如果未提供workingDir,或未通过显式选择的已注册工作区、调用方拥有的会话元数据或配置的默认值选择工作区,则会安全失败。该工作区还可选择性地提供网关 worktree。codex_request的sessionId需要真实的 Codex UUID,或者使用resumeLatest:true恢复之前的会话。不要依赖--last选择哪个会话,也不要依赖恢复后的工作目录(仍在审查中)。 - 调用方隔离:HTTP/OAuth 调用方只能检索或重用自己拥有的会话。其会话投影会隐藏本地路径和提供方原生标识符。
- Personal Kit:启用 Personal Agent Config Kit 后,Claude、Codex 和 Mistral 会使用独立的、绑定上下文的活动会话指针,并且仅在当前网关进程中保留原生续接句柄。请参阅 Personal Agent Config Kit 指南。
会话工作流
// 1. Create a gateway tracking record when you need one.
// This ID is not automatically a native provider resume ID.
await callTool("session_create", {
cli: "claude",
description: "Debugging session",
setAsActive: true,
});
// 2. Use the provider's documented native continuity control.
await callTool("claude_request", {
prompt: "What's the bug in this code?",
continueSession: true, // Claude's latest conversation in this working directory
});
await callTool("codex_request", {
prompt: "Review the proposed fix.",
resumeLatest: true, // or pass a real Codex UUID as sessionId
});
// 3. Inspect or remove gateway tracking records.
await callTool("session_list", { cli: "claude" });
await callTool("session_delete", {
sessionId: "session-id-to-delete",
});
session_delete 会从调用方可见的会话界面中移除网关跟踪信息。使用任一会话管理器时,它还会在处理删除的主机上,尽力清理关联的网关自有生命周期资源,例如受管理的工作树。持久拥有的工作树会话会作为隐藏的待清理墓碑保留,直到验证 Git 移除成功;如果移除失败,则会在所属主机上注册管理器时重试。它不会删除提供方自行存储的对话。
无论使用哪种引擎,清理都由所属主机负责。从另一台主机处理删除时,不会移除任何工作树,也会保留所属主机上的记录,而不是丢弃跟踪信息。session_clear_all 对每个会话的处理方式相同。数据库端的 cleanup_expired_sessions 函数会暂存相同的墓碑,而不是删除包含工作树的会话;它不会调用任何网关观察器,因此不会自行尝试移除工作树。墓碑会一直保留,直到清理成功,且不受保留期限限制。
配置
环境变量
-
DEBUG:启用调试日志(设置为任意值即可)DEBUG=1 node dist/index.js -
LLM_GATEWAY_APPROVAL_POLICY:Claudemcp_managed请求未传入approvalPolicy时使用的默认审批策略(strict、balanced、permissive)。对非 Claude 适配器无效,因为它们会在启动前拒绝mcp_managed。LLM_GATEWAY_APPROVAL_POLICY=strict node dist/index.js -
LLM_GATEWAY_APPROVAL_ALLOW_BYPASS:仅适用于 Claude 的approvalStrategy:"mcp_managed"。直接请求bypassPermissions,或使用未经验证的执行配置(例如设置、指令覆盖、插件、额外目录、worktree 或原生续接),默认均会被拒绝,无论审批评分如何。普通的 Claude 托管请求会使用--permission-mode acceptEdits。仅当你希望允许调用方通过 Claude 的常规审批决策明确提出请求时,才将此项设为
1/true。单靠环境设置绝不会提升普通 Claude 托管请求的权限。此设置不会在 Codex、Gemini、Grok、Mistral、Devin 或 Cursor 上启用mcp_managed:这些适配器会在启动提供方之前拒绝该请求。LLM_GATEWAY_APPROVAL_ALLOW_BYPASS=1 node dist/index.js -
LLM_GATEWAY_TRUSTED_PRINCIPAL_HEADER:承载由可信入口(任何具备身份识别功能的反向代理 / IdP)声明的已认证用户身份的 HTTP 标头名称。设置后,网关会采用该标头值作为请求的所有权主体——但仅适用于使用网关自身静态 bearer token 进行身份验证的请求(即来自可信上游代理的请求),绝不会采用任意远程客户端提供的值。默认关闭;与 IdP 无关。可让由代理接入的多用户部署将各用户身份传递到网关。LLM_GATEWAY_TRUSTED_PRINCIPAL_HEADER=x-gateway-principal node dist/index.js -
LLM_GATEWAY_OAUTH_REQUIRE_CONSENT/LLM_GATEWAY_OAUTH_CONSENT_SECRET:内置 OAuth 服务的可选人工同意关卡。启用后(REQUIRE_CONSENT=1,或通过设置CONSENT_SECRET隐式启用),/oauth/authorize会显示操作员审批页面(受 CSRF 保护),并且仅在输入专用同意密码后才签发授权码,而不是自动签发。CONSENT_SECRET是明文密码(在内存中进行哈希;也可以在[http.oauth]中持久化consent_secret_hash)。默认关闭;远程 OAuth 在没有可供验证的密码时拒绝启用同意关卡。LLM_GATEWAY_OAUTH_REQUIRE_CONSENT=1 LLM_GATEWAY_OAUTH_CONSENT_SECRET='choose-a-strong-code' node dist/index.js -
LLM_GATEWAY_CONFIG:网关 TOML 配置文件的路径(默认:~/.llm-cli-gateway/config.toml)。有关[persistence]架构,请参阅上文的持久化配置。 -
LLM_GATEWAY_SKILLS_PATH:启动时要加载的额外本地技能包根目录,使用主机路径分隔符分隔(Linux/macOS 上为:,Windows 上为;)。这些路径会追加在[skills].paths之后;如果存在,~/.llm-cli-gateway/skills仍会最后加载。 -
LLM_GATEWAY_LOGS_DB:已弃用。未显式配置后端时,会使用此路径选择backend = "sqlite"(或在设为none时选择backend = "none")。对于作业持久化,显式配置的后端优先。此变量仍会独立地在所有后端上禁用记录器,并在选择 SQLite 时为其指定路径。启动时会发出弃用警告;请迁移到config.toml。# Custom path LLM_GATEWAY_LOGS_DB=/var/log/gateway/logs.db node dist/index.js # Disable the recorder; with no explicit backend, also disable async persistence LLM_GATEWAY_LOGS_DB=none node dist/index.js -
LLM_GATEWAY_REDACT_LOGGED_SECRETS:从写入飞行记录日志的提示词 / 系统 / 响应副本中,删除可识别的密钥(提供方 / 云 / VCS 密钥、bearer token、JWT、PEM 私钥、key=value格式的密钥赋值)。默认启用;设为0/false/off/no可原样存储内容。此设置仅影响审计日志——实时同步响应和异步llm_job_result输出不会被修改。# Opt out of flight-recorder secret redaction LLM_GATEWAY_REDACT_LOGGED_SECRETS=0 node dist/index.js
CLI 专属设置
每个 CLI 都可以通过各自的配置文件进行配置:
- Claude Code:
~/.claude/settings.json - Codex:
~/.codex/config.toml - Gemini / Antigravity (
agy):~/.gemini/settings.json
开发
项目结构
llm-cli-gateway/
├── src/
│ ├── index.ts # Main MCP server and tool definitions
│ ├── executor.ts # CLI execution with timeout support
│ ├── session-manager.ts # Session management logic
│ └── __tests__/
│ ├── executor.test.ts # Unit tests for executor
│ └── integration.test.ts # Integration tests
├── dist/ # Compiled JavaScript
├── package.json
├── tsconfig.json
└── vitest.config.ts
运行测试
# Run all tests
npm test
# Run unit tests only
npm run test:unit
# Run integration tests only
npm run test:integration
# Watch mode
npm run test:watch
构建
npm run build
启动服务
npm start
错误处理
网关会为常见问题提供详细的错误消息:
未找到 CLI
Error executing claude CLI:
spawn claude ENOENT
The 'claude' command was not found. Please ensure claude CLI is installed and in your PATH.
外部超时 / 旧版超时选项
Error executing codex CLI: Command timed out
Process timed out after 120000ms
参数无效
Prompt cannot be empty
Prompt too long (max 100k chars)
日志记录
日志写入 stderr(stdout 保留给 MCP 协议):
[INFO] 2026-01-24T05:00:00.000Z - Starting llm-cli-gateway MCP server
[INFO] 2026-01-24T05:00:01.000Z - claude_request invoked with model=sonnet, prompt length=150
[INFO] 2026-01-24T05:00:05.000Z - claude_request completed successfully in 4523ms, response length=2048
[ERROR] 2026-01-24T05:00:10.000Z - codex CLI execution failed: spawn codex ENOENT
启用调试日志:
DEBUG=1 node dist/index.js
故障排除
未找到 CLI
确保已安装 CLI,并且位于 PATH 中:
which claude
which codex
which agy
网关会扩展 PATH,以包含常见位置:
~/.local/bin/usr/local/bin/usr/bin- 所有
~/.nvm/versions/node/*/bin目录
权限错误
如果遇到权限错误,请确保 CLI 工具具有适当的权限:
chmod +x $(which claude)
chmod +x $(which codex)
chmod +x $(which agy)
会话存储问题
以下文件检查仅适用于默认的文件会话管理器。如果 [persistence].backend = "postgres" 选择了 PostgreSQL 会话存储,则 sessions.json 并非权威数据源。在网关进程、提供程序子进程或 Personal Agent Config Kit 操作正在运行时,请勿删除或编辑会话存储。
- 停止本地网关进程后检查文件权限:
ls -la ~/.llm-cli-gateway/
-
如需有意清理网关记录,请使用
session_delete或session_clear_all。请勿将手动编辑文件作为常规恢复方法。 -
如果文件存储无法读取,请仅在网关停止后检查副本:
cat ~/.llm-cli-gateway/sessions.json
如果 Personal Agent Config Kit 操作卡住,请勿重置 sessions.json;请按照 Personal Agent Config Kit 指南中的仅限本地恢复流程操作。
性能
超时
网关不会为大模型 CLI 请求强制设置默认执行超时。
如果 MCP 客户端/运行时对每次工具调用设置了截止时间,请使用异步工具(*_request_async + llm_job_status/llm_job_result),以便长时间运行的作业能够在单次调用窗口之外完成。
并发请求
网关支持跨不同 CLI 并发处理请求。每个请求都会启动一个独立进程。
安全注意事项
- 输入验证:所有提示词都会经过验证(最少 1 个字符,最多 100k 个字符)
- API 提供商密钥:对于
[providers.<name>]HTTP 提供商,网关仅在请求时从指定的环境变量中读取密钥。解析出的密钥不会写入持久化的payloadJson、去重密钥、日志或飞行记录器,也绝不会出现在发现/诊断界面上(这些界面只报告环境变量名称和是否存在)。诊断界面会对base_url中的用户信息进行脱敏。请参阅 API 提供商(HTTP)。 - 提示词的静态持久化:普通的非 Kit 异步任务行会以明文形式存储请求提示词(HTTP
payloadJson;以及在提示词作为命令参数而非通过 stdin 流式传递时的 CLIargsJson);这不受密钥脱敏机制的保护。review_changes明确说明了其不同的 CLI 布局:完整的围栏提示词会保留在有时限的payloadJson中,持久化的 argv 只包含其哈希标记,且提示词不会复制到飞行记录器中。Personal Agent Config Kit 的持久化行不会存储编译后的指令或请求参数,并会隐藏提供商的输出和错误,仅保留安全且保护隐私的恢复与完整性状态。在非 Windows 主机上,SQLite 任务存储文件(默认为~/.llm-cli-gateway/logs.db,可通过[persistence].path配置)会设置为chmod的0o600权限;Postgres 后端会将对应字段存储在数据库行中。请将任一后端都视为敏感数据,并像管理任何提示词日志一样限定访问范围并定期轮换。设置[persistence].backend = "none"可完全禁用异步任务存储(此时不会注册*_request_async/llm_job_*工具)。有关其更严格的持久化记录范围,请参阅 Personal Agent Config Kit 指南。 - 命令执行:使用
spawn和独立参数(而非 shell 执行) - 不使用 Eval:我们的源代码中没有动态代码求值(有关传递依赖
ajv的代码生成情况,请参阅下文“Socket 警报”) - 沙箱隔离:生产环境中可考虑在容器内运行
- npm 发布控制:npm 版本发布受生成的仅生产依赖 shrinkwrap、发布安全审计、打包使用者检查,以及使用短期 OIDC 派生 npm 发布凭据的 GitHub Actions Trusted Publishing 把关
- 发布签名:GitHub 发布安装程序工件使用 Sigstore 无密钥签名;请先验证
SHA256SUMS.sigstore.json,再信任校验和文件
Socket 警报——供审核者参考
如果你通过 Socket 或类似的供应链扫描器审核 llm-cli-gateway,会看到行为警报和一些依赖项所有权警报。这些警报准确描述了该软件包的行为及其依赖项。经审核的 shellAccess 和 shrinkwrap 条目已在 socket.yml 中配置,用于仓库/PR 策略检查;但 Socket 的公开软件包页面仍可能在已发布的 npm 工件上显示这些警报。相关原因仍会在此处和软件包中说明。
目前标记的项目并非 2.6.x 新增:2.3.0、2.4.0、2.5.0 和 2.6.3 的 npm tarball 均包含 npm-shrinkwrap.json,也都包含用于运行提供商 CLI 的同一 dist/executor.js 子进程 spawn 功能。2.4.0、2.5.0、2.6.0 和 2.6.3 的 socket.yml 策略在 shellAccess 方面实质上相同;本 README 现还补充了之前缺失的 shrinkwrap 披露。
| 提示 | 位置 | 限定原因 |
|---|---|---|
| 网络访问 | 通过 npm run start:http 启动时,src/http-transport.ts 会打开 HTTP MCP 传输。src/endpoint-exposure.ts 会发起 HEAD 探测,以验证已配置的公共/隧道 URL。Socket 还在 v1.17.2 中标记了 dist/upstream-contracts.js,但这是因为描述性文本,而非网络调用。 | 默认情况下,传输绑定到 127.0.0.1,并要求设置 LLM_GATEWAY_AUTH_TOKEN。默认的 stdio MCP 入口点(npm start)不会打开任何套接字。src/upstream-contracts.ts 存储提供商 CLI 元数据,不会导入任何 HTTP 客户端 API。 |
| Shell 访问 | src/executor.ts 使用 child_process.spawn(cmd, args, …) 调用底层 LLM CLI。 | 调用 spawn 时会传入参数数组,且绝不会使用 shell: true,因此调用方输入不存在 shell 插值路径。命令名称也绝不会来自调用方输入:它要么通过 providerCommandName(cli) 从 CLI_TYPES 成员对应的提供商注册表中得出,要么来自 src/cli-updater.ts 中由网关计算的升级计划(npm/pip/uv/brew)。没有任何 MCP 工具接受二进制名称。请注意,这是每个调用点的属性,而不是 src/executor.ts 内部的成员检查;该文件接收字符串形式的 command,且不会对其进行过滤。 |
| 已发布的 shrinkwrap | npm 制品包含 npm-shrinkwrap.json;package.json#files 包含该文件,且 scripts/make-prod-shrinkwrap.mjs 会根据 package-lock.json 生成它。 | 这是一个 CLI/应用程序包。npm 文档说明,shrinkwrap 适用于通过注册表发布的应用程序、守护进程和命令行工具。我们的 shrinkwrap 是仅包含生产依赖的投影,而非已提交的完整开发锁定文件:scripts/release-security-audit.sh 会验证它与已审计锁定文件的一致性;scripts/verify-registry-install.sh 则证明从注册表全新安装的用户不会获得 better-sqlite3/prebuild-install/tar-fs/tar-stream 生产依赖链。 |
| 使用 eval | 我们的源码中没有。传递依赖:@modelcontextprotocol/sdk → ajv@8 在 ajv/dist/compile/index.js 中使用 new Function(...) 来编译 JSON Schema 验证器。 | 这是 ajv 的标准代码生成路径。流经该路径的只有已知架构(在我们的源码和 MCP SDK 中定义);任何调用方提供的数据都不会进入编译后的函数体。 |
| SQLite 适配器隔离 | SQLite 后端通过单一适配器 src/sqlite-driver.ts 使用 Node 内置的 node:sqlite 模块(无原生绑定、无安装脚本)。 | 生产环境中恰好只有一个模块(该适配器)会接触 node:sqlite;所有其他模块都通过其类型化接口与 SQLite 通信。我们从不调用任何 db.pragma() 助手方法(node:sqlite 中不存在该方法);SQLite 设置使用固定的字面量 db.exec("PRAGMA ...") 语句。若生产代码在适配器之外引用 node:sqlite,或重新引入 .pragma() 调用,npm run security:audit 就会阻止发布。 |
| 依赖归属 | 少数小型传递依赖包(例如通过 @modelcontextprotocol/sdk 引入的 media-typer)会触发 Socket 的“不稳定归属”或“混淆代码”启发式检测。 | 这些是 Node 生态系统中已固定版本、广为人知的微型依赖包,没有已知问题。我们在 package.json#overrides 中固定了 content-type 和 type-is 的直接覆盖版本。从 2.0.0 起,生产依赖关系图中不再包含原生模块(better-sqlite3 已移至 devDependencies;node:sqlite 内置于 Node 中),因此彻底消除了 prebuild-install/tar-fs/tar-stream 安装时依赖链。我们之前对 [email protected] 的直接依赖已替换为 smol-toml。 |
请参阅 socket.yml,获取相同内容的机器可读版本。 |
贡献
- Fork 此仓库
- 创建功能分支
- 进行修改
- 运行测试:
npm test - 构建:
npm run build - 提交 pull request
许可证
MIT。详情请参阅 LICENSE。
支持
如有问题或疑问:
- 在 GitHub 上提交 issue
- 查看现有 issue 和文档
- CLI 相关问题请查阅 CLI 专属文档
更新日志
详细的发布历史请参阅 CHANGELOG.md。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。