# エッジ

サイトと API の前で動くものです。コンソールの分析・エッジ画面、管理 REST、下のツールで扱います。

## ウェブ分析と速度インサイト

```
get_analytics          訪問、参照元、国、デバイスの合計
get_speed_insights     Core Web Vitals の75パーセンタイル
set_insights           オン・オフ (オーナーと管理者)
get_analytics_breakdown   キャンペーン（UTM）、ルート、カスタムイベントとプロパティ、除外したボット
set_analytics_previews    プレビューの訪問を数える（オーナーと管理者）
export_analytics          合計を CSV で
list_analytics_reports    カスタムレポート一覧
get_analytics_report      カスタムレポート 1 つ
save_analytics_report     カスタムレポートの作成・変更（開発者以上）
remove_analytics_report   カスタムレポートの削除
```

オンにするとサイトの HTML に `/_oneend/insights/script.js` が入ります。Cookie もブラウザのストレージも使わず、IP アドレスとブラウザの情報は保存せず、合計だけを残します。訪問者は1日だけ区別します。
Vercel の `@vercel/analytics`・`@vercel/speed-insights` を使うアプリは、コードを変えずに同じ場所で集計されます。すべてのサイトが `/_vercel/insights/script.js`・`/_vercel/speed-insights/script.js` と報告先のパスを、サイトのファイルや rewrite より先に応答します。オフの間、そのスクリプトは何も送りません。カスタムイベントは `oneend.track('Signup', { plan: 'pro' })` か `@vercel/analytics` の `track()` で送り、名前とプロパティの値ごとに数えます。最初のページのアドレスの `utm_source`・`utm_medium`・`utm_campaign` とフレームワークのルート（`/blog/[slug]`）も数えます。人ではない訪問（AI クローラー、検索エンジン、その他のプログラム）は数えず、除外した数だけを残し、計量しません。プレビューと `dpl-` アドレスの訪問は、`set_analytics_previews` でオンにするまで数えません。
イベントは `analytics_events` として計量されます。国は運営者が指定したヘッダーかアドレス範囲の表からだけ読みます。どちらもないサーバーでは国を推測せず、`??`として数え、レポートの note でそう伝えます。

## エッジ設定と機能フラグ

```
get_edge_config           全体とバージョン、上限
set_edge_config           キーの保存・削除 (開発者以上、履歴が残る)
get_edge_config_history   誰がいつ何を変えたか
list_edge_config_tokens        読み取りトークンと連携したプロジェクト
create_edge_config_token       読み取りトークンを作成（管理者、一度だけ表示、すぐに連携も可）
connect_edge_config_token      別のプロジェクトのデプロイに変数として連携
disconnect_edge_config_token   連携を解除
revoke_edge_config_token       トークンを失効
list_flags                フラグとエッジ設定のフラグをまとめて、どちらが決めるか
evaluate_flag             アプリが受け取る値（コンテキスト、環境）
set_flag_enabled          1 つの環境でオン・オフ
get_flag                  フラグ1つのバリアント、環境ごとの設定とルール
create_flag               フラグを作成（すべての環境でオフ）
update_flag               説明とバリアントを変更（管理者）
set_flag_environment      1つの環境のオン・オフ、オフのときのバリアント、既定で返すもの（serve）
add_flag_rule             ルールを追加（条件1〜3個、serve）
move_flag_rule            ルールを並べ替え（up | down）
remove_flag_rule          ルールを削除
set_flag_archived         アーカイブ・元に戻す（管理者）
list_flag_segments        セグメント一覧
create_flag_segment       セグメントを作成
update_flag_segment       セグメントの条件、含める・除く id（管理者）
remove_flag_segment       セグメントを削除（管理者、使うルールがないとき）
list_flag_sdk_keys        /flags/v1/evaluate を呼ぶキー
create_flag_sdk_key       SDK キーを作成（server | client、一度だけ表示）
revoke_flag_sdk_key       SDK キーを廃止
get_remote_cache          Turborepo リモートキャッシュ: 容量、ヒット、今月の計量（remote_cache_gb_hour）
clear_remote_cache        リモートキャッシュを空にする（開発者以上）
list_remote_cache_tokens  リモートキャッシュのトークンと turbo に渡す api、team
create_remote_cache_token トークンを作成（1回だけ表示、開発者以上）
revoke_remote_cache_token トークンを無効化
get_queue_metrics         キューの記録: キューごとの待機数と最長待ち時間、時間ごとの送信数と処理数
```

数秒以内にすべてのサーバーに反映されます。関数とミドルウェアは `ctx.config.get(key)`、ほかのサーバーは secret キーで `GET https://<ref>.<zone>/config/v1` を呼び出します。
機能フラグは値の形で決まります: `true`、`{"enabled": true, "percent": 20}`、`{"enabled": true, "allow": ["user-1"]}`。`ctx.flags.isEnabled(key, userId)` は同じユーザーに常に同じ答えを返します。
コンソールの機能フラグ（ルール、セグメント、環境、SDK キー）とエッジ設定のフラグは一度に読まれます。順番: 同じキーのフラグ（アーカイブされていないもの）が決め、なければエッジ設定の値が決め、どちらもなければ見つかりません（`isEnabled` は false）。`ctx.flags`、`OneEnd.flags`、SDK キーで呼ぶ `/flags/v1/evaluate`、secret キーで呼ぶ `/config/v1/flags/<key>` はすべてこの順番に従い、答えの `source`（`flag`、`edge_config`）がどちらで決まったかを示します。`ctx.flags.evaluate(key, { user: { id } })` はフラグの値とバリアントを返します。

同じ組織の別のプロジェクトもこのエッジ設定を読めます。読み取りトークン（`bwec_…`）を作ってそのプロジェクトに連携すると、そのプロジェクトの次のデプロイから `GLOBAL_CONFIG`（名前は選べます）に接続文字列 `https://<ref>.<zone>/config/v1?token=…` が入ります。トークンは読むだけで、失効させるとすぐに止まります。本番への連携は、そのプロジェクトのオーナー・管理者だけができます。

## 段階的リリース

```
get_rolling_releases      設定と進行中のリリース
set_rolling_releases      段階の割合、長さ、自動進行、自動中止の基準
advance_rolling_release   次の段階へ
promote_rolling_release   すぐに全員へ
abort_rolling_release     中止して以前のデプロイへ
```

オンにすると、新しい本番デプロイは段階の割合 (例: 10%、50%) の訪問者だけを受け取ります。訪問者は `__oneend_rr` Cookie でどちらかに固定されます。
新しいデプロイの5xx率が基準を超えると、ゲートウェイが自分でリリースを中止し、以前のデプロイに戻します。以前のデプロイに rollback しても中止されます。

## KV ストア

```
list_kv_keys   キーの一覧 (シークレットを見られるロール)
get_kv         値1つ
set_kv         保存 (ttl は秒)
delete_kv      削除
```

関数では `ctx.kv.get/set/del/incr/expire/ttl/list` を使います。REST は secret キーで `https://<ref>.<zone>/kv/v1` です。Upstash の形式なので `@upstash/redis` をそのまま使えます:

```
import { Redis } from '@upstash/redis'
const redis = new Redis({ url: 'https://<ref>.<zone>/kv/v1', token: process.env.ONEEND_SECRET_KEY })
await redis.set('session:abc', 'payload', { ex: 60 })
```

リクエストは `kv_operations` として、保存したバイトはデータベース容量として計量されます。値1つと全体のサイズはプランが決めます。

## AI ゲートウェイ

```
get_ai_gateway            アドレス、設定、プロバイダー、30日の使用量
set_ai_gateway            オン、ユーザーの呼び出し、キャッシュ、1分の上限、プラットフォームへの切り替え
add_ai_provider           プロバイダーを追加 (キーは封印)
set_ai_provider_enabled   プロバイダーのオン・オフ
remove_ai_provider        プロバイダーを削除
get_ai_gateway_logs       最近の呼び出し
set_ai_budgets               プロジェクトの予算（ドル、日・週・月）
list_ai_gateway_keys         ゲートウェイキー一覧
create_ai_gateway_key        アプリ 1 つに渡すキー（予算付き、一度だけ表示）
set_ai_gateway_key_budgets   キーの予算を変更
set_ai_gateway_key_limits    キーの 1 分あたりのリクエスト数・トークン数
set_ai_model_aliases         モデルの別名（blackall-air -> プロバイダーのモデル、システム指示付き）
set_ai_gateway_hooks         キーの呼び出しの前に問い合わせ、後に知らせるフック（署名付き）
revoke_ai_gateway_key        キーを無効化
get_ai_spend                 支出: 今日・週・月、日別・モデル別・キー別
```

```
import OpenAI from 'openai'
const ai = new OpenAI({ baseURL: 'https://<ref>.<zone>/ai/v1', apiKey: process.env.ONEEND_SECRET_KEY })
await ai.chat.completions.create({ model: 'openai/gpt-4o-mini', messages: [{ role: 'user', content: 'hi' }] })
```

OpenAI クライアントをこのアドレスと secret キーで使います。`provider/model` はそのプロバイダーだけを、`model` だけなら優先順位どおりに呼び出し、失敗 (接続失敗、429、5xx) すると次に移ります。
プロバイダーのアドレスは公開された https のみです。プライベート、内部、メタデータのアドレスとリダイレクトは拒否します。プロジェクトのキーはプロバイダーに送りません。
リクエストは `ai_gateway_requests`、プラットフォームのプロバイダー (DeepSeek) のトークンは `ai_gateway_tokens` として計量されます。自分のキーで呼んだトークンはそのプロバイダーが請求します。
プロバイダーの種類は `openai`、`anthropic`、`google`、`deepseek`、`openai-compatible` です。Anthropic は Messages API と相互に変換して呼び出し（ストリームとツール呼び出しを含む）、Google は Gemini の OpenAI 互換アドレスで呼び出します。
アプリにはプロジェクトの secret キーの代わりにゲートウェイキー（`bwai_…`）を渡せます。キーごと、そしてプロジェクト全体に日・週・月の予算（ドル、UTC）を設定すると、使い切った後の呼び出しはプロバイダーに送る前に `429 budget_exceeded` で拒否されます。呼び出しごとに費用（`x-oneend-cost-usd`、記録の `cost_usd`）と最初のトークンまでの時間（`ttft_ms`）を残します。自分のキーで呼んだ費用はプロバイダーの定価で計算した推定です。
**画像と音声**: 同じアドレスで `/ai/v1/images/generations`(画像)、`/ai/v1/audio/speech`(読み上げ)、`/ai/v1/audio/transcriptions`(文字起こし、multipart のファイル)も受け付けます。OpenAI SDK の `images.generate`・`audio.speech.create`・`audio.transcriptions.create` がそのまま使えます。プラットフォームのプロバイダーは画像も音声も作らないため、OpenAI か OpenAI 互換の GPU アドレス(RunPod・Together など)を `add_ai_provider` で追加します。ユーザーごとの上限と記録は会話と同じです。

**アプリのユーザーごとの上限**: `set_ai_gateway` の `user_daily_requests`・`user_daily_tokens` で、アプリのユーザー1人が1日(韓国時間0時区切り)に使えるリクエストとトークンを決めます。超えると 429 `user_limit` を返します。ユーザーはログインしたユーザーの id で、サーバーから secret キーで呼ぶときは `x-oneend-user` ヘッダー(または本文の `user`)にその人の id を入れます。自分のキーで呼んだトークンもユーザーごとに数えます。よく使ったユーザーは `get_ai_gateway` の `top_users_today`・`top_users_30d` に出ます。

## ミドルウェア

```
get_middleware   実行するかと時間制限
set_middleware   オン・オフと時間制限 (オーナーと管理者)
```

```
import { NextResponse } from 'next/server'

export function middleware(request) {
  if (!request.cookies.get('session')) return NextResponse.redirect(new URL('/login', request.url))
  return NextResponse.next({ headers: { 'x-frame-options': 'DENY' } })
}

export const config = { matcher: ['/app/:path*'] }
```

Next.js の `middleware.ts` (または `proxy.ts`) はビルド時にまとめられ、リクエストごとに最初に実行されます。ほかのフレームワークは blackwall.json に `"middleware": "middleware.ts"` と指定します。
関数と同じ隔離環境で実行し、`next/server` の NextResponse (next、rewrite、redirect、json、cookies) を使えます。rewrite は同じサイトのパスにだけできます。失敗したり時間を超えたりすると、ページは表示しません。
ミドルウェアの中では `@vercel/edge-config` の `get()` か `OneEnd.config.get()` でエッジ設定を、`OneEnd.kv` で KV を読みます。
実行回数は `middleware_invocations`、起動している間のメモリは `runtime_gb_hour` として計量されます。

## REST

管理 REST:

```
GET    /v1/projects/<ref>/analytics?days=7
GET    /v1/projects/<ref>/analytics/breakdown?days=7&event=Signup
PUT    /v1/projects/<ref>/analytics/previews
GET    /v1/projects/<ref>/analytics/export?days=30
GET    /v1/projects/<ref>/analytics/reports
POST   /v1/projects/<ref>/analytics/reports
GET    /v1/projects/<ref>/analytics/reports/<id>
PATCH  /v1/projects/<ref>/analytics/reports/<id>
DELETE /v1/projects/<ref>/analytics/reports/<id>
GET    /v1/projects/<ref>/speed-insights?days=7&device=mobile
PUT    /v1/projects/<ref>/insights
GET    /v1/projects/<ref>/edge-config
GET    /v1/projects/<ref>/flags
GET    /v1/projects/<ref>/flags/evaluate?key=promo&environment=production&context={...}
PUT    /v1/projects/<ref>/flags/<key>/environments/<env>
GET    /v1/projects/<ref>/remote-cache
DELETE /v1/projects/<ref>/remote-cache/artifacts
GET    /v1/projects/<ref>/queues/metrics?hours=24
PATCH  /v1/projects/<ref>/edge-config
GET    /v1/projects/<ref>/edge-config/history
GET    /v1/projects/<ref>/edge-config/tokens
POST   /v1/projects/<ref>/edge-config/tokens
DELETE /v1/projects/<ref>/edge-config/tokens/<id>
PUT    /v1/projects/<ref>/edge-config/tokens/<id>/connections/<target>
DELETE /v1/projects/<ref>/edge-config/tokens/<id>/connections/<target>
GET    /v1/projects/<ref>/rolling-releases
PUT    /v1/projects/<ref>/rolling-releases
POST   /v1/projects/<ref>/rolling-releases/advance | promote | abort
GET    /v1/projects/<ref>/kv?prefix=session:
GET    /v1/projects/<ref>/kv/<key>
PUT    /v1/projects/<ref>/kv/<key>
DELETE /v1/projects/<ref>/kv/<key>
GET    /v1/projects/<ref>/ai-gateway
PUT    /v1/projects/<ref>/ai-gateway
GET    /v1/projects/<ref>/ai-gateway/logs
POST   /v1/projects/<ref>/ai-gateway/providers
PATCH  /v1/projects/<ref>/ai-gateway/providers/<id>
DELETE /v1/projects/<ref>/ai-gateway/providers/<id>
PUT    /v1/projects/<ref>/ai-gateway/budgets
GET    /v1/projects/<ref>/ai-gateway/keys
POST   /v1/projects/<ref>/ai-gateway/keys
PATCH  /v1/projects/<ref>/ai-gateway/keys/<id>
DELETE /v1/projects/<ref>/ai-gateway/keys/<id>
PUT    /v1/projects/<ref>/ai-gateway/keys/<id>/limits
PUT    /v1/projects/<ref>/ai-gateway/aliases
PUT    /v1/projects/<ref>/ai-gateway/hooks
GET    /v1/projects/<ref>/ai-gateway/spend?days=30
GET    /v1/projects/<ref>/middleware
PUT    /v1/projects/<ref>/middleware
```

---

https://end.blackall.ai/docs/edge?lang=ja
