open-sse Architecture (한국어)
별도의 워크스페이스 패키지를 사용하는 이유
섹션 제목: “별도의 워크스페이스 패키지를 사용하는 이유”open-sse/는 다음과 같은 여러 이유로 OmniRoute 모노레포에서 독립형 워크스페이스로 구성됩니다.
- 재사용성 —
open-sse는 npm에@omniroute/open-sse로 게시되므로 다른 프로젝트에서도 독립적으로 사용할 수 있습니다 - 명확한 경계 — 스트리밍 엔진이 OmniRoute 전용 UI/DB 계층과 분리되어 있습니다
- 성능 — 엔진에 Next.js 종속성이 없어 CLI/서버리스 환경에서 더 빠른 콜드 스타트가 가능합니다
- 버전 관리 —
open-sse는 자체 주기에 따라 릴리스할 수 있습니다
"workspaces": ["open-sse"]최상위 구조
섹션 제목: “최상위 구조”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/ # 공유 유틸리티(로깅, 오류, 스트림 등)모듈 수
섹션 제목: “모듈 수”| 디렉터리 | 파일 수 | 용도 |
| executors/ | 167 | 제공자별 HTTP 실행기(DefaultExecutor 팩토리를 통해 통합) |
| handlers/ | 157 | 요청 진입점(chatCore, responses, embeddings) |
| services/ | ~536 | 라우팅, 캐싱, 속도 제한, 갱신 등 |
| translator/ | 56 | 형식 변환(OpenAI ↔ Claude ↔ Gemini) |
| mcp-server/ | 44 | MCP 도구 및 전송 방식 |
| utils/ | ~108 | 공통 유틸리티(로깅, 오류, 스트림) |
| config/ | ~339 | 제공자 설정, 상수, 레지스트리 |
요청 파이프라인
섹션 제목: “요청 파이프라인”모든 LLM 요청은 5단계 파이프라인을 거칩니다.
┌──────────────┐ HTTP 요청 │ 1. 라우팅 │ 콤보 해석, 모델 선택 (Next.js 라우트) └──────┬───────┘ │ ▼ ┌──────────────┐ │ 2. 변환 │ 형식 변환(OpenAI ↔ Claude ↔ Gemini) └──────┬───────┘ │ ▼ ┌──────────────┐ │ 3. 실행 │ 제공자 실행기, HTTP, 재시도, 차단기 └──────┬───────┘ │ ▼ ┌──────────────┐ │ 4. 스트리밍 │ SSE 변환, 백프레셔 └──────┬───────┘ │ ▼ ┌──────────────┐ │ 5. 기록 │ 사용량 추적, 호출 로그, 오류 분류 └──────┬───────┘ │ ▼ HTTP 응답(SSE 또는 JSON)1단계: 라우팅(services/combo.ts)
섹션 제목: “1단계: 라우팅(services/combo.ts)”진입점: services/combo.ts의 handleComboChat()
요청을 구체적인 (provider, model, account, credentials) 튜플로 해석합니다.
- ID로 콤보를 조회하거나
auto/*모델을 위한 가상 콤보 생성 - 라우팅 전략 적용(우선순위, 가중치, 라운드 로빈 등)
- 비정상 제공자 제외(서킷 브레이커)
- 사용 가능한 다음 대상 선택
auto/* 모델의 경우 이 단계에서 다음 작업도 수행합니다.
- 16개 요소 기반 점수 산정 알고리즘 실행(
services/autoCombo/) - 상태, 비용, 지연 시간 등을 기준으로
provider+model쌍 선택
2단계: 변환(translator/)
섹션 제목: “2단계: 변환(translator/)”소스 형식(예: OpenAI)이 대상 형식(예: Claude)과 다르면 요청이 변환됩니다.
- 시스템 프롬프트 → 시스템 메시지
- 도구 정의 → 제공자별 도구 형식
- 추론/사고 매개변수 → 제공자별 대응 항목
- 메시지 역할 정규화(OpenAI 이외의 경우
developer→system)
translator/index.ts는 다음을 노출합니다.
translateRequest(body, sourceFormat, targetFormat): TranslatedRequestneedsTranslation(source, target): boolean3단계: 실행(executors/)
섹션 제목: “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)
섹션 제목: “4단계: 스트리밍(utils/stream.ts)”스트리밍 응답의 경우 실행기는 ReadableStream을 반환합니다. 핸들러는 다음 작업을 수행합니다.
- SSE 변환을 통해 파이프 처리(
createSSETransformStreamWithLogger) - 비활성 연결을 감지하기 위한 하트비트 핑 적용
- 클라이언트 연결 해제를 정상적으로 처리(
pipeWithDisconnect) - 비스트리밍 클라이언트를 위해 SSE → JSON 변환
비스트리밍 응답의 경우 실행기는 파싱된 JSON 객체를 반환하며, 이 객체는 변경 없이 그대로 전달됩니다.
5단계: 기록(services/usage.ts)
섹션 제목: “5단계: 기록(services/usage.ts)”응답 후(성공 또는 실패), 사용량이 기록됩니다:
- 응답의
prompt_tokens,completion_tokens,cached_tokens - 가격 데이터를 기반으로 계산된
cost_usd - 실패한 경우
latency_ms,status,error_class usage_history테이블에 저장됨
호출 로그 아티팩트가 활성화된 경우 ${DATA_DIR}/call_logs/에 기록됩니다.
주요 파일 심층 분석
섹션 제목: “주요 파일 심층 분석”chatCore.ts (5977줄)
섹션 제목: “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);}하나의 거대한 함수이지만, 5단계 파이프라인에 대응하는 주석이 달린 섹션으로 구성되어 있습니다.
combo.ts (4456 LOC)
섹션 제목: “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, err }); } } throw new ComboExhaustedError("모든 대상이 실패했습니다");}19가지 라우팅 전략을 지원합니다(src/shared/constants/routingStrategies.ts 참조).
| 전략 | 동작 |
|---|---|
priority |
첫 번째 대상 우선의 순서 지정 목록 |
weighted |
대상별 가중치에 따른 확률적 선택 |
round-robin |
대상들을 순서대로 순환 |
context-relay |
대상 간에 컨텍스트 전달 |
fill-first |
다음 대상으로 이동하기 전에 할당량을 먼저 채움 |
p2c |
두 개 선택지 중 최적 선택 |
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)
섹션 제목: “base.ts (1170 LOC)”107개의 모든 실행기가 확장하는 추상 실행기입니다. 다음 항목을 포함합니다.
buildUrl()— 기본 URL 구성(사용자 정의가 필요한 경우 하위 클래스에서 재정의)buildHeaders()— 기본 헤더(인증, 콘텐츠 유형)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개 이상의 파일)
제공자마다 하나의 파일이 있습니다. 모두 `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로 폴백합니다. 전체 별칭 → 실행기 매핑은 골든 테스트 tests/unit/executor-map-golden.test.ts로 명세됩니다.
변환기
섹션 제목: “변환기”3가지 형식인 OpenAI, Anthropic, Gemini와 새로운 Responses API 간에 변환합니다.
변환이 수행되는 시점
섹션 제목: “변환이 수행되는 시점”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상태
처리되는 엣지 케이스
섹션 제목: “처리되는 엣지 케이스”developer역할 → OpenAI 이외의 경우systemsystem역할 → GLM/ERNIE의 경우 첫 번째 사용자 메시지에 병합json_schema→ Gemini의responseMimeType+responseSchematools→ 제공자별 도구 형식- 사고 매개변수(o1, Claude) → 제공자별 대응 항목
MCP 서버
섹션 제목: “MCP 서버”open-sse/mcp-server/는 Model Context Protocol 서버를 구현합니다.
- 110개의 도구(제공자 관리, 조합, 메모리, 캐시, 압축, 프록시, 스킬, 게임화, 플러그인, Notion, Obsidian, 로컬 코퍼스)
- 3가지 전송 방식: stdio, SSE, Streamable HTTP
- 세분화된 권한 부여를 위한 33개의 범위
도구 등록
섹션 제목: “도구 등록”도구는 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(); },};전송 방식
섹션 제목: “전송 방식”// 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");}변환기
섹션 제목: “변환기”open-sse/transformer/는 Chat Completions와 Responses API 형식 간에 변환합니다.
별도의 변환기가 필요한 이유
섹션 제목: “별도의 변환기가 필요한 이유”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 사고 시그니처 |
modelStrip.ts (서비스 내) |
제공자별 필드 제거 |
제공자 레지스트리 스키마
섹션 제목: “제공자 레지스트리 스키마”interface ProviderConfig { id: string; name: string; baseUrl: string; authType: "bearer" | "api-key" | "oauth" | "cookie"; executorClass: string; defaultModel: string; capabilities: ProviderCapabilities; models: ModelDefinition[];}모듈 로드 시 Zod 검증을 수행하여 모든 제공자 구성이 유효한지 확인합니다.
성능 제약 조건
섹션 제목: “성능 제약 조건”라우팅 엔진에는 엄격한 성능 예산이 적용됩니다.
| 작업 | 목표 | 측정 기준 |
|---|---|---|
| 콤보 결정 | <10ms | 대상 50개 기준 |
| 속도 제한 확인 | <1ms | 인메모리 토큰 버킷 |
| 모델 패밀리 폴백 | <5ms | 캐시된 패밀리 정의 |
| 요청 라우팅 디스패치 | <2ms | 핫 패스 |
| 라우팅 핫 패스에서 블로킹 I/O 금지 | — | 모두 비동기 |
안티패턴
섹션 제목: “안티패턴”❌ combo.ts에서 동기식 DB 호출 — 미리 계산하고 캐시할 것
❌ 핸들러 내 재시도 로직 — 복원력 서비스의 retry()를 사용할 것
❌ 프로바이더 설정에 직접 접근 — providerRegistry getter를 사용할 것
❌ 하드코딩된 폴백 체인 — modelFamilyFallback.ts에 정의할 것
❌ 동시 요청 간 상태 변경 — 요청 범위 컨텍스트만 사용할 것
새 컴포넌트 추가
섹션 제목: “새 컴포넌트 추가”새 서비스 추가
섹션 제목: “새 서비스 추가”- 명확한 단일 책임을 갖는
open-sse/services/[serviceName].ts생성 - 기본 핸들러 함수와 필요한 상수 내보내기
tests/unit/services/[serviceName].test.mjs에 단위 테스트 추가- 라우팅과 관련된 경우
handlers/chatCore.ts의 요청 파이프라인에 통합 - 서비스가 대상 선택에 영향을 주는 경우
combo.ts의 라우팅 로직 업데이트 - 이 파일에 문서화
새 실행기 추가
섹션 제목: “새 실행기 추가”BaseExecutor를 확장하는open-sse/executors/[provider].ts생성config/providerRegistry.ts에 등록executors/index.ts팩토리에 추가- 실행기 단위 테스트 추가
docs/architecture/ARCHITECTURE.md에 문서화
새 MCP 도구 추가
섹션 제목: “새 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은 긴 코딩 세션을 시각적이고 협업적인 경험으로 만듭니다.