It’s written by gpt-5.2 pro
Python 중심으로 개발해 온 사람이 opencode 레포를 읽기 시작하면, “TypeScript 문법 자체”보다 **이 레포가 코드를 어떻게 나누고(모듈), 타입을 어떻게 다루고(타입/런타임 경계), 서버와 도구를 어떻게 엮는지(Bun/Hono/Zod)**가 먼저 걸립니다.
이 글은 바로 코드로 들어가기 위한 최소 개념 + 실제 파일 위치를 중심으로 정리한 “온보딩 글”입니다.
0) 먼저 잡아야 할 큰 그림: opencode는 무엇을 하는가?
opencode를 한 문장으로 요약하면 다음이 가장 읽기 편합니다.
- CLI가 로컬에서 서버를 띄우고
- 클라이언트(TUI/웹/SDK)가 HTTP API를 호출하며
- 이벤트는 SSE(Server‑Sent Events) 처럼 스트리밍으로 흘린다
이 큰 그림을 가장 빠르게 보여주는 문서가 specs/project.md입니다.
- 설계 목표:
specs/project.md:3 - API 스케치:
specs/project.md:9
1) “Spec 문서”는 정확히 무엇을 말하나?
specs/project.md는 “현재 구현의 완전한 문서”라기보단, opencode가 지향하는 API/구조를 스케치합니다.
핵심은 이 목표입니다.
- 단일 opencode 인스턴스가
- 여러 프로젝트/여러 worktree를 대상으로
- 세션을 실행/관리할 수 있게 한다 (
specs/project.md:3)
그리고 그 목표를 이루기 위해 /project 아래에서 프로젝트/세션/메시지/파일을 다루는 형태로 엔드포인트가 제안됩니다. (specs/project.md:9)
2) API는 어떻게 실행/호출하나?
결론부터 말하면:
- 서버를 먼저 띄우고
- 그 서버에 HTTP 요청을 보내면 됩니다
2.1 서버는 어떤 코드가 띄우나?
CLI의 serve 커맨드가 대표적이며, 결국 Server.listen(...)을 호출합니다.
serve커맨드 정의:packages/opencode/src/cli/cmd/serve.ts:5- 실제 서버 시작:
packages/opencode/src/cli/cmd/serve.ts:11
2.2 기본 host/port는 어디서 정해지나?
네트워크 옵션은 한 곳에서 정리됩니다.
- 옵션 정의:
packages/opencode/src/cli/network.ts:5 - 기본값:
packages/opencode/src/cli/network.ts:13(hostname=127.0.0.1,port=0)
port=0은 “랜덤 포트(빈 포트를 OS가 선택)”로 이해하면 됩니다.
2.3 웹 UI까지 띄우고 싶다면?
web 커맨드는 서버를 띄운 다음 브라우저를 여는 흐름을 포함합니다.
packages/opencode/src/cli/cmd/web.ts:30
3) 서버 코드는 어디서 보고, 어떤 방식으로 구성되나?
처음 읽을 때는 아래 3개만 잡아도 흐름이 보입니다.
- 메인 서버/라우터:
packages/opencode/src/server/server.ts - 프로젝트 라우트:
packages/opencode/src/server/project.ts - TUI 라우트:
packages/opencode/src/server/tui.ts
3.1 Hono + Bun 조합
서버는 Hono로 라우팅을 만들고, Bun의 HTTP 서버로 이를 서빙합니다.
Server네임스페이스 시작:packages/opencode/src/server/server.ts:58/project하위 라우트 마운트:packages/opencode/src/server/server.ts:273- OpenAPI 문서 엔드포인트(
/doc) 근방:packages/opencode/src/server/server.ts:259 - 서버 리스닝(
listen) 시작:packages/opencode/src/server/server.ts:2792
Hono는 app.fetch 형태의 핸들러를 제공하므로(“fetch 함수처럼 동작”), Bun이 이를 Bun.serve({ fetch: ... })로 바로 붙일 수 있습니다.
3.2 OpenAPI/입력 검증 패턴
이 레포는 “문서화 + 검증”을 코드 레벨에서 같이 가져갑니다.
describeRoute(...): 라우트 설명(요약/operationId/응답 스키마 등)validator(...): 요청 파라미터/바디 검증
관련 import를 보면 hono-openapi를 쓰는 것을 알 수 있습니다.
packages/opencode/src/server/server.ts:5
4) Python 개발자가 TypeScript 레포를 읽을 때 제일 헷갈리는 지점: “타입은 어디에 존재하나?”
TypeScript에서 가장 중요한 감각은 **“타입은 런타임에 없다”**입니다.
- 런타임(실제 실행되는 값):
const,function,class,z.object(...)같은 것 - 타입(컴파일 타임 전용):
type,interface,Omit<...>,T extends ...같은 것
Python에서는 타입 힌트가 런타임 로직과 느슨하게 공존할 수 있지만, TS는 “타입 정보가 빌드 결과물에서 사라진다”는 점이 훨씬 강하게 체감됩니다.
그래서 opencode는 **Zod(런타임 스키마/검증) + TypeScript 타입(정적 타입)**을 함께 씁니다.
5) import가 ../..(상대경로)로 많은 이유와, @/…는 뭔가?
5.1 상대경로 import가 흔한 이유
TS/JS에서 상대경로 import(../x)는 “기본값”에 가깝습니다.
- 추가 설정 없이도 항상 동작하고
- 런타임/번들러 차이에 덜 민감합니다
5.2 그런데 opencode는 alias도 같이 쓴다
코드에서 @/…가 보이면, 이건 “npm scope(@opencode-ai/…)”가 아니라 tsconfig path alias입니다.
- 예:
packages/opencode/src/server/server.ts:1
실제로 alias는 패키지의 tsconfig에 정의되어 있습니다.
packages/opencode/tsconfig.json:11
6) “절대경로 하나로 통일하면 더 깔끔하지 않나?”에 대한 현실적인 답
Python에서 절대 import는 보통 패키지 구조/런타임 import 시스템이 잘 맞아떨어지는 경우가 많습니다.
반면 TS/JS에서 “절대경로처럼 보이는 import”는 대부분:
- TypeScript의
compilerOptions.paths - 런타임/번들러(Bun, Vite, Webpack 등)의 해석 규칙
이 둘이 동시에 맞아야 합니다.
그래서 많은 코드베이스가 현실적으로:
- 가까운 곳은 상대경로(설정 의존 ↓)
- 멀리 건너가는 공용 모듈은 alias(가독성 ↑,
../../..지옥 회피)
를 섞어서 씁니다.
VSCode에서 파일 이동 시 import 자동 갱신은?
가능합니다. (TypeScript language service 기능)
- VSCode 설정:
"typescript.updateImportsOnFileMove.enabled": "always"
7) packages/opencode/tsconfig.json의 paths는 어떻게 읽나?
packages/opencode/tsconfig.json에 이런 설정이 있습니다.
"@/*": ["./src/*"]"@tui/*": ["./src/cli/cmd/tui/*"]
의미는 단순합니다.
@/x→packages/opencode/src/x@tui/foo→packages/opencode/src/cli/cmd/tui/foo
즉, packages/opencode 패키지 안에서 src/를 루트처럼 쓰겠다는 뜻입니다.
주의할 점:
paths는 TS 컴파일러/IDE가 모듈을 찾는 규칙입니다.- 런타임에서도 동일하게 동작하려면, 실행 환경(Bun)이나 빌드가 그 규칙을 동일하게 해석해야 합니다.
8) Bun은 뭐고, import { $ } from "bun"은 뭔가?
8.1 Bun의 역할
Bun은 이 레포에서:
- 런타임
- 패키지 매니저
- 테스트 러너
역할을 동시에 합니다.
- 테스트:
packages/opencode/package.json:10 - 개발 실행:
packages/opencode/package.json:12
8.2 import { $ } from "bun"의 의미
이건 from package import * 같은 “전체 import”가 아니라, named import입니다.
- Bun이 export하는 것 중 이름이
$인 심볼 “하나만” 가져옵니다.
이 레포에서 $는 주로 **서브프로세스 실행(쉘 커맨드 실행)**에 사용됩니다.
- 예:
packages/opencode/src/tool/bash.ts:11
9) Zod는 왜 쓰나? (타입이 있는데 굳이?)
TypeScript 타입은 런타임에 사라지기 때문에, 외부 입력(HTTP 요청, 파일, 유저 입력)을 다룰 때는 런타임 검증이 필요합니다.
Zod는:
- 런타임 검증:
schema.parse(input) - 타입 추론:
z.infer<typeof schema>
을 동시에 제공합니다.
opencode의 Tool 시스템은 이 패턴을 표준처럼 사용합니다.
- Tool 타입 정의:
packages/opencode/src/tool/tool.ts:25 - BashTool 파라미터 스키마:
packages/opencode/src/tool/bash.ts:59
10) 왜 packages/opencode/src/tool/에 .ts와 .txt가 쌍으로 있나?
packages/opencode/src/tool/를 보면 bash.ts와 bash.txt 같은 쌍이 반복됩니다.
의도는 명확합니다.
.ts: 도구 실행 로직.txt: 도구 설명(긴 프롬프트/문서)
예를 들어 bash 도구는 설명을 텍스트 파일에서 읽어옵니다.
.txtimport:packages/opencode/src/tool/bash.ts:5- description 적용:
packages/opencode/src/tool/bash.ts:58
도구들을 모아서 “어떤 도구를 노출할지”를 결정하는 곳은 레지스트리입니다.
packages/opencode/src/tool/registry.ts:91
11) 타입이 어렵게 느껴질 때: 이 한 줄을 읽는 방법
실제 코드에 이런 줄이 있습니다.
export type Info = Omit<z.infer<typeof Info>, "template"> & { template: Promise<string> | string }
- 위치:
packages/opencode/src/command/index.ts:41
11.1 단계별로 해석하기
z.infer<typeof Info>
- “Zod 스키마
Info(런타임 값)로부터 TypeScript 타입을 추론”
Omit<..., "template">
- 그 타입에서
template만 제거
& { template: Promise<string> | string }
template을 원하는 타입으로 다시 붙여서 합성(교차 타입)
11.2 왜 타입을 ‘보정’하나?
같은 파일에 이유가 코멘트로 남아 있습니다.
template은z.promise(z.string()).or(z.string())로 정의됨:packages/opencode/src/command/index.ts:32- 그런데 추론 결과가 기대와 달라 수동 override:
packages/opencode/src/command/index.ts:40
즉, 런타임 검증(Zod)은 유지하면서, TS 타입만 정확히 보정하는 패턴입니다.
12) Info<Parameters extends z.ZodType> 같은 문법(제네릭)은 어떻게 읽나?
Tool 타입 정의를 보면 이런 형태가 있습니다.
packages/opencode/src/tool/tool.ts:25
핵심은:
Info<...>는 제네릭 타입Parameters extends z.ZodType는 “Parameters는 Zod 스키마 타입이어야 한다”라는 제약
이렇게 해두면, 도구마다 다른 Zod 스키마를 넣어도 execute(args)의 args 타입이 자동으로 따라옵니다.
execute(args: z.infer<Parameters>, ...):packages/opencode/src/tool/tool.ts:30
13) TypeScript의 namespace/class/interface/type/enum은 이 레포에서 어떻게 쓰이나?
“TS 언어 기능”을 전부 공부하기보다, 이 레포에서 실제로 자주 보이는 형태만 잡아도 코드를 읽는 속도가 빨라집니다.
- module(모듈): 파일 단위 (
import/export) - namespace: 한 파일 안에서 기능을 묶는 그룹핑
Server:packages/opencode/src/server/server.ts:58Tool:packages/opencode/src/tool/tool.ts:6
- type alias:
Omit,&같은 조합에 자주 사용packages/opencode/src/command/index.ts:41
- interface: 객체의 “모양”/API 계약 정의
Tool.Info:packages/opencode/src/tool/tool.ts:25
- class: 상태/행동을 가진 유틸
AsyncQueue<T>:packages/opencode/src/util/queue.ts:1
- enum: 고정된 값 집합
ApplyPatchError:packages/opencode/src/patch/index.ts:52
Python 관점으로 비유하면:
type/interface는typing의TypedDict/Protocol같은 “정적 계약”에 가깝지만, TS는 런타임에서 사라짐- 런타임 검증은 Python의
pydantic처럼, 여기서는zod가 담당
14) 바로 코드 읽기 시작하는 추천 루트
아래 순서로 열면, opencode의 “서버/도구/검증/alias” 핵심이 빠르게 잡힙니다.
specs/project.md:3packages/opencode/src/cli/cmd/serve.ts:11packages/opencode/src/server/server.ts:58packages/opencode/tsconfig.json:11packages/opencode/src/tool/tool.ts:25packages/opencode/src/tool/bash.ts:53packages/opencode/src/command/index.ts:41