开发者文档 / 即将开放

清晰地构建。

兼容 OpenAI 的 API,模型路由公开,数据处理方式明明白白。

当前进展

API 已构建并测试完毕。在第一条模型路由开放之前,聊天请求会返回 503 inference_not_configured。实时状态始终可在 https://undergrove.vercel.app/api/status 查看。

它遵循 OpenAI API 格式,因此现有的 SDK 只需更改基础 URL 即可使用:支持带图片的聊天、函数调用和结构化输出,以及嵌入、转录和语音。要让请求连我们的网关也无法读取,请像工作区一样使用加密模式。

快速入门

基础 URL:https://undergrove.vercel.app/v1。用 GET /v1/models 列出模型,然后以流式方式获取回复:

request.shShell
curl https://undergrove.vercel.app/v1/chat/completions \
  -H "Authorization: Bearer $UNDERGROVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<model-id>",
    "stream": true,
    "messages": [{"role": "user", "content": "Help me explore an idea."}]
  }'
示例响应示例;数值已缩短

HTTP/2 200

9 个标头
content-type
text/event-stream; charset=utf-8
cache-control
no-store
x-undergrove-route
tinfoil
x-undergrove-attestation
verified-by-gateway
x-undergrove-enclave-host
inference.tinfoil.sh
x-undergrove-enclave-measurement
c0ffee12…9a7d
x-undergrove-enclave-key
4f2b81e0…d3c6
x-undergrove-attestation-sha256
8e41c7aa…01fb
x-undergrove-attestation-url
/v1/attestation?sha256=8e41c7aa…01fb

    目前的回答

    只改一行,从 OpenAI 迁移过来

    Undergrove 支持 OpenAI API,因此官方 SDK 可以直接使用。在创建客户端的那一行代码中,填入 Undergrove 的基础 URL 和 Undergrove 密钥,然后指定这里提供的某个开放模型即可。

    import os
    from openai import OpenAI
     
    client = OpenAI(base_url="https://undergrove.vercel.app/v1", api_key=os.environ["UNDERGROVE_API_KEY"])
     
    response = client.chat.completions.create(
        model="<model-id>",
        messages=[{"role": "user", "content": "Summarise this contract in three points."}],
    )
    print(response.choices[0].message.content)
    

    需要更改 2 行:客户端的基础 URL 和密钥,以及模型。

    也可以完全不改代码:在创建客户端时未传入这两项的情况下,两个 SDK 都会读取 OPENAI_BASE_URL 和 OPENAI_API_KEY,因此只要把它们分别设为 https://undergrove.vercel.app/v1 和你的密钥,就能原样切换应用。流式输出、图片、函数调用和结构化输出按下文所述工作,适用于支持它们的模型。

    开发者工具

    在你自己的机器上验证安全飞地并加密的客户端。每个客户端都会先检查 Tinfoil 安全飞地的证明,再将每个请求加密发送到该安全飞地,因此 Undergrove 的中继在转发时无法读取它。它们位于仓库的 packages/ 文件夹中,针对本地替身服务进行测试,目前尚未发布到任何包注册表。

    试用 CLI

    这个终端会重放真实的 undergrove 命令。它的输出录制自用于测试这些包的本地替身服务:verify 是对一份录制的 Tinfoil 证明进行的真实检查,models 列出当前的模型目录,而 ask 的回答是预先写好的。此页面背后没有模型,你输入的任何内容都不会离开此页面。

    滚动到终端时才会加载。

    示例应用

    packages/examples 中的三个小型 Web 应用,每个都由一个页面和一个持有密钥的 Node 服务器组成。没有密钥时,它们针对本地替身服务运行并使用预先写好的回答,任何内容都不会离开你的电脑;设置 UNDERGROVE_API_KEY 后,同样的代码会验证安全飞地,并将每个请求加密发送给它。

    根据帮助中心文件作答的聊天,以服务器发送事件的形式流式传送到页面。

    npm run support-bot位于 packages/examples;源代码在 packages/examples/support-bot

    const SYSTEM = `You are the support assistant for Acme Bikes.
    Answer only from the help centre below. Quote prices and times exactly.
    When it doesn't cover a question, say so and offer the contact options.
    Keep answers short: two to four sentences, or a short list.
    
    ${knowledge}`;
    
    // Streams one answer. `history` is the conversation so far, as
    // [{ role: "user" | "assistant", content }].
    export async function answer(undergrove, history, send) {
      const stream = await undergrove.chat.completions.create({
        model,
        stream: true,
        max_tokens: 600,
        messages: [{ role: "system", content: SYSTEM }, ...history.slice(-12)],
      });
      for await (const chunk of stream) {
        const text = chunk.choices[0]?.delta?.content;
        if (text) send({ text });
      }
    }

    在你的编辑器和 CI 中

    • Node SDK@undergrove/sdk:OpenAI 风格的聊天、流式输出、嵌入、转录和语音,默认加密。packages/sdk-js
    • Python SDKundergrove,基于 Tinfoil 的 Python SDK,同样默认加密。packages/sdk-python
    • CLIundergrove ask、models 和 verify,密钥保存在系统钥匙串中。面向编辑器:--events 以 JSON 行流式输出,acp 将其作为智能体运行,mcp 将其作为工具运行。packages/cli
    • VS Code一个聊天面板,以及针对眼前代码的“Explain selection”“Fix this”(含“Apply fix”)和“Write tests”。packages/vscode
    • JetBrains IDEs在 IntelliJ IDEA、PyCharm 等 IDE 中提供工具窗口聊天和同样的三个操作,密钥保存在 IDE 的密码保险库中。packages/jetbrains
    • ZedUndergrove 作为 Agent Panel 中的智能体,支持 /explain、/fix、/tests 和 /review,或作为 Zed 自带智能体的工具。两者都运行 CLI,在你的机器上完成加密。packages/zed
    • Neovim:UndergroveAsk,以及对可视选区使用 :UndergroveExplain,回答会流式显示在浮动窗口中。packages/neovim
    • Raycast在任何应用中使用“Ask Undergrove”和“Ask About Selected Text”。packages/raycast
    • GitHub ActionUndergrove Review:每个拉取请求一次审查,包含行内评论和建议,并在页脚附上证明。packages/github-action

    试验场

    在此页面用你自己的密钥发送聊天请求,查看回答、用量和原始响应,以及对应的 curl 请求。它和其他 API 调用一样,从你的浏览器发送到本站的 /v1,并照常消耗你的配额或额度。

    选择“加密”即可按 SDK 加密模式的方式发送:Tinfoil 的 SDK 在此页面中验证安全飞地,然后用它的密钥密封请求。试验场会显示实际离开页面的字节、返回的内容,以及请求所密封到的安全飞地的回执。

    只保存在此页面的内存中,并且只发送到 https://undergrove.vercel.app/v1。重新加载或离开页面后即被清除。设有每日上限的密钥可以限制误操作可能造成的花费。

    路径

    加密模式需要在安全飞地中运行的模型;这个模型不是。

    API 暂时还不会回答:在第一条模型路由开放之前,请求会返回 503 inference_not_configured。你仍然可以构建请求,并将其复制为 curl 命令。

    消息

    在消息中按 Ctrl 或 ⌘ 加 Enter 也可以发送。

    用 curl 发送同一请求

    curl --no-buffer https://undergrove.vercel.app/v1/chat/completions \
      -H "Authorization: Bearer $UNDERGROVE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "<model-id>",
        "messages": [
          {
            "role": "user",
            "content": "帮我探索一个想法。"
          }
        ],
        "stream": true
      }'

    身份验证

    以 Bearer token 的形式发送 Undergrove API 密钥。密钥以 ugk_ 开头,内测期间通过邀请发放。在开启了自助密钥的地方,内测成员可在工作区设置中创建最多五个密钥,每个密钥都会消耗所属账户的配额和额度。我们只存储每个密钥的单向哈希,因此丢失的密钥无法找回,只能更换。

    每个自助密钥都可以有自己的限制,可在创建时或之后在设置中配置:每日支出上限(达到后,该密钥的请求会被拒绝,直到 UTC 午夜),以及它可以使用的模型列表。设有上限的密钥仍会计入所属账户的配额。在限制功能推出之前创建的密钥没有限制,照常使用。

    models.shShell
    curl https://undergrove.vercel.app/v1/models \
      -H "Authorization: Bearer $UNDERGROVE_API_KEY"

    端点

    端点用途
    GET /v1/models当前可用的模型,每个都附有类型(聊天、嵌入、转录或语音)、许可证、上下文长度、价格、推理级别、对图片、工具和网页搜索的支持情况、节奏(快速或审慎),以及运行位置。
    POST /v1/chat/completionsChat, streamed or as a single response, with images, function calling and structured outputs where the model supports them.
    POST /v1/embeddings由 Nomic Embed Text v1.5 生成的向量(768 维),用于搜索和相似度计算。最多 256 个输入。每 48 个输入或 67,200 个字符收费 $0.002,以较多者为准。
    POST /v1/audio/transcriptions由 Whisper Large v3 Turbo 生成的转录文本。以 multipart 上传,最大 4.4 MB,可选指定语言和提示词。每个起始兆字节收费 $0.01。
    POST /v1/audio/speech由 Qwen3-TTS 生成的 WAV 音频,共九种声音:aiden、dylan、eric、ono_anna、ryan、serena、sohee、uncle_fu 和 vivian。最多 4,096 个字符。每起始 600 个字符收费 $0.005。
    GET /api/status服务状态、用量限制和完整的模型登记表。

    响应使用标准的 chat.completion 结构。流式响应发送 chat.completion.chunk 事件,并以 data: [DONE] 结束。推理模型会在 delta.reasoning_content 中单独流式输出推理过程,最后一个分块会报告 token 用量。如果流被中断,最后一个事件会是一个错误对象而不是 [DONE],因此不完整的回答绝不会被误认为完整的回答。

    embeddings.shShell
    curl https://undergrove.vercel.app/v1/embeddings \
      -H "Authorization: Bearer $UNDERGROVE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "undergrove/nomic-embed-text",
        "input": ["Where is the signed contract?", "Payment terms"]
      }'

    参数

    字段类型默认含义
    modelstring必填来自 /v1/models 的 ID。
    messagesarray必填最多 200 条 system、user、assistant 和 tool 消息。对于支持视觉的模型,user 消息最多可包含 16 个 image_url 部分,格式为 base64 数据 URL(PNG、JPEG、GIF 或 WebP);不会抓取链接。除非模型另有报告,每张图片按 3,000 个输入 token 计算。
    tools, tool_choice, parallel_tool_callsarray, string | object, boolean—函数调用,适用于在 /v1/models 中标有工具支持的模型:最多 128 个函数。流式调用以 tool_calls 增量的形式到达,回答以 finish_reason tool_calls 结束。将结果作为 tool 消息连同其 tool_call_id 发回。
    response_formatobject—结构化输出:{"type": "json_object"},或带有 name 和 schema 的 json_schema。
    streambooleanfalse发送 true 即可以服务器发送事件的形式流式输出回答。
    max_tokensinteger模型的上限也可写作 max_completion_tokens。上限为模型的输出限制。
    temperature, top_pnumber由模型决定采样控制:0–2 和 0–1。
    reasoning_effortstring由模型决定推理模型在回答前思考的时长。可接受的值因模型而异,列在 /v1/models 中;工作区的“快速”和“深入”使用列出的 fast 和 thorough 值。
    web_search_optionsobject—发送 {} 即可让模型进行网页搜索。查询会经过筛查以去除个人信息,结果会经过筛查以防范提示词注入。来源以 url_citation 注释的形式返回。每次搜索请求从你的配额中扣除 $0.05。
    stopstring | string[]—一个字符串,或最多四个字符串。
    presence_penalty, frequency_penalty, seednumber, integer—在模型路由支持时原样传递。
    tools.shShell
    curl https://undergrove.vercel.app/v1/chat/completions \
      -H "Authorization: Bearer $UNDERGROVE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "<model-id>",
        "max_tokens": 500,
        "messages": [{"role": "user", "content": "Is it raining in Lisbon?"}],
        "tools": [{
          "type": "function",
          "function": {
            "name": "get_weather",
            "description": "Current weather for a city",
            "parameters": {
              "type": "object",
              "properties": {"city": {"type": "string"}},
              "required": ["city"]
            }
          }
        }]
      }'

    加密模式

    标准 API 由我们的网关在内存中读取,以检查每个请求。要让请求对 Undergrove 也不可读,请用你的密钥搭配 Tinfoil 的 SDK。它会验证安全飞地的硬件证明,然后加密每个请求,使只有该安全飞地能读取。我们的中继检查你的密钥和配额,并把密文转发出去。

    1. 你的代码Tinfoil 的 SDK 先验证安全飞地,然后将每个请求加密发送给它。可以读取请求
    2. 已密封至安全飞地Undergrove 中继检查密钥和配额,把密文转发出去。无法读取内容。只能看到密文
    3. 仍处于密封状态模型安全飞地唯一解密请求并运行模型的地方。可以读取请求
    加密模式:你的代码自行验证安全飞地,请求在进入安全飞地之前始终保持密封。
    encrypted.mjsJavaScript
    import { SecureClient } from "tinfoil"; // npm install tinfoil
    
    const RELAY = "https://undergrove.vercel.app/api/enclave";
    const client = new SecureClient({
      baseURL: `${RELAY}/v1/`,
      attestationBundleURL: RELAY,
    });
    await client.ready(); // verifies the enclave; throws if it can't
    
    const response = await client.fetch("chat/completions", {
      method: "POST",
      headers: {
        authorization: `Bearer ${process.env.UNDERGROVE_API_KEY}`,
        "content-type": "application/json",
      },
      body: JSON.stringify({
        model: "gemma4-31b", // enclave_model in /v1/models
        max_tokens: 1000,
        messages: [{ role: "user", content: "Summarise this contract." }],
      }),
    });
    console.log((await response.json()).choices[0].message.content);

    同一个客户端还可以访问 embeddings、audio/transcriptions 和 audio/speech。由于中继无法读取这些请求,模型需以 /v1/models 中的 enclave_model ID 指定,回答以 Tinfoil 的格式返回,用量则根据加密数据的大小估算。配额和额度照常适用。

    MCP 服务器

    Undergrove 同时也是一个 Model Context Protocol 服务器,地址为 https://undergrove.vercel.app/mcp。每个实时模型都是一个工具,例如 ask_<model>,Claude Code、Cursor 和 Claude Desktop 等 MCP 客户端可以用你的 API 密钥调用它。工具接受一个 prompt,以及可选的 system、max_tokens,对于能搜索的模型还有 web_search,并以文本形式返回模型的回答。

    工具调用和其他 API 请求一样:网关在内存中读取它,通过模型公开的路由发送出去,不保留副本,并计入你密钥的限制、配额和额度。服务器使用可流式 HTTP,以 JSON 回答,不保留会话。它支持的协议版本为 2025-11-25、2025-06-18 和 2025-03-26。

    Claude Code

    在终端中运行一次:

    terminalShell
    claude mcp add --transport http undergrove https://undergrove.vercel.app/mcp \
      --header "Authorization: Bearer ugk_your_key"

    Cursor

    写入 ~/.cursor/mcp.json,或项目中的 .cursor/mcp.json:

    mcp.jsonJSON
    {
      "mcpServers": {
        "undergrove": {
          "url": "https://undergrove.vercel.app/mcp",
          "headers": {
            "Authorization": "Bearer ugk_your_key"
          }
        }
      }
    }

    Claude Desktop

    写入 claude_desktop_config.json(Settings › Developer › Edit Config)。Claude Desktop 通过 mcp-remote 连接远程服务器,这需要 Node.js:

    claude_desktop_config.jsonJSON
    {
      "mcpServers": {
        "undergrove": {
          "command": "npx",
          "args": [
            "-y",
            "mcp-remote",
            "https://undergrove.vercel.app/mcp",
            "--header",
            "Authorization:${AUTH_HEADER}"
          ],
          "env": {
            "AUTH_HEADER": "Bearer ugk_your_key"
          }
        }
      }
    }

    任意客户端

    也可以直接调用。它通过可流式 HTTP 使用 JSON-RPC 通信:

    tools-list.shShell
    curl https://undergrove.vercel.app/mcp \
      -H "Authorization: Bearer ugk_your_key" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'

    然后调用某个模型的工具:

    tools-call.shShell
    curl https://undergrove.vercel.app/mcp \
      -H "Authorization: Bearer $UNDERGROVE_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -d '{
        "jsonrpc": "2.0",
        "id": 2,
        "method": "tools/call",
        "params": {
          "name": "ask_<model>",
          "arguments": {"prompt": "Name three uses for a sealed envelope."}
        }
      }'

    反过来,工作区也可以使用你在设置中连接的 MCP 服务器提供的工具。这些调用经由 Undergrove 的服务器以明文发送到你连接的服务器,因此请只使用你放心交给其工具相应内容的服务器。

    错误与限制

    错误使用 OpenAI 格式:{"error": {"message", "type", "code"}}。

    状态错误码
    400invalid_request, invalid_json, unsupported_parameter, unsupported_content, context_length_exceeded, model_rejected_request, unsupported_api_version, async_needs_webhook, async_stream, invalid_webhook_url
    401authentication_required、invalid_api_key、invalid_session、sandbox_key(ugk_sandbox_ 密钥,只有本地替身服务会响应)
    403alpha_access_required、model_not_allowed_for_key(该密钥仅限使用其他模型)
    404model_not_found, webhook_not_found, thread_not_found, assistant_not_found, prompt_not_found, version_not_found
    409idempotency_in_progress(使用此 Idempotency-Key 的第一个请求仍在运行)、version_conflict(密封条目在读取后已被更改)、id_taken
    413request_too_large(聊天和转录超过 4.4 MB,嵌入超过 1 MB,语音超过 100 KB)
    422idempotency_key_reused(同一个 Idempotency-Key 用于不同的请求)
    429rate_limited、budget_exhausted、capacity_exhausted、key_budget_exceeded(该密钥已达到自身的每日上限;消息中会注明重置时间)。请按 Retry-After 标头等待,在工作区设置中添加额度,或在那里提高该密钥的上限。
    502model_unavailable, stream_interrupted
    503capacity_exhausted(附带 Retry-After)、inference_not_configured、realtime_not_available(某条音频路由未通过证明)
    504model_timeout

    内测期间,每个密钥或账户都有每分钟请求数、每日请求数和每日支出配额。当前数值发布在 /api/status。

    429.jsonJSON
    HTTP/2 429
    retry-after: 41
    
    {
      "error": {
        "message": "You’re sending messages quickly. Please wait a moment.",
        "type": "rate_limit_error",
        "code": "rate_limited"
      }
    }

    了解你的数据去向

    1. 你的服务器使用你的密钥发送请求。可以读取请求
    2. HTTPSUndergrove 网关检查密钥和限制,在内存中读取请求,不保留副本。可以读取请求
    3. 加密发送到已验证的安全飞地模型安全飞地Tinfoil 的硬件安全飞地在公开的路由上运行模型。可以读取请求
    标准 API:网关在内存中读取每个请求以进行检查,然后将其加密发送到它验证过的安全飞地。

    网关检查你的密钥、执行限制,并只把上述字段转发到模型路由。它不保留提示词或回答的副本,也不记录它们。其日志只记录路由名称、状态码和耗时。速率限制计数器使用你的密钥或账户的单向哈希,并在两天内过期。

    每条模型路由的运营方、所在地和数据保留方式都发布在 /api/status 的模型登记表和隐私页面上。请求绝不会回退到其他路由。我们的托管方 Vercel 会保留 IP 地址等标准请求元数据。

    在机密计算路由上,API 网关在建立连接前会验证安全飞地的硬件证明,并加密每个请求,使只有该安全飞地能读取。但网关本身仍会在内存中读取 API 请求以进行检查。

    工作区数据

    同步、共享链接和共享项目只存储浏览器生成的密文。每张图都逐步展示谁持有密钥。

    验证能证明什么

    在发送任何内容之前,工作区会在你的浏览器中运行 Tinfoil 的开源验证器。它对照 AMD 或 Intel 的签名密钥检查安全飞地的硬件证明报告,检查安全飞地中度量的代码是否与 Tinfoil 在 GitHub 上发布并签名的版本一致,并检查所用的加密密钥是否属于该安全飞地。在工作区中选择已验证,即可查看每一项检查、版本和度量值。

    它证明你的消息只能由运行该公开代码的那台硬件解密。它不能证明公开的代码没有缺陷,不能防范处理器本身的弱点,也不涵盖回答到达你的浏览器之后你如何使用它们。工作区代码由 Undergrove 提供,因此你需要信任我们按公开版本提供验证器;验证此页面会把你的浏览器加载的代码与构建清单进行比较,npm run verify:site 则会把网站与你自己基于同一提交构建的版本进行比较。我们计划进行独立评估。

    来自安全飞地的每个回答都有一个回执按钮。它会显示在发送该请求之前验证过的安全飞地、版本和度量值,以及请求加密所用的密钥,并可将它们下载为 JSON。在标签页关闭之前,下载内容包含证明报告,你以后可以再次对其运行验证器;之后则包含报告的哈希,以及获取该安全飞地当前报告的位置。回执是你浏览器的记录,而不是签名声明,其中列出的安全飞地是 Tinfoil 的模型路由器;路由器会把请求转交给模型自己的安全飞地。回执也会列出该安全飞地,你的浏览器会尝试以同样的方式验证它。大多数模型安全飞地运行在 Intel TDX 上,浏览器验证器目前还无法检查;对于这些安全飞地,回执会注明这一点,检查则依赖路由器,其公开代码会在使用每个模型安全飞地之前对其进行验证。

    attestation.shShell
    # The attestation bundle the gateway verified, what it checked
    # in it, and the steps to check it again yourself
    curl https://undergrove.vercel.app/v1/attestation

    可验证的响应

    对于在安全飞地中运行的模型,每个 API 响应都会说明网关在发送你的请求前验证了哪个安全飞地,GET /v1/attestation 则会返回其背后的证明包,因此你可以对同样的证据运行 Tinfoil 的开源验证器。

    标头数值
    x-undergrove-attestationverified-by-gateway
    x-undergrove-enclave-host经过证明的安全飞地:Tinfoil 的模型路由器
    x-undergrove-enclave-measurement代码度量值,与已签名的版本相匹配
    x-undergrove-enclave-release版本标签(以及 -release-digest,即其公开的摘要)
    x-undergrove-enclave-key请求加密所用的安全飞地公钥
    x-undergrove-attestation-sha256证明报告的 SHA-256
    x-undergrove-attestation-url获取证明包的位置:/v1/attestation?sha256=…

    任何客户端都可以读取它们。使用 OpenAI Python SDK 时,请求原始响应即可:

    headers.shShell
    curl -si https://undergrove.vercel.app/v1/chat/completions \
      -H "Authorization: Bearer $UNDERGROVE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"model": "<model-id>", "messages": [{"role": "user", "content": "Say hello."}]}' \
      | grep -i '^x-undergrove-'

    要检查某个响应,这个 Node 脚本会发送请求,获取其标头所指明的证明包,用 @tinfoilsh/verifier 进行验证,并检查密钥、度量值、安全飞地和报告哈希是否全部一致。上方的试验场在你的浏览器中通过在此浏览器中检查做同样的事。

    verify-response.mjs
    verify-response.mjsJavaScript
    // Checks the attestation evidence on an Undergrove API response yourself.
    //
    //   npm install @tinfoilsh/verifier
    //   UNDERGROVE_API_KEY=ugk_… node verify-response.mjs
    import { createHash } from "node:crypto";
    import { Verifier } from "@tinfoilsh/verifier";
    
    const base = process.env.UNDERGROVE_BASE_URL || "https://undergrove.vercel.app/v1";
    const model = process.env.UNDERGROVE_MODEL || "undergrove/gpt-oss-120b";
    
    // 1. Each response from an attested route names the enclave the gateway
    //    verified and encrypted the request to.
    const response = await fetch(`${base}/chat/completions`, {
      method: "POST",
      headers: {
        authorization: `Bearer ${process.env.UNDERGROVE_API_KEY}`,
        "content-type": "application/json",
      },
      body: JSON.stringify({
        model,
        max_tokens: 50,
        messages: [{ role: "user", content: "Say hello." }],
      }),
    });
    if (!response.ok) throw new Error(`The API answered ${response.status}.`);
    const header = (name) => response.headers.get(`x-undergrove-${name}`);
    const claim = {
      host: header("enclave-host"),
      key: header("enclave-key"),
      measurement: header("enclave-measurement"),
      report: header("attestation-sha256"),
    };
    if (!claim.key) throw new Error("This response carries no attestation evidence.");
    
    // 2. Fetch the attestation bundle it names. Without a report hash, the
    //    route's current bundle (its key must still match).
    const query = claim.report ? `?sha256=${claim.report}` : "";
    const { bundle } = await (await fetch(`${base}/attestation${query}`)).json();
    
    // 3. Verify the bundle with Tinfoil's open-source verifier: the chip
    //    maker's signature on the report, the release's signed build record
    //    from GitHub, and the enclave's certificate. It throws if any fails.
    const verifier = new Verifier({ configRepo: "tinfoilsh/confidential-model-router" });
    const attested = await verifier.verifyBundle(bundle);
    const verified = verifier.getVerificationDocument();
    
    // 4. The verified bundle must attest exactly what the response claimed.
    const { format, body } = bundle.enclaveAttestationReport;
    const hash = createHash("sha256").update(format + body).digest("hex");
    const checks = [
      ["report hash", !claim.report || hash === claim.report],
      ["encryption key", attested.hpkePublicKey === claim.key],
      ["code measurement", verified.codeFingerprint === claim.measurement],
      ["enclave host", verified.enclaveHost === claim.host],
    ];
    for (const [name, ok] of checks) if (!ok) throw new Error(`The ${name} doesn’t match the response.`);
    
    console.log(
      `Verified: ${verified.enclaveHost} runs ${verified.configRepo} ${verified.releaseTag || ""},` +
        ` and its attested key is the one this response names (${claim.key.slice(0, 16)}…).`,
    );
    

    检查通过说明什么

    说明确实存在一个持有该密钥、运行该度量代码的安全飞地:真正的 AMD 或 Intel 机密计算硬件,运行着 Tinfoil 在 GitHub 上发布并签名的版本。同时说明网关声称它在转发你的请求之前验证了该安全飞地,并用其密钥加密了你的请求。

    不能说明什么

    不能说明网关确实用该密钥加密了你的请求。这些标头是网关的报告,而不是由安全飞地签名的声明,而且网关会在内存中读取 API 请求以进行检查。如果想依靠你自己的验证,请使用加密模式,由你的客户端自行检查安全飞地并加密每个请求。这里列出的安全飞地是 Tinfoil 的模型路由器,它会把请求转交给模型自己的安全飞地。

    网站助手

    只需一个 script 标签,就能在你自己的网站上放一个助手。它会添加一个按钮,点开后是聊天面板,由你选择的开放模型作答,支出不超过你设定的每日上限。面板是由 Undergrove 提供的框架,因此你的页面无法读取访客输入的内容。

    1. 在工作区中打开设置,在网站助手下创建一个可公开密钥:设置名称、模型、可以使用它的网站,以及每日上限。
    2. 复制生成的代码,粘贴到每个页面的 </body> 之前。
    3. 先在演示页面上试试,那是一家虚构的商店。
    index.htmlHTML
    <script
      src="https://undergrove.vercel.app/embed.js"
      data-key="ugp_…"
      data-model="&lt;model-id&gt;"
      data-instructions="我们是一家陶艺工作室。帮忙解答课程、营业时间和配送方面的问题。"
      async
    ></script>
    属性类型默认含义
    data-keystring必填你的可公开密钥,ugp_…
    data-modelstring必填该密钥所列模型之一。
    data-titlestring“Ask a question”面板的标题。
    data-greetingstring—访客看到的第一条消息。
    data-instructionsstring—你的网站是关于什么的、需要帮忙解答哪些问题,以及语气。随每个问题一起发送;访客可以在你的页面中看到它。
    data-labelstring“Ask”按钮上的文字。
    data-positionstringrightright 或 left。
    data-themestringautoauto(跟随访客的设备)、light 或 dark。
    data-openbooleanfalse设为 true 时,页面加载后即打开面板。

    为什么可公开密钥可以放在公开页面中

    • 它只能用于聊天,而且只能从 Undergrove 自己的助手框架中使用。在其他任何地方,API 都会拒绝它。
    • 只有当框架嵌入在你列出的某个网站中时,它才有效。浏览器会报告是哪个页面嵌入了框架,服务器会进行检查。
    • 它始终设有每日支出上限和模型列表。浏览器之外的程序可以冒充任何网站,因此请把这个密钥视为公开的,并把上限设为你愿意一天内花费的金额。
    • 每位访客都有每分钟和每日的提问次数限制,发布在 /api/status。 访客按其 IP 地址的带密钥单向哈希计数,从不记录地址本身,计数在两天内过期。

    和任何密钥一样,回答会消耗你账户的配额和额度。对于在安全飞地中运行的模型,框架会像工作区一样,在访客的浏览器中把每条消息加密发送到已验证的安全飞地;其他模型则经由网关访问,网关在内存中读取请求,不保留副本。你的网站两者都看不到。

    “Verified by Undergrove”徽章

    带有助手的网站可以显示一个小徽章,链接到 Undergrove 为该密钥提供的页面。该页面从 Undergrove 的服务器读取密钥对应的网站和模型,在访客的浏览器中验证其背后的安全飞地,并说明徽章能证明什么、不能证明什么。把这个标签放在徽章应出现的位置。

    或者不用脚本,只用一张图片和一个链接(深色页面使用 badge-dark.svg):

    badge.htmlHTML
    <script src="https://undergrove.vercel.app/badge.js" data-key="ugp_…" async></script>
    • 徽章只是一个链接。它不会从你的页面发出任何请求,自身也不做任何检查;检查由它打开的页面完成。
    • 它会写明你的可公开密钥,而这个密钥本来就为了助手放在你的页面中。徽章页面显示该密钥的网站和模型,从不显示账户、上限或已花费的金额。
    • 该链接会请求浏览器发送你网站的来源,这样页面就能告诉访客他们是否来自该密钥所列的网站之一。如果徽章被复制到其他网站,页面会在那里如实说明。
    • 它只为助手作担保,不涉及你网站的其他部分。

    集成

    Undergrove 自己的大多数客户端都会先用 Tinfoil 的验证器检查安全飞地的证明,然后把每个请求加密发送到该安全飞地,因此中继在转发时无法读取它。每个客户端都使用你的 API 密钥和 Undergrove 的模型 ID。它们尚未发布到包注册表;每个都可以从本站仓库的 packages/ 构建。

    Rust、Java 和 Kotlin 客户端以及 Make 应用改用开发者 API,因为没有可供它们构建的 Tinfoil SDK:网关在内存中读取每个请求,自行验证安全飞地,并在回答的标头中报告它验证了什么。

    包适用于作用
    @undergrove/sdkNodeOpenAI 风格的客户端,支持流式输出、工具、嵌入、转录和语音。
    undergrovePythonPython 客户端,并以附加组件形式提供 LangChain(ChatUndergrove)和 LlamaIndex(UndergroveLLM)支持。
    GoGo基于 Tinfoil Go 验证器的客户端,支持流式输出和工具调用。
    UndergroveSwift适用于 macOS 14 和 iOS 17 的 Swift 包,基于 Tinfoil 的 Swift 验证器。
    @undergrove/ai-sdk-providerVercel AI SDKgenerateText、streamText 和工具,每个回答的 provider 元数据中都附有验证过的内容。
    @undergrove/langchainLangChain.js聊天模型和嵌入。
    @undergrove/reactReact聊天组件和 hooks,以及一个让 API 密钥不进入浏览器的服务器路由。
    create-undergrove-app入门npm create undergrove-app:一个私密聊天应用,两条命令即可部署到 Vercel。
    @undergrove/agents智能体一个工具调用循环,记录每一步由哪个已验证的安全飞地作答,形成任何人都能检查是否被篡改的记录,并支持固定安全飞地。
    undergroveRust开发者 API 的客户端,支持流式输出和工具调用,并为每个回答附上网关的证明材料。它自身不会把请求加密发送到安全飞地。
    dev.undergrove:undergroveJava 和 Kotlin开发者 API 的客户端,支持流式输出、工具和嵌入,并为 Kotlin 提供协程和 Flow。不是加密模式:网关会在内存中读取请求。
    undergrove[jupyter]Jupyter%%undergrove 单元格魔法命令,以笔记本变量和 DataFrame 作为上下文,并附带证明小组件。
    @undergrove/officeExcel 和 Word在任意单元格中使用 =UNDERGROVE.ASK(),在 Word 中对所选内容进行总结、改写或提问。
    @undergrove/officeOutlook总结当前打开的邮件,并起草回复,由你检查后再发送。
    undergrove-obsidianObsidian就当前打开的笔记提问、总结、改写和续写,每个回答下方都显示已验证的安全飞地。仅限桌面版。
    Undergrove for AlfredAlfred输入 ug 和一个问题:回答以大字显示并复制到剪贴板,经由 undergrove CLI 加密。
    n8n-nodes-undergroven8n社区节点:向模型提问、创建嵌入和列出模型,每个条目都附有验证过的内容。
    Undergrove for ZapierZapier“Ask a Model”和“Create Embeddings”操作,每个请求都从 Zap 的进程中加密发送到安全飞地。
    Undergrove for MakeMake基于开发者 API 的自定义应用:网关验证安全飞地,并在每个模块的输出中报告它验证了什么。
    Undergrove Enclave PinGitHub Actions一个步骤:在 runner 上用 Tinfoil 的验证器验证安全飞地,如果不是你固定的那个,就让任务失败。

    使用 Vercel AI SDK:

    summarise.mjsJavaScript
    import { streamText } from "ai";
    import { undergrove } from "@undergrove/ai-sdk-provider";
    
    const result = streamText({
      model: undergrove("undergrove/gemma-4-31b"),
      prompt: "Summarise this contract.",
    });
    for await (const text of result.textStream) process.stdout.write(text);

    使用 React 组件和入门模板时,消息从页面发送到你自己的服务器,由服务器进行密封;和任何服务器端集成一样,运行该服务器的人可以读取这些消息。

    在 CI 中测试

    用于测试这些包的替身服务会响应沙盒密钥:任何以 ugk_sandbox_ 开头的密钥。你的测试可以使用这样的密钥,无需机密也不产生费用,而 Undergrove 本身会以 sandbox_key 拒绝它们。它的回答是预先写好的,并非来自模型。在 GitHub Actions 中,用一个步骤启动它,并设置 UNDERGROVE_BASE_URL 和 UNDERGROVE_API_KEY:

    .github/workflows/test.ymlYAML
    - uses: ekempinski/undergrove/packages/github-action/sandbox@main
    - run: npm test   # UNDERGROVE_API_KEY=ugk_sandbox_ci
    
    # And before a deploy: fail if the enclave isn’t the one you pinned.
    - uses: ekempinski/undergrove/packages/github-action/pin@main
      with:
        measurement: 9134838da8aa4e1792566b2988820340356517c4db0c94261eb8154843e0357554ba72fcf185f950d9301fbede574c90

    接下来

    已提供Workspace with encryption from your browser to the enclave, streaming gateway, OpenAI-compatible API with images, function calling, structured outputs, embeddings, transcription and speech, encrypted API mode, web search, documents, folders and codebases, images, audio transcripts, read-aloud, search across long documents, voice input, model register, saved conversations and export on your device.
    下一个第一条模型路由、内测登录、共享用量限制。
    已构建,尚未开启自助密钥、付费套餐,以及用于超出每日配额部分的预付额度。
    计划中团队访问。
    需要验证对工作区、中继和网关进行独立安全评估。