ابنِ بوضوح.
واجهة API متوافقة مع OpenAI، بمسارات نماذج منشورة وتعامل واضح مع البيانات.
أين وصلنا
بُنيت واجهة API واختُبرت. إلى أن يُفتح أول مسار للنماذج، تُرجع طلبات المحادثة 503 inference_not_configured. والحالة المباشرة متاحة دائمًا على https://undergrove.vercel.app/api/status.
تتبع تنسيق واجهة OpenAI API، لذا تعمل حزم SDK الحالية بمجرد تغيير عنوان URL الأساسي: المحادثة مع الصور، واستدعاء الدوال، والمخرجات المنظَّمة، إضافةً إلى التضمينات والتفريغ النصي والكلام. ولإبقاء الطلبات غير قابلة للقراءة حتى من بوابتنا، استخدم الوضع المشفَّر، كما تفعل مساحة العمل.
البدء السريع
عنوان URL الأساسي: https://undergrove.vercel.app/v1. اعرض النماذج عبر GET /v1/models، ثم ابثّ ردًّا:
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 الرسمية كما هي. امنح العميل عنوان URL الأساسي لـ Undergrove ومفتاحًا من 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)
يتغير سطران: عنوان URL الأساسي للعميل ومفتاحه، والنموذج.
أو لا تغيّر أي شيفرة على الإطلاق: تقرأ حزمتا SDK كلتاهما OPENAI_BASE_URL وOPENAI_API_KEY عند إنشاء العميل دونهما، لذا فإن ضبطهما على https://undergrove.vercel.app/v1 وعلى مفتاحك ينقل التطبيق كما هو. يعمل البث والصور واستدعاء الدوال والمخرجات المنظَّمة كما هو موضح أدناه، للنماذج التي تدعمها.
أدوات المطوّرين
عملاء يتحققون من الجيب الآمن ويشفّرون على جهازك أنت. يتحقق كل منهم أولًا من تصديق الجيب الآمن لدى Tinfoil، ثم يشفّر كل طلب إلى ذلك الجيب الآمن، فيمرّره المُرحِّل في Undergrove دون أن يتمكن من قراءته. توجد في المجلد packages/ في المستودع، وتُختبر مقابل بديل محلي، ولم تُنشر بعد في أي سجل حزم.
تجربة واجهة سطر الأوامر
تعيد هذه الطرفية تشغيل الأمر undergrove الحقيقي. سُجّلت مخرجاتها مقابل البديل المحلي الذي تُختبر عليه الحزم: verify تحقق حقيقي من تصديق مسجَّل من Tinfoil، وmodels يعرض الفهرس الحالي، وإجابات ask مكتوبة مسبقًا. لا يوجد نموذج خلف هذه الصفحة، ولا يغادرها أي شيء تكتبه.
تُحمَّل الطرفية عند وصولك إليها.
تطبيقات نموذجية
ثلاثة تطبيقات ويب صغيرة في packages/examples، يتكون كل منها من صفحة وخادم 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، مبنية على Python SDK من Tinfoil، بالتشفير الافتراضي نفسه.
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 وغيرهما، مع حفظ المفتاح في خزنة كلمات المرور في بيئة التطوير.
packages/jetbrains - ZedUndergrove كوكيل في Agent Panel، مع /explain و/fix و/tests و/review، أو كأدوات لوكيل Zed نفسه. يعمل كلاهما عبر واجهة سطر الأوامر، التي تشفّر على جهازك.
packages/zed - Neovim:UndergroveAsk، و:UndergroveExplain على تحديد مرئي، مع بث الإجابة في نافذة عائمة.
packages/neovim - RaycastAsk Undergrove، وAsk About Selected Text في أي تطبيق.
packages/raycast - GitHub ActionUndergrove Review: مراجعة واحدة لكل طلب سحب (pull request)، مع تعليقات على الأسطر واقتراحات، والتصديق في تذييلها.
packages/github-action
ساحة التجربة
أرسل طلب محادثة من هذه الصفحة بمفتاحك، واطّلع على الإجابة واستخدامها والاستجابة الخام، مع الطلب نفسه بصيغة curl. ينتقل الطلب من متصفحك إلى /v1 في هذا الموقع، مثل أي استدعاء API آخر، ويُخصم من حصتك أو رصيدك كالمعتاد.
اختر «مشفَّر» لإرساله بالطريقة التي يتبعها الوضع المشفَّر في حزم SDK: تتحقق حزمة SDK من Tinfoil من الجيب الآمن داخل هذه الصفحة، ثم تختم الطلب إليه. تعرض ساحة التجربة البايتات التي غادرت الصفحة فعلًا، وما عاد منها، والإيصال الخاص بالجيب الآمن الذي خُتم الطلب إليه.
الطلب نفسه باستخدام 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
}'المصادقة
أرسل مفتاح API من Undergrove بوصفه رمز Bearer. تبدأ المفاتيح بـ ugk_ وتُمنح بدعوة خلال مرحلة ألفا. وحيثما تُفعَّل المفاتيح ذاتية الخدمة، ينشئ أعضاء ألفا ما يصل إلى خمسة مفاتيح من إعدادات مساحة العمل، ويُخصم كل منها من حصة حسابه ورصيده. لا نخزّن إلا تجزئة أحادية الاتجاه لكل مفتاح، لذا لا يمكن استرداد المفتاح المفقود، بل استبداله فقط.
يمكن أن تكون لكل مفتاح ذاتي الخدمة حدوده الخاصة، تُضبط عند إنشائه أو لاحقًا في الإعدادات: حد يومي للإنفاق تُرفض طلباته بعد بلوغه حتى منتصف الليل بتوقيت UTC، وقائمة بالنماذج التي يُسمح له باستخدامها. ويظل المفتاح المقيَّد يُحتسب ضمن حصة حسابه. أما المفاتيح التي أُنشئت قبل وجود الحدود فلا حدود لها وتستمر في العمل كما كانت.
curl https://undergrove.vercel.app/v1/models \
-H "Authorization: Bearer $UNDERGROVE_API_KEY"نقاط النهاية
| نقطة النهاية | الغرض |
|---|---|
| GET /v1/models | النماذج المتاحة الآن، ولكل منها نوع (محادثة أو تضمين أو تفريغ نصي أو كلام)، وترخيصه، وحجم السياق، والأسعار، ومستويات الاستدلال، ودعم الصور والأدوات والبحث في الويب، والإيقاع (سريع أو متأنٍّ)، ومكان تشغيله. |
| POST /v1/chat/completions | Chat, 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 مُدخلًا. $0.002 لكل 48 مُدخلًا أو 67,200 حرف، أيهما أكبر. |
| POST /v1/audio/transcriptions | نصوص مُفرَّغة من Whisper Large v3 Turbo. رفع متعدد الأجزاء حتى 4.4 ميغابايت، مع لغة وموجِّه اختياريين. $0.01 لكل ميغابايت يُبدأ فيه. |
| POST /v1/audio/speech | صوت WAV من Qwen3-TTS بتسعة أصوات: aiden وdylan وeric وono_anna وryan وserena وsohee وuncle_fu وvivian. حتى 4,096 حرفًا. $0.005 لكل 600 حرف يُبدأ فيها. |
| GET /api/status | حالة الخدمة، وحدود الاستخدام، وسجل النماذج الكامل. |
تستخدم الاستجابات البنية القياسية chat.completion. ترسل التدفقات أحداث chat.completion.chunk وتنتهي بـ data: [DONE]. تبث نماذج الاستدلال استدلالها بشكل منفصل في delta.reasoning_content، ويُبلغ الجزء الأخير عن استخدام الرموز. إذا انقطع التدفق، يكون الحدث الأخير كائن خطأ بدلًا من [DONE]، فلا تُحسب الإجابة الجزئية أبدًا إجابةً كاملة.
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"]
}'المعلَمات
| الحقل | النوع | افتراضي | المعنى |
|---|---|---|---|
model | string | مطلوب | معرّف من /v1/models. |
messages | array | مطلوب | حتى 200 رسالة من الأنواع system وuser وassistant وtool. وللنماذج التي تدعم الرؤية، يمكن أن تتضمن رسائل user حتى 16 جزءًا من نوع image_url بصيغة data URL بترميز base64 (PNG أو JPEG أو GIF أو WebP)؛ ولا تُجلب الروابط. تُحتسب كل صورة 3,000 رمز إدخال ما لم يذكر النموذج غير ذلك. |
tools, tool_choice, parallel_tool_calls | array, string | object, boolean | — | استدعاء الدوال للنماذج التي لديها tools في /v1/models: حتى 128 دالة. تصل الاستدعاءات المبثوثة على هيئة أجزاء tool_calls، وتنتهي الإجابة بـ finish_reason tool_calls. أعِد النتائج على هيئة رسائل tool مع tool_call_id الخاص بها. |
response_format | object | — | المخرجات المنظَّمة: {"type": "json_object"}، أو json_schema مع اسم ومخطط. |
stream | boolean | false | أرسل true لبث الإجابة على هيئة أحداث مرسلة من الخادم. |
max_tokens | integer | حد النموذج | يُقبل أيضًا باسم max_completion_tokens. ولا يتجاوز حد الإخراج الخاص بالنموذج. |
temperature, top_p | number | يحدده النموذج | عناصر التحكم في أخذ العيّنات: 0–2 و0–1. |
reasoning_effort | string | يحدده النموذج | المدة التي يفكر فيها نموذج الاستدلال قبل الإجابة. تختلف القيم المقبولة باختلاف النموذج، وهي مدرجة في /v1/models؛ ويستخدم وضعا «سريع» و«شامل» في مساحة العمل القيمتين fast وthorough المدرجتين. |
web_search_options | object | — | أرسل {} للسماح للنموذج بالبحث في الويب. تُفحص الاستعلامات لإزالة البيانات الشخصية، وتُفحص النتائج بحثًا عن حقن الموجِّهات. تصل المصادر على هيئة تعليقات url_citation. يكلّف كل طلب بحث $0.05 من حصتك. |
stop | string | string[] | — | سلسلة نصية واحدة أو حتى أربع سلاسل. |
presence_penalty, frequency_penalty, seed | number, integer | — | تُمرَّر عندما يدعمها مسار النموذج. |
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 أيضًا، استخدم حزمة SDK من Tinfoil مع مفتاحك. فهي تتحقق من التصديق العتادي للجيب الآمن، ثم تشفّر كل طلب بحيث لا يقرؤه إلا ذلك الجيب الآمن. ويتحقق المُرحِّل لدينا من مفتاحك وحصتك ويمرّر النص المشفَّر.
- شيفرتكتتحقق حزمة SDK من Tinfoil من الجيب الآمن، ثم تشفّر كل طلب إليه.يستطيع قراءة الطلب
- مختوم حتى الجيب الآمنمُرحِّل Undergroveيتحقق من المفتاح والحصة، ويمرّر النص المشفَّر. لا يستطيع قراءته.لا يرى إلا نصًا مشفَّرًا
- لا يزال مختومًاالجيب الآمن للنموذجالمكان الوحيد الذي يُفك فيه تشفير الطلب ويعمل فيه النموذج.يستطيع قراءة الطلب
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. ولأن المُرحِّل لا يستطيع قراءة هذه الطلبات، تُسمّى النماذج بمعرّف enclave_model الخاص بها من /v1/models، وتعود الإجابات بتنسيق Tinfoil، ويُقدَّر الاستخدام من حجم البيانات المشفَّرة. وتسري الحصص والرصيد كالمعتاد.
خادم MCP
Undergrove هو أيضًا خادم Model Context Protocol، على العنوان https://undergrove.vercel.app/mcp. كل نموذج مباشر أداة، مثل ask_<model>، يمكن لعملاء MCP مثل Claude Code وCursor وClaude Desktop استدعاؤها بمفتاح API الخاص بك. تأخذ الأداة prompt، واختياريًا system وmax_tokens، وللنماذج التي تبحث web_search، وتُرجع إجابة النموذج نصًا.
استدعاء الأداة طلب API كأي طلب آخر: تقرؤه البوابة في الذاكرة، وترسله عبر المسار المنشور للنموذج، ولا تحتفظ بأي نسخة، وتحتسبه ضمن حدود مفتاحك وحصته ورصيده. يستخدم الخادم HTTP القابل للبث، ويجيب بصيغة JSON، ولا يحتفظ بجلسات. ويدعم إصدارات البروتوكول 2025-11-25 و2025-06-18 و2025-03-26.
Claude Code
شغّل هذا مرة واحدة في الطرفية:
claude mcp add --transport http undergrove https://undergrove.vercel.app/mcp \
--header "Authorization: Bearer ugk_your_key"Cursor
في ~/.cursor/mcp.json، أو .cursor/mcp.json داخل مشروع:
{
"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:
{
"mcpServers": {
"undergrove": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://undergrove.vercel.app/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer ugk_your_key"
}
}
}
}أي عميل
أو استدعه مباشرة. فهو يتعامل بـ JSON-RPC عبر HTTP القابل للبث:
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"}'ثم استدعِ أداة نموذج:
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"}}.
| الحالة | الرموز |
|---|---|
| 400 | invalid_request, invalid_json, unsupported_parameter, unsupported_content, context_length_exceeded, model_rejected_request, unsupported_api_version, async_needs_webhook, async_stream, invalid_webhook_url |
| 401 | authentication_required وinvalid_api_key وinvalid_session وsandbox_key (مفتاح ugk_sandbox_، لا يجيب عنه إلا البديل المحلي) |
| 403 | alpha_access_required وmodel_not_allowed_for_key (المفتاح مقصور على نماذج أخرى) |
| 404 | model_not_found, webhook_not_found, thread_not_found, assistant_not_found, prompt_not_found, version_not_found |
| 409 | idempotency_in_progress (الطلب الأول الذي يحمل Idempotency-Key هذا لا يزال قيد التنفيذ)، وversion_conflict (تغيّر عنصر مختوم منذ قراءته)، وid_taken |
| 413 | request_too_large (المحادثة والتفريغ النصي فوق 4.4 ميغابايت، والتضمينات فوق 1 ميغابايت، والكلام فوق 100 كيلوبايت) |
| 422 | idempotency_key_reused (مفتاح Idempotency-Key نفسه مع طلب مختلف) |
| 429 | rate_limited وbudget_exhausted وcapacity_exhausted وkey_budget_exceeded (بلغ المفتاح حده اليومي الخاص؛ وتذكر الرسالة وقت إعادة الضبط). انتظر المدة المحددة في ترويسة Retry-After، أو أضف رصيدًا من إعدادات مساحة العمل، أو ارفع حد المفتاح من هناك. |
| 502 | model_unavailable, stream_interrupted |
| 503 | capacity_exhausted (مع Retry-After)، وinference_not_configured، وrealtime_not_available (أحد المسارات الصوتية غير مُصدَّق) |
| 504 | model_timeout |
خلال مرحلة ألفا، لكل مفتاح أو حساب حصة في الدقيقة، وحصة يومية من الطلبات، وحصة يومية من الإنفاق. القيم الحالية منشورة على /api/status.
HTTP/2 429
retry-after: 41
{
"error": {
"message": "You’re sending messages quickly. Please wait a moment.",
"type": "rate_limit_error",
"code": "rate_limited"
}
}اعرف إلى أين تذهب بياناتك
- خادمكيرسل الطلب باستخدام مفتاحك.يستطيع قراءة الطلب
- HTTPSبوابة Undergroveتتحقق من المفتاح والحدود، وتقرأ الطلب في الذاكرة، ولا تحتفظ بأي نسخة.يستطيع قراءة الطلب
- مشفَّر إلى الجيب الآمن الموثَّقالجيب الآمن للنموذجيشغّل الجيب الآمن في عتاد Tinfoil النموذج على المسار المنشور.يستطيع قراءة الطلب
تتحقق البوابة من مفتاحك، وتطبّق الحدود، ولا تمرّر إلى مسار النموذج إلا الحقول المذكورة أعلاه. ولا تحتفظ بأي نسخة من الموجِّهات أو الإجابات ولا تسجّلها. لا تسجّل سجلاتها إلا أسماء المسارات ورموز الحالة والتوقيتات. تستخدم عدّادات حدود المعدّل تجزئة أحادية الاتجاه لمفتاحك أو حسابك، وتنتهي صلاحيتها خلال يومين.
يُنشر مشغّل كل مسار نماذج وموقعه وسياسة الاحتفاظ لديه في سجل النماذج على /api/status وفي صفحة الخصوصية. لا تتحول الطلبات أبدًا إلى مسار آخر. وتحتفظ Vercel، الجهة المستضيفة لنا، بالبيانات الوصفية المعتادة للطلبات مثل عناوين IP.
على مسارات الحوسبة السرّية، تتحقق بوابة API من التصديق العتادي للجيب الآمن قبل الاتصال، وتشفّر كل طلب بحيث لا يقرؤه إلا ذلك الجيب الآمن. ومع ذلك تقرأ البوابة نفسها طلبات API في الذاكرة للتحقق منها.
بيانات مساحة العمل
لا تخزّن المزامنة والروابط المشتركة والمشاريع المشتركة إلا نصًا مشفَّرًا صنعه المتصفح. ويُظهر كل مخطط، خطوة بخطوة، مَن يحمل المفتاح.
ما يثبته التحقق
قبل إرسال أي شيء، تشغّل مساحة العمل مُحقِّق Tinfoil المفتوح المصدر في متصفحك. فيتحقق من تقرير التصديق العتادي للجيب الآمن مقابل مفاتيح التوقيع لدى AMD أو Intel، ويتحقق من أن الشيفرة المقيسة في الجيب الآمن تطابق إصدارًا نشرته Tinfoil ووقّعته على GitHub، ويتحقق من أن مفتاح التشفير الذي يستخدمه يعود إلى ذلك الجيب الآمن. اختر موثَّق في مساحة العمل لرؤية كل عملية تحقق، والإصدار، والقياس.
يثبت ذلك أن رسائلك لا يمكن فك تشفيرها إلا بواسطة ذلك العتاد الذي يشغّل تلك الشيفرة المنشورة. لكنه لا يثبت أن الشيفرة المنشورة خالية من العيوب، ولا يحمي من نقاط الضعف في المعالجات نفسها، ولا يشمل ما تفعله بالإجابات بعد وصولها إلى متصفحك. يقدّم Undergrove شيفرة مساحة العمل، لذا فأنت تثق بأننا نقدّم المُحقِّق كما هو منشور؛ وتقارن تحقّق من هذه الصفحة الشيفرة التي حمّلها متصفحك ببيان البناء، ويقارن npm run verify:site الموقع ببنائك الخاص للإيداع نفسه. ومن المخطط إجراء تقييم مستقل.
لكل إجابة من جيب آمن زر إيصال. يعرض الجيب الآمن والإصدار والقياسات التي جرى التحقق منها قبل إرسال ذلك الطلب، والمفتاح الذي شُفّر إليه، وينزّلها بصيغة JSON. وإلى أن يُغلق التبويب، يتضمن التنزيل تقرير التصديق، لتتمكن من تشغيل المُحقِّق عليه مجددًا لاحقًا؛ وبعد ذلك يتضمن تجزئة التقرير ومكان جلب التقرير الحالي للجيب الآمن. الإيصال سجلّ يحتفظ به متصفحك، وليس بيانًا موقَّعًا، والجيب الآمن الذي يذكره هو موجِّه النماذج لدى Tinfoil؛ إذ يمرّر الموجِّه الطلبات إلى الجيب الآمن الخاص بالنموذج. ويذكر الإيصال ذلك الجيب الآمن أيضًا، ويحاول متصفحك التحقق منه بالطريقة نفسها. تعمل معظم الجيوب الآمنة للنماذج على Intel TDX، الذي لا يستطيع مُحقِّق المتصفح التحقق منه بعد؛ وفي هذه الحالات يذكر الإيصال ذلك، ويعتمد التحقق على الموجِّه، الذي تتحقق شيفرته المنشورة من كل جيب آمن للنماذج قبل استخدامه.
# 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-attestation | verified-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، اطلب الاستجابة الخام:
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
// 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، لذا لا تستطيع صفحتك قراءة ما يكتبه الزوار.
- في مساحة العمل، افتح الإعدادات وأنشئ مفتاحًا قابلًا للنشر ضمن مساعد المواقع الإلكترونية: اسمه، ونموذجه، والمواقع المسموح لها باستخدامه، وحدًّا يوميًا.
- انسخ الشيفرة التي يقدّمها والصقها قبل
</body>في كل صفحة. - جرّبه أولًا في صفحة العرض التوضيحي، وهي متجر وهمي.
<script
src="https://undergrove.vercel.app/embed.js"
data-key="ugp_…"
data-model="<model-id>"
data-instructions="نحن استوديو فخار. ساعد في الدروس ومواعيد العمل والشحن."
async
></script>| السمة | النوع | افتراضي | المعنى |
|---|---|---|---|
data-key | string | مطلوب | مفتاحك القابل للنشر، ugp_… |
data-model | string | مطلوب | أحد النماذج المدرجة في المفتاح. |
data-title | string | “Ask a question” | عنوان اللوحة. |
data-greeting | string | — | أول رسالة يراها الزوار. |
data-instructions | string | — | موضوع موقعك، وما يساعد فيه المساعد، والأسلوب. يُرسل مع كل سؤال؛ ويمكن للزوار قراءته في صفحتك. |
data-label | string | “Ask” | نص الزر. |
data-position | string | right | right أو left. |
data-theme | string | auto | auto (جهاز الزائر)، أو light أو dark. |
data-open | boolean | false | القيمة true تفتح اللوحة عند تحميل الصفحة. |
لماذا يمكن وضع المفتاح القابل للنشر في صفحة عامة
- لا يعمل إلا للمحادثة، ولا يعمل إلا من إطار المساعد الخاص بـ Undergrove. وترفضه واجهة API في أي مكان آخر.
- لا يعمل إلا ما دام الإطار مضمَّنًا في أحد المواقع التي تدرجها. يُبلغ المتصفح عن الصفحة التي تضمّن الإطار، ويتحقق الخادم من ذلك.
- له دائمًا حد يومي للإنفاق وقائمة نماذج. يستطيع برنامج يعمل خارج المتصفح أن يدّعي أنه أي موقع، لذا تعامل مع المفتاح على أنه عام، واضبط الحد على ما ترضى بإنفاقه في يوم واحد.
- لكل زائر حد للأسئلة في الدقيقة وحد يومي، وهما منشوران على /api/status. يُحصى الزوار تحت تجزئة أحادية الاتجاه بمفتاح لعنوان IP الخاص بهم، وليس العنوان نفسه أبدًا، وتنتهي صلاحية الأعداد خلال يومين.
تُخصم الإجابات من حصة حسابك ورصيده، مثل أي مفتاح. بالنسبة إلى النماذج التي تعمل داخل جيب آمن، يشفّر الإطار كل رسالة في متصفح الزائر إلى الجيب الآمن الموثَّق، كما تفعل مساحة العمل؛ أما النماذج الأخرى فيُوصل إليها عبر البوابة، التي تقرأ الطلبات في الذاكرة ولا تحتفظ بأي نسخة. ولا يرى موقعك أيًّا منهما.
شارة «Verified by Undergrove»
يمكن للموقع الذي يضم مساعدًا أن يعرض شارة صغيرة ترتبط بـ صفحة Undergrove الخاصة بمفتاحه. تقرأ تلك الصفحة مواقع المفتاح ونماذجه من خادم Undergrove، وتتحقق في متصفح الزائر من الجيب الآمن الذي يقف خلفها، وتوضح ما تدّعيه الشارة وما لا تدّعيه. ضع الوسم حيث ينبغي أن تظهر الشارة.
أو، من دون برنامج نصي، صورة ورابط (badge-dark.svg للصفحات الداكنة):
<script src="https://undergrove.vercel.app/badge.js" data-key="ugp_…" async></script>- الشارة رابط. لا ترسل أي طلبات من صفحتك ولا تتحقق من شيء بنفسها؛ فالصفحة التي تفتحها هي التي تتولى التحقق.
- تذكر مفتاحك القابل للنشر، الموجود أصلًا في صفحتك من أجل المساعد. تعرض صفحة الشارة مواقع المفتاح ونماذجه، ولا تعرض أبدًا الحساب أو الحد أو ما أُنفق.
- يطلب الرابط من المتصفح إرسال أصل موقعك، كي تتمكن الصفحة من إخبار الزوار بما إذا كانوا قد أتوا من أحد مواقع المفتاح. والشارة المنسوخة إلى موقع آخر تذكر ذلك هناك.
- تشهد للمساعد فقط، لا لبقية موقعك.
التكاملات
يتحقق معظم عملاء Undergrove من تصديق الجيب الآمن باستخدام مُحقِّق Tinfoil، ثم يشفّر كل طلب إلى ذلك الجيب الآمن، فيمرّره المُرحِّل دون أن يتمكن من قراءته. يأخذ كل منها مفتاح API الخاص بك ويستخدم معرّفات نماذج Undergrove. لم تُنشر بعد في سجلات الحزم؛ ويُبنى كل منها من packages/ في مستودع الموقع.
أما عملاء Rust وJava وKotlin وتطبيق Make فتستخدم واجهة API للمطوّرين بدلًا من ذلك، إذ لا توجد حزمة SDK من Tinfoil تُبنى عليها: تقرأ البوابة كل طلب في الذاكرة، وتتحقق من الجيب الآمن بنفسها، وتذكر ما تحققت منه في ترويسات الإجابة.
| الحزمة | لأجل | ما تفعله |
|---|---|---|
@undergrove/sdk | Node | عميل على نمط OpenAI مع البث والأدوات والتضمينات والتفريغ النصي والكلام. |
undergrove | Python | عميل Python، مع LangChain (ChatUndergrove) وLlamaIndex (UndergroveLLM) كإضافات اختيارية. |
Go | Go | عميل مبني على مُحقِّق Go من Tinfoil، مع البث واستدعاءات الأدوات. |
Undergrove | Swift | حزمة Swift لنظامي macOS 14 وiOS 17، مبنية على مُحقِّق Swift من Tinfoil. |
@undergrove/ai-sdk-provider | Vercel AI SDK | generateText وstreamText والأدوات، مع ما جرى التحقق منه في البيانات الوصفية للمزوّد في كل إجابة. |
@undergrove/langchain | LangChain.js | نموذج محادثة وتضمينات. |
@undergrove/react | React | مكوّنات وخطافات (hooks) للمحادثة، ومسار على الخادم يُبقي مفتاح API بعيدًا عن المتصفح. |
create-undergrove-app | قالب للبدء | npm create undergrove-app: تطبيق محادثة خاص يُنشر على Vercel بأمرين. |
@undergrove/agents | الوكلاء | حلقة لاستدعاء الأدوات تسجّل الجيب الآمن الموثَّق الذي أجاب عن كل خطوة، في سجل يمكن لأي شخص فحصه بحثًا عن التعديلات، مع تثبيت الجيوب الآمنة. |
undergrove | Rust | عميل لواجهة API للمطوّرين، مع البث واستدعاءات الأدوات، ودليل التصديق من البوابة لكل إجابة. لا يشفّر الطلبات إلى الجيب الآمن بنفسه. |
dev.undergrove:undergrove | Java وKotlin | عميل لواجهة API للمطوّرين، مع البث والأدوات والتضمينات، والإجراءات المتزامنة (coroutines) وFlow في Kotlin. ليس الوضع المشفَّر: فالبوابة تقرأ الطلبات في الذاكرة. |
undergrove[jupyter] | Jupyter | أمر الخلية السحري %%undergrove، مع متغيرات الدفتر وإطارات DataFrame سياقًا، وأداة واجهة للتصديق. |
@undergrove/office | Excel وWord | =UNDERGROVE.ASK() في أي خلية، وفي Word: تلخيص التحديد أو إعادة صياغته أو السؤال عنه. |
@undergrove/office | Outlook | ملخص للرسالة المفتوحة، ومسودة رد تراجعها قبل الإرسال. |
undergrove-obsidian | Obsidian | السؤال عن الملاحظة المفتوحة، والتلخيص، وإعادة الصياغة، ومتابعة الكتابة، مع الجيب الآمن الموثَّق أسفل كل إجابة. لنسخة سطح المكتب فقط. |
Undergrove for Alfred | Alfred | اكتب ug ثم سؤالًا: تظهر الإجابة بخط كبير وفي الحافظة، مشفَّرة عبر واجهة سطر الأوامر undergrove. |
n8n-nodes-undergrove | n8n | عقدة مجتمعية: السؤال لنموذج، وإنشاء التضمينات، وعرض النماذج، مع ما جرى التحقق منه في كل عنصر. |
Undergrove for Zapier | Zapier | إجراءا Ask a Model وCreate Embeddings، مع تشفير كل طلب إلى الجيب الآمن من عملية Zap. |
Undergrove for Make | Make | تطبيق مخصص مبني على واجهة API للمطوّرين: تتحقق البوابة من الجيب الآمن وتذكر ما تحققت منه في مخرجات كل وحدة. |
Undergrove Enclave Pin | GitHub Actions | خطوة تتحقق من الجيب الآمن على المشغّل (runner) باستخدام مُحقِّق Tinfoil، وتُفشل المهمة إذا لم يكن الجيب الآمن الذي ثبّتّه. |
مع Vercel AI SDK:
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
يجيب البديل المحلي الذي تُختبر عليه الحزم عن مفاتيح sandbox: أي مفتاح يبدأ بـ ugk_sandbox_. يمكن لاختباراتك استخدام أحدها دون سرّ أو رسوم، أما Undergrove نفسه فيرفضها مع sandbox_key. إجاباته مكتوبة مسبقًا، وليست من نموذج. في GitHub Actions، تشغّله خطوة واحدة وتضبط UNDERGROVE_BASE_URL وUNDERGROVE_API_KEY:
- 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. |
|---|---|
| التالي | أول مسار للنماذج، وتسجيل الدخول إلى مرحلة ألفا الخاصة، وحدود استخدام مشتركة. |
| مبني، لكنه غير مفعّل بعد | مفاتيح ذاتية الخدمة، وخطط مدفوعة، ورصيد مدفوع مسبقًا للاستخدام الذي يتجاوز الحصة اليومية. |
| مُخطَّط له | وصول الفرق. |
| يتطلب التحقق | تقييم أمني مستقل لمساحة العمل والمُرحِّل والبوابة. |