open-sse Architecture (日本語)
ワークスペースパッケージを分離する理由
Section titled “ワークスペースパッケージを分離する理由”open-sse/ は、以下の理由により OmniRoute モノレポ内の独立したワークスペースとなっています。
- 再利用性 —
open-sseは npm で@omniroute/open-sseとして公開されているため、他のプロジェクトでも独立して使用できます - 明確な境界 — ストリーミングエンジンが OmniRoute 固有の UI/DB レイヤーから分離されています
- パフォーマンス — エンジンは Next.js に依存しないため、CLI/サーバーレス環境でのコールドスタートが高速になります
- バージョニング —
open-sseは独自のサイクルでリリースできます
"workspaces": ["open-sse"]トップレベル構造
Section titled “トップレベル構造”open-sse/├── index.ts # 公開エントリーポイント├── types.d.ts # 公開型エクスポート├── package.json # @omniroute/open-sse├── config/ # プロバイダー設定、定数、レジストリ├── executors/ # プロバイダー別 HTTP エグゼキューター(67個 + base.ts/index.ts)├── handlers/ # リクエストハンドラー(chatCore、responses など)├── lib/ # 内部ユーティリティ├── mcp-server/ # Model Context Protocol サーバー├── services/ # 約298個のサービスモジュール├── transformer/ # Responses API 形式トランスフォーマー├── translator/ # 形式変換(OpenAI ↔ Claude ↔ Gemini)└── utils/ # 共有ユーティリティ(ロギング、エラー、ストリームなど)モジュール数
Section titled “モジュール数”| ディレクトリ | ファイル数 | 目的 |
| executors/ | 167 | プロバイダー別 HTTP エグゼキューター(DefaultExecutor ファクトリーにより統一) |
| handlers/ | 157 | リクエストのエントリーポイント(chatCore、responses、embeddings) |
| services/ | 約536 | ルーティング、キャッシュ、レート制限、更新など |
| translator/ | 56 | 形式変換(OpenAI ↔ Claude ↔ Gemini) |
| mcp-server/ | 44 | MCP ツールおよびトランスポート |
| utils/ | 約108 | 横断的ユーティリティ(ロギング、エラー、ストリーム) |
| config/ | 約339 | プロバイダー設定、定数、レジストリ |
リクエストパイプライン
Section titled “リクエストパイプライン”すべての LLM リクエストは、5段階のパイプラインを通過します。
┌──────────────┐ HTTP リクエスト │ 1. ルーティング │ コンボ解決、モデル選択 (Next.js ルート) └──────┬───────┘ │ ▼ ┌──────────────┐ │ 2. 変換 │ 形式変換(OpenAI ↔ Claude ↔ Gemini) └──────┬───────┘ │ ▼ ┌──────────────┐ │ 3. 実行 │ プロバイダーエグゼキューター、HTTP、再試行、ブレーカー └──────┬───────┘ │ ▼ ┌──────────────┐ │ 4. ストリーム │ SSE 変換、バックプレッシャー └──────┬───────┘ │ ▼ ┌──────────────┐ │ 5. 記録 │ 使用量追跡、呼び出しログ、エラー分類 └──────┬───────┘ │ ▼ HTTP レスポンス(SSE または JSON)ステージ1: ルーティング(services/combo.ts)
Section titled “ステージ1: ルーティング(services/combo.ts)”エントリーポイント: services/combo.ts の handleComboChat()
リクエストを具体的な (provider, model, account, credentials) タプルへ解決します。
- ID でコンボを検索します(または
auto/*モデル用の仮想コンボを構築します) - ルーティング戦略(優先度、重み付け、ラウンドロビンなど)を適用します
- 正常でないプロバイダーを除外します(サーキットブレーカー)
- 次に利用可能なターゲットを選択します
auto/* モデルの場合、このステージではさらに以下を行います。
- 16要素スコアリングアルゴリズム(
services/autoCombo/)を実行します - 正常性、コスト、レイテンシなどに基づいて
provider+modelの組み合わせを選択します
ステージ2: 変換(translator/)
Section titled “ステージ2: 変換(translator/)”送信元の形式(例: OpenAI)が送信先の形式(例: Claude)と異なる場合、リクエストは変換されます。
- システムプロンプト → システムメッセージ
- ツール定義 → プロバイダー固有のツール形式
- 推論/思考パラメーター → プロバイダー固有の同等パラメーター
- メッセージロールの正規化(OpenAI 以外では
developer→system)
translator/index.ts は以下を公開します。
translateRequest(body, sourceFormat, targetFormat): TranslatedRequestneedsTranslation(source, target): booleanステージ3: 実行(executors/)
Section titled “ステージ3: 実行(executors/)”エントリーポイント: getExecutor(providerId).execute(request, options)
すべてのプロバイダーは、getExecutor() ファクトリーのフォールバックを介して DefaultExecutor(executors/default.ts)を使用します。エグゼキューターは以下を行います。
- アップストリーム URL を構築します(
buildUrl()) - プロバイダー固有のヘッダーを追加します(
buildHeaders()) - リクエストボディを変換します(
transformRequest()) - 再試行と指数バックオフを使用して HTTP リクエストを送信します
- 必要に応じて認証を更新します(OAuth プロバイダー)
すべてのエグゼキューターは BaseExecutor(executors/base.ts、1170 LOC)を継承し、以下の機能を利用します。
- 共通の再試行ロジック
- プロキシ連携
- サーキットブレーカー連携
- 使用量記録フック
ステージ4: ストリーム(utils/stream.ts)
Section titled “ステージ4: ストリーム(utils/stream.ts)”ストリーミングレスポンスの場合、エグゼキューターは ReadableStream を返します。ハンドラーは以下を行います。
- SSE トランスフォーム(
createSSETransformStreamWithLogger)を通します - 切断された接続を検出するためにハートビート ping を適用します
- クライアントの切断を適切に処理します(
pipeWithDisconnect) - 非ストリーミングクライアント向けに SSE → JSON 変換を行います
非ストリーミングレスポンスの場合、エグゼキューターは解析済みの JSON オブジェクトを返し、そのまま渡されます。
ステージ5: 記録(services/usage.ts)
Section titled “ステージ5: 記録(services/usage.ts)”レスポンス後(成功・失敗を問わず)、使用状況が記録されます。
- レスポンスから取得した
prompt_tokens、completion_tokens、cached_tokens - 料金データに基づいて算出された
cost_usd - 失敗した場合の
latency_ms、status、error_class usage_historyテーブルに永続化
コールログのアーティファクト(有効な場合)は ${DATA_DIR}/call_logs/ に書き込まれます。
主要ファイルの詳細
Section titled “主要ファイルの詳細”chatCore.ts(5977行)
Section titled “chatCore.ts(5977行)”メインのリクエストハンドラーです。非常に大きなファイルですが、構造は明確です。
// chatCore.tsの擬似構造export async function handleChat(request: NextRequest) { // 1. 認証 + CORS await authenticateRequest(request); applyCorsHeaders(response);
// 2. ボディの検証 const body = await parseRequestBody(request);
// 3. フォーマットの検出 + 変換 const sourceFormat = detectFormat(request); const targetFormat = getTargetFormat(providerId); if (needsTranslation(sourceFormat, targetFormat)) { body = translateRequest(body, sourceFormat, targetFormat); }
// 4. コンボルーティング const targets = await resolveComboTargets(comboId, body); for (const target of targets) { try { const result = await executeOnTarget(target, body); await recordUsage(result); return result; } catch (err) { // 次のターゲットへ進む } }
// 5. 緊急フォールバック return await emergencyFallback(body);}1つの巨大な関数であるにもかかわらず、5段階のパイプラインに対応するコメント付きのセクションに整理されています。
combo.ts(4456 LOC)
Section titled “combo.ts(4456 LOC)”コンボを順序付きターゲットへ解決するルーティングエンジンです。
export async function handleComboChat(body, comboId): Promise<ChatResult> { const targets = await resolveComboTargets(comboId, body); for (const target of targets) { try { return await handleSingleModel(target, body); } catch (err) { log.warn("target failed, trying next", { target, err }); } } throw new ComboExhaustedError("All targets failed");}19種類のルーティング戦略をサポートしています(src/shared/constants/routingStrategies.tsを参照)。
| 戦略 | 動作 |
|---|---|
priority |
先頭ターゲットを優先する順序付きリスト |
weighted |
ターゲットごとの重みに基づく確率的選択 |
round-robin |
ターゲットを順番に巡回 |
context-relay |
ターゲット間でコンテキストを引き継ぐ |
fill-first |
次へ移る前にクォータを使い切る |
p2c |
2択のうち良い方を選択 |
random |
一様ランダム |
least-used |
最近の使用回数が最も少ないものを選択 |
cost-optimized |
正常なターゲットのうち最も安価なものを優先 |
reset-aware |
プロバイダーのリセット期間を考慮 |
reset-window |
リセット期間に基づくルーティング |
headroom |
残りのクォータ余裕が最も大きいものを優先 |
strict-random |
完全な一様ランダム(品質による重み付けなし) |
auto |
16要素のスコアリングを使用(autoCombo/) |
lkgp |
最後に正常動作した既知のプロバイダーを優先 |
context-optimized |
長いコンテキストのリクエストに最適なものを選択 |
fusion |
複数のパネルへ並列に展開し、審判役を通じて統合(fusion.ts) |
base.ts(1170 LOC)
Section titled “base.ts(1170 LOC)”107個すべてのエグゼキューターが継承する抽象エグゼキューターです。以下が含まれます。
buildUrl()— デフォルトのURL構築(カスタム動作が必要な場合はサブクラスでオーバーライド)buildHeaders()— デフォルトのヘッダー(認証、content-type)transformRequest()— デフォルトではそのまま渡すexecute()— 再試行、バックオフ、ブレーカーを備えたメインのHTTPループ
export class DefaultExecutor extends BaseExecutor { // OpenAI/Anthropic互換のすべてのプロバイダーを処理 // プロバイダーは設定(URL、認証、ヘッダー)を登録するが、エグゼキューターのロジックは共有する}プロバイダー固有の動作(認証ヘッダー、ベースURL、バージョンヘッダー)は、個別のエグゼキュータークラスではなく、プロバイダーレジストリを介して設定されます。
---
## サービス(117モジュール)
サービスは、ハンドラーによって構成される、**特定の目的に特化した単一用途のモジュール**です。主なカテゴリーは次のとおりです。
### ルーティングとコンボ
- `combo.ts` — コンボルーティングされたリクエストのエントリーポイント- `services/autoCombo/` — 16要素のスコアリング、8つの自動ルーティング戦略- `wildcardRouter.ts` — ワイルドカードルート(`gpt-*`)の照合- `modelFamilyFallback.ts` — T5ファミリー内のフォールバック
### レート制限とクォータ
- `rateLimitManager.ts` — キーとプロバイダーごとのトークンバケット- `usage.ts` — 使用量の記録- `quotaCache.ts` — インメモリのクォータスナップショット
### アカウントとトークン
- `tokenRefresh.ts` — 401発生時のOAuth更新- `accountFallback.ts` — 代替アカウントへの切り替え- `sessionManager.ts` — 複数ターンのセッション状態
### インテリジェンス
- `intentClassifier.ts` — リクエストの意図を分類- `taskAwareRouter.ts` — タスクタイプ別にルーティング- `thinkingBudget.ts` — 思考トークンを割り当て- `contextManager.ts` — ルーティングコンテキストを注入
### レジリエンス
- `resilience.ts` — 再試行、バックオフ、サーキットブレーカーのオーケストレーション- `emergencyFallback.ts` — 最終手段のフォールバック- `modelDeprecation.ts` — 後継モデルへの自動ルーティング
### 状態
- `signatureCache.ts` — リクエストシグネチャによる重複排除- `volumeDetector.ts` — 負荷制限- `contextHandoff.ts` — セッションのシリアライズ
### 圧縮
- `compression/`(サブディレクトリ)— 完全な圧縮パイプライン- エンジン、ルールパック、アダプターを網羅する39ファイル
### スキル
- ([SKILLS.md](./SKILLS.md)で説明)
### メモリ
- ([MEMORY.md](./MEMORY.md)で説明)
---
## エグゼキューター(75以上のファイル)
プロバイダーごとに1ファイルあります。すべてが`BaseExecutor`を拡張し、異なる部分をオーバーライドします。
### 共通パターン
プロバイダーは`getExecutor(providerId)`を介して解決され、設定済みのエグゼキューターが返されます。OpenAI/Anthropic互換プロバイダーは`DefaultExecutor`(`executors/default.ts`)を使用します。プロバイダー固有の動作(ベースURL、認証ヘッダー、APIバージョン)は`open-sse/config/providers/`で設定され、リクエストボディの変換は`open-sse/translator/`で処理されます。
**カスタムURL**はプロバイダー設定で指定します。
```ts// open-sse/config/providers/内のプロバイダー設定export default { id: "together", baseURL: "https://api.together.xyz/v1/chat/completions",}カスタム認証は、プロバイダーレジストリの認証設定(APIキー、OAuth、ヘッダープロファイル)を通じて処理されます。
カスタムリクエストボディの変換(たとえば、Anthropicによるsystemとmessagesの分離)は、open-sse/translator/でプロバイダーごとに登録されます。
### エグゼキューターファクトリ
`executors/index.ts`は`getExecutor(providerId)`をエクスポートします。
```tsimport { getExecutor } from "@omniroute/open-sse/executors";
const executor = getExecutor("anthropic");const result = await executor.execute({ model: "claude-sonnet-4-5", messages: [...],});解決はExecutorRegistry(executors/registry.ts)を介して行われます。特殊化された各エグゼキューターは、executors/index.tsの組み込みテーブルで宣言され、モジュールのロード時にregisterExecutor(alias, instance)を介して登録されます。getExecutor()はレジストリを参照し、特殊化されたエントリが存在しないプロバイダーについては、メモ化されたDefaultExecutorへフォールバックします。alias → executorの完全なマッピングは、ゴールデンテストtests/unit/executor-map-golden.test.tsで規定されています。
トランスレーター
Section titled “トランスレーター”3つの形式(OpenAI、Anthropic、Gemini)および新しい Responses API の間で変換します。
変換が行われるタイミング
Section titled “変換が行われるタイミング”import { needsTranslation, translateRequest } from "@omniroute/open-sse/translator";
if (needsTranslation(sourceFormat, targetFormat)) { body = translateRequest(body, sourceFormat, targetFormat);}一般的な変換:
OpenAI → Anthropic:独立したsystemフィールド、x-api-keyヘッダーOpenAI → Gemini:messagesの代わりにcontents、systemInstructionOpenAI → Responses API:input配列、previous_response_idによる状態管理
対応済みのエッジケース
Section titled “対応済みのエッジケース”developerロール → OpenAI 以外ではsystemsystemロール → GLM/ERNIE では最初のユーザーメッセージに統合json_schema→ Gemini のresponseMimeType+responseSchematools→ プロバイダー固有のツール形式- Thinking パラメーター(o1、Claude)→ プロバイダー固有の同等機能
MCP サーバー
Section titled “MCP サーバー”open-sse/mcp-server/ は Model Context Protocol サーバーを実装しています:
- 110個のツール(プロバイダー管理、コンボ、メモリ、キャッシュ、圧縮、プロキシ、スキル、ゲーミフィケーション、プラグイン、Notion、Obsidian、ローカルコーパス)
- 3つのトランスポート:stdio、SSE、Streamable HTTP
- きめ細かな認可のための 33個のスコープ
ツールの登録
Section titled “ツールの登録”ツールは open-sse/mcp-server/tools/ 内の独立したファイルとして登録され、それぞれが名前、スキーマ、ハンドラー、スコープをエクスポートします:
import { z } from "zod";export default { name: "omniroute_get_health", description: "Get system health snapshot", scope: "read:health", inputSchema: z.object({}), handler: async (_args, ctx) => { return await getSystemHealth(); },};トランスポート
Section titled “トランスポート”// stdio(CLIでの使用)startMcpStdio(server);
// SSE(HTTPベースのストリーミング)startMcpSse(server, port);
// Streamable HTTP(最新のMCP)startMcpStreamable(server, port);すべてのツール呼び出しはスコープチェック(open-sse/mcp-server/auth/)を通過します:
if (!hasScope(apiKey, "providers:read")) { throw new Error("Insufficient scope");}トランスフォーマー
Section titled “トランスフォーマー”open-sse/transformer/ は Chat Completions 形式と Responses API 形式の間で変換します。
トランスフォーマーを分離する理由
Section titled “トランスフォーマーを分離する理由”Responses API は、ステートフルな会話(previous_response_id)に対応した OpenAI の新しい形式です。クライアントが Responses リクエストを送信すると、OmniRoute は次の処理を行います:
- 内部で Responses → Chat Completions に変換
- プロバイダー(Chat Completions をサポートする任意のプロバイダー)に送信
- レスポンスを Responses 形式に再変換
- 変換されたレスポンスをクライアントにストリーミング
トランスフォーマー(transformer/responsesTransformer.ts)は以下を提供します:
createResponsesApiTransformStream(): TransformStreamこれは以下を処理します:
response.output_item.addedイベントresponse.output_text.deltaイベントresponse.completedイベント- ツール呼び出しのマッピング(
function_call↔tool_calls)
open-sse/config/ は設定レイヤーを保持します:
| ファイル | 目的 |
|---|---|
providerRegistry.ts |
352プロバイダーのカタログを対象としたチャットモデルレジストリ |
providerModels.ts |
モデルエイリアス、形式マッピング |
constants.ts |
タイムアウト、制限、ステータスコード |
defaultThinkingSignature.ts |
デフォルトのClaude Thinkingシグネチャ |
modelStrip.ts(services内) |
プロバイダーごとのフィールド除去 |
プロバイダーレジストリのスキーマ
Section titled “プロバイダーレジストリのスキーマ”interface ProviderConfig { id: string; name: string; baseUrl: string; authType: "bearer" | "api-key" | "oauth" | "cookie"; executorClass: string; defaultModel: string; capabilities: ProviderCapabilities; models: ModelDefinition[];}モジュール読み込み時の Zod 検証により、すべてのプロバイダー設定が有効であることが保証されます。
パフォーマンス制約
Section titled “パフォーマンス制約”ルーティングエンジンには厳格なパフォーマンス予算があります:
| 操作 | 目標 | 測定条件 |
|---|---|---|
| コンボ解決 | <10ms | 50ターゲットの場合 |
| レート制限チェック | <1ms | インメモリトークンバケット |
| モデルファミリーのフォールバック | <5ms | キャッシュ済みのファミリー定義 |
| リクエストルーティングのディスパッチ | <2ms | ホットパス |
| ルーティングのホットパスでブロッキングI/Oを行わない | — | すべて非同期 |
アンチパターン
Section titled “アンチパターン”❌ combo.ts内での同期DB呼び出し — 事前計算してキャッシュする
❌ ハンドラー内の再試行ロジック — レジリエンスサービスのretry()を使用する
❌ プロバイダー設定への直接アクセス — providerRegistryのゲッターを使用する
❌ ハードコードされたフォールバックチェーン — modelFamilyFallback.tsで定義する
❌ 並行リクエスト間での状態変更 — リクエストスコープのコンテキストのみを使用する
新しいコンポーネントの追加
Section titled “新しいコンポーネントの追加”新しいサービスの追加
Section titled “新しいサービスの追加”- 責務を明確に限定した
open-sse/services/[serviceName].tsを作成する - メインハンドラー関数と必要な定数をエクスポートする
tests/unit/services/[serviceName].test.mjsにユニットテストを追加する- ルーティング関連の場合は、
handlers/chatCore.tsのリクエストパイプラインに統合する - サービスがターゲット選択に影響する場合は、
combo.tsのルーティングロジックを更新する - このファイルに文書化する
新しいエグゼキューターの追加
Section titled “新しいエグゼキューターの追加”BaseExecutorを継承するopen-sse/executors/[provider].tsを作成するconfig/providerRegistry.tsに登録するexecutors/index.tsのファクトリーに追加する- エグゼキューターのユニットテストを追加する
docs/architecture/ARCHITECTURE.mdに文書化する
新しいMCPツールの追加
Section titled “新しいMCPツールの追加”open-sse/mcp-server/tools/[category]Tools.tsを作成または更新する- 入力用のZodスキーマを定義する
mcp-server/index.tsにツールを登録するmcp-server/auth/のスコープマトリクスに追加する- ユニットテストを追加する
- ARCHITECTURE.md — 高レベルアーキテクチャ
- CODEBASE_DOCUMENTATION.md — エンジニアリングリファレンス
- REPOSITORY_MAP.md — ディレクトリごとの説明
- AUTO-COMBO.md — 16要素スコアリング
- MCP-SERVER.md — MCPサーバー
- A2A-SERVER.md — A2Aサーバー
- ソース:
open-sse/(400以上のファイル、約143K LOC)
HagiCode
HagiCode は構造化ワークフロー、マルチエージェント実行、Hero Dungeon ビューを備えたエージェント型コーディングワークスペースです。
よりスマートで速く、楽しいエージェント型ワークフローで、使いやすいソフトウェアを形にします。

- Smart構造化ワークフローは意図をアイデアから変更のリリースまで実行可能な道筋にします。
- Efficientマルチエージェントのワークフローで調査、実装、レビューを並行して進めます。
- FunHero Dungeon により長時間のコーディングを視覚的で協力的な体験にします。