Codex
Codex 어댑터는 OpenAI Codex(@openai/codex-sdk를 통해)를 채팅 백엔드로 실행합니다. HTTP 공급자 어댑터와 달리 이 어댑터는 하네스 어댑터입니다. Codex가 자체 에이전트 루프를 실행하고 자체 도구인 셸 명령, 파일 변경, 웹 검색을 서버의 샌드박스 안에서 로컬로 실행합니다. 각 chat() 호출은 하나의 전체 하네스 턴을 실행하며, 하네스의 도구 활동은 UI에서 렌더링할 수 있도록 이미 해결된 도구 호출 이벤트로 스트리밍됩니다.
서버 전용입니다. 하네스는 SDK에 포함된 Codex 런타임을 하위 프로세스로 생성하므로 이 어댑터는 Node.js 서버 환경에서만 작동하며 브라우저에서는 작동하지 않습니다. 샌드박스 모드는 안전 경계이므로 신중하게 구성해야 합니다.
설치
npm install @tanstack/ai-codex
실행 가능한 데모는 examples/sandbox-cloudflare에 있습니다. UI에서 Claude Code, Codex 또는 Grok Build를 선택하고, 세션 재개, 하네스 도구 타임라인, 도구 브리징이 포함된 TanStack Start 앱을 Workers에서 실행할 수 있습니다. 영속적이고 새로 고침 후에도 유지되는 실행을 일반 Node에서 동일하게 구성한 예제(Claude Code on Docker)는 examples/sandbox-web를 참조하세요. 이 어댑터로 교체하는 작업은 한 줄만 변경하면 됩니다(src/sandbox-agent.ts).
인증
노트북에는 이미 codex login이 되어 있을 수 있습니다. CI 실행자에는
CODEX_API_KEY만 있을 수 있습니다. 기본 authMode는 'api-key'입니다. codex login을
사용하려면 'host'로 설정하세요. 하네스 인증을 참조하세요.
import { codexText } from "@tanstack/ai-codex"
codexText("gpt-5.5")
codexText("gpt-5.5", { authMode: "host" })
'api-key'(기본값):CODEX_API_KEY를 사용한다고 가정합니다(또는apiKey를 전달합니다).'host':codex login을 사용합니다.CODEX_API_KEY를 주입하지 않습니다.
기본 사용법
import { chat } from "@tanstack/ai";
import { codexText } from "@tanstack/ai-codex";
const stream = chat({
adapter: codexText("gpt-5.1-codex", {
cwd: "/path/to/project",
sandboxMode: "workspace-write",
}),
messages: [{ role: "user", content: "Fix the failing test in utils.test.ts" }],
});
구성
| 옵션 | 설명 |
|---|---|
cwd | 하네스 세션의 작업 디렉터리입니다. 기본값은 process.cwd()입니다. |
sandboxMode | Codex 샌드박스입니다. 'read-only', 'workspace-write' 또는 'danger-full-access'를 사용합니다. local-process와 Docker에서는 기본값이 'workspace-write'입니다. Daytona와 Cloudflare에서는 중첩된 bubblewrap 네임스페이스를 만들 수 없으므로 기본값이 'danger-full-access'입니다. 이 경우 격리는 외부 VM과 defineSandboxPolicy가 담당합니다. |
approvalPolicy | Codex 승인 정책입니다. 기본값은 'never'입니다. 헤드리스 실행에는 승인 UI가 없으므로 다른 값을 사용하면 턴이 멈출 수 있습니다. |
modelReasoningEffort | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'입니다. |
skipGitRepoCheck | 하네스의 git 저장소 안전 검사를 건너뜁니다. 기본값은 true입니다(서버 어댑터는 일반적으로 임시 디렉터리를 가리킵니다). |
networkAccessEnabled | workspace-write 샌드박스 내부의 네트워크 액세스를 허용합니다. |
webSearchMode | 'disabled' | 'cached' | 'live'입니다. |
additionalDirectories | cwd 외에 쓰기가 가능한 추가 디렉터리입니다. |
authMode | 'api-key'(기본값)는 CODEX_API_KEY를 사용합니다. 'host'는 codex login을 사용합니다. 하네스 인증을 참조하세요. |
apiKey | 하네스 하위 프로세스에 사용할 OpenAI API 키입니다. |
baseUrl | Codex 백엔드 기본 URL을 재정의합니다. |
codexPathOverride | SDK에 포함된 바이너리 대신 특정 codex 실행 파일을 사용합니다. |
env | 하위 프로세스의 환경 변수입니다. 설정하면 process.env를 상속하지 않습니다(Codex SDK 의미 체계). |
config | Codex CLI에 전달할 추가 --config key=value 재정의입니다(예: 추가 mcp_servers 항목). |
호출별 재정의는 modelOptions를 통해 전달합니다: sessionId, sandboxMode,
approvalPolicy, modelReasoningEffort, workingDirectory,
skipGitRepoCheck, authMode입니다.
상태 저장 세션
Codex 스레드는 상태를 저장합니다. 하네스는 턴 사이에 전체 작업 컨텍스트(읽은 파일, 실행한 명령, 도출한 결론)를 유지합니다. 어댑터는 새 실행마다 스레드 ID를 codex.session-id라는 사용자 지정 스트림 이벤트로 노출합니다. 재개하려면 이 ID를 modelOptions.sessionId로 다시 전달하세요. 재개할 때는 최신 사용자 메시지만 전송하면 됩니다. 하네스가 이전 컨텍스트를 이미 보유하고 있기 때문입니다.
서버 엔드포인트:
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { codexText } from "@tanstack/ai-codex";
export async function POST(request: Request) {
const params = await chatParamsFromRequest(request);
// Extra fields the client puts in the connection `body` arrive here.
const sessionId =
typeof params.forwardedProps.sessionId === "string"
? params.forwardedProps.sessionId
: undefined;
const stream = chat({
adapter: codexText("gpt-5.1-codex", {
cwd: "/path/to/project",
sandboxMode: "workspace-write",
}),
messages: params.messages,
modelOptions: { sessionId },
});
return toServerSentEventsResponse(stream);
}
클라이언트(React) — 사용자 지정 이벤트에서 세션 ID를 캡처하고 이후 요청에 다시 전송합니다.
import { useState } from "react";
import { useChat } from "@tanstack/ai-react";
import { fetchServerSentEvents } from "@tanstack/ai-client";
function CodingAssistant() {
const [sessionId, setSessionId] = useState<string | undefined>(undefined);
const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat", () => ({
body: { sessionId },
})),
onCustomEvent: (name, value) => {
if (
name === "codex.session-id" &&
typeof value === "object" &&
value !== null &&
"sessionId" in value &&
typeof value.sessionId === "string"
) {
setSessionId(value.sessionId);
}
},
});
// ... render messages; harness tool activity (command_execution,
// file_change, ...) arrives as regular tool-call parts with results.
}
세션은 세션을 실행한 머신(~/.codex/sessions/)에 저장되므로 같은 서버 인스턴스에서만 재개할 수 있습니다.
도구
이 어댑터를 통해 흐르는 도구는 두 종류입니다.
-
내장 하네스 도구는 Codex 자체가 실행하며, 결과가 이미 연결된 도구 호출 이벤트로 다시 스트리밍됩니다:
command_execution(셸),file_change(패치),web_search,todo_list(에이전트의 진행 중인 계획)입니다. 코드는 이를 실행하지 않습니다. -
TanStack 도구는 하네스 안으로 브리징됩니다. 어댑터는 턴이 진행되는 동안
127.0.0.1에서 수명이 짧은 Streamable-HTTP MCP 서버를 시작하고 Codex가 이 서버를 가리키도록 합니다. 평소처럼toolDefinition().server()로 도구를 정의하면 도구 호출 이벤트가 등록한 이름으로 돌아옵니다.
import { z } from "zod";
import { chat, toolDefinition } from "@tanstack/ai";
import { codexText } from "@tanstack/ai-codex";
const lookupTicket = toolDefinition({
name: "lookup_ticket",
description: "Look up an issue ticket by id",
inputSchema: z.object({ ticketId: z.string() }),
}).server(async ({ ticketId }) => {
return { ticketId, status: "open", title: "Crash on startup" };
});
const stream = chat({
adapter: codexText("gpt-5.1-codex"),
messages: [{ role: "user", content: "What's the status of ticket T-123?" }],
tools: [lookupTicket],
});
클라이언트 측 도구와 승인 게이트 도구는 지원되지 않습니다. 하네스는 활성 하위 프로세스 내부에서 도구를 실행하므로, 브라우저 왕복이나 사람의 승인을 기다리기 위해 HTTP 요청 사이에서 일시 중지할 수 없습니다. 서버 execute() 구현이 없는 도구 또는 needsApproval로 표시된 도구를 전달하면 설명이 포함된 오류와 함께 즉시 실패합니다. 이러한 도구는 일반 공급자 어댑터를 사용해 하네스 외부에서 실행하세요.
구조화된 출력
chat()에 outputSchema를 전달합니다. Codex는 하나의 하네스 턴을 실행하고 --output-schema로 마지막 메시지를 제한합니다. Codex가 기록하는 동안 도구 활동과 어시스턴트 텍스트가 스트리밍됩니다. 마지막 메시지는 스키마 객체로도 파싱되어 structured-output.complete으로 도착합니다.
import { chat } from "@tanstack/ai"
import { codexText } from "@tanstack/ai-codex"
import { defineSandbox, withSandbox } from "@tanstack/ai-sandbox"
import { dockerSandbox } from "@tanstack/ai-sandbox-docker"
import { z } from "zod"
const Report = z.object({
summary: z.string(),
filesChanged: z.array(z.string()),
})
const sandbox = defineSandbox({
id: "repo-report",
provider: dockerSandbox({ image: "node:22" }),
})
const report = await chat({
adapter: codexText("gpt-5.3-codex"),
messages: [{ role: "user", content: "Review this repo." }],
outputSchema: Report,
middleware: [withSandbox(sandbox)],
})
report.summary
클라이언트에서는 동일한 스키마를 useChat에 전달하고 final을 읽습니다. partial은 마지막까지 비어 있습니다.
import { fetchServerSentEvents, useChat } from "@tanstack/ai-react"
import { z } from "zod"
const Report = z.object({
summary: z.string(),
filesChanged: z.array(z.string()),
})
function ReportView() {
const { final, isLoading } = useChat({
connection: fetchServerSentEvents("/api/repo-report"),
outputSchema: Report,
})
if (isLoading) return <p>The agent is inspecting the repo.</p>
if (!final) return null
return <p>{final.summary}</p>
}
프롬프트에서 JSON만 추출하고 샌드박스가 필요하지 않다면 @tanstack/ai-openai를 사용하세요. 이 경로가 더 빠릅니다.
클라이언트를 포함한 전체 안내는 하네스 에이전트를 참조하세요.
제한 사항
- 토큰 수준 텍스트 스트리밍은 지원되지 않습니다. Codex SDK는 어시스턴트 텍스트와 추론을 완료된 항목으로만 보고하므로 텍스트가 메시지 단위로 도착합니다. 도구 활동(명령 시작/완료)은 여전히 실시간으로 스트리밍되므로 긴 턴 동안에도 UI가 계속 반응하는 것처럼 보입니다.
- 서버 전용(Node)입니다. 하네스가 하위 프로세스를 생성합니다.
- 하네스가 에이전트 루프를 소유합니다. TanStack의 에이전트 루프 전략과 반복별 미들웨어는 하네스 턴 내부에 적용되지 않습니다.
- 샘플링 제어가 없습니다.
temperature방식의 옵션은 여기에서 존재하지 않습니다. - 세션은 머신 로컬입니다. 재개하려면 동일한 서버 인스턴스에 접속해야 합니다.
- 콜드 스타트가 발생합니다. 각 호출이 하네스 턴을 생성하므로 HTTP 어댑터보다 첫 토큰 지연 시간이 더 길 수 있습니다.