@abto-app/sdk
v0.0.1
Published
ABTO SDK — broad autocapture of user behavior x LLM cost/latency/quality attribution. Browser (.) and Node server (./server) in one package.
Readme
@abto-app/sdk
ABTO SDK는 브라우저에서 관측 가능한 사용자 행동을 수집하고, Gateway가 반환한 request_id를 통해 서버의 LLM 비용·지연 데이터와 연결한다.
@abto-app/sdk: Browser SDK — autocapture, custom events, AI trace@abto-app/sdk/server: Node Server SDK — Gateway 호출과 context 전달
이 문서의 앞부분은 Browser SDK를 다룬다. 브라우저와 서버는 같은 npm 패키지로 배포되지만 API와 책임은 분리되어 있다.
PostHog의 autocapture, ingestion, session, schema/discovery는 ABTO 이벤트 설계의 참고 모델이다. ABTO 이벤트를 PostHog로 보내는 연동이 아니라, 검증된 수집 철학을 ABTO 독립 수집 구조에 적용한다.
설치
pnpm add @abto-app/sdk이벤트 경계
Browser SDK가 보내는 이벤트는 두 종류다.
| 종류 | 이름 | 정의 주체 | 발생 방식 |
|---|---|---|---|
| 시스템 이벤트 | $로 시작 | ABTO | SDK 자동 수집 또는 전용 API |
| 커스텀 이벤트 | $ 없이 제품 도메인 이름 사용 | 고객 저장소 | client.capture() |
사용자는 $ 이벤트나 $ 속성을 등록할 수 없다. ABTO 시스템 이벤트도 일반 capture()로 보낼 수 없으며 SDK 내부 경로와 AI trace 전용 메서드만 발생시킨다.
Browser SDK 시스템 이벤트
| 이벤트 | 의미 | 발생 조건 |
|---|---|---|
| $pageview | 페이지/SPA route 진입 | 초기 로드, history 변경, bfcache 복원 |
| $pageleave | 페이지/route 이탈 | SPA 이동, pagehide |
| $autocapture | DOM 상호작용 원시 사실 | click, change, submit, copy |
| $rageclick | 짧은 시간의 반복 클릭 | SDK 휴리스틱 |
| $dead_click | 반응이 관측되지 않은 클릭 | SDK 휴리스틱 |
| $ai_prompt_submitted | 프롬프트 제출을 앱이 확인 | trace.submitPrompt() |
| $ai_response_rendered | 응답이 UI에 렌더됨을 앱이 확인 | trace.markResponseRendered() |
| $ai_response_interacted | 응답에 대한 명시적 행동 | trace.captureResponseInteraction() |
$session_start와 $session_end는 보내지 않는다. 모든 이벤트의 $session_id와 timestamp의 최솟값·최댓값을 분석 계층에서 사용해 세션 시작, 종료, duration을 파생한다. 브라우저 종료 신호는 유실될 수 있으므로 $session_end를 확정 사실로 기록하지 않는다.
커스텀 이벤트 정본: abto.events.ts
제품 이벤트는 고객 애플리케이션 저장소의 abto.events.ts에 사전 등록한다. 이 파일을 코드 리뷰와 향후 CLI/CI schema push의 정본으로 사용한다.
// abto.events.ts
import { defineEvents } from '@abto-app/sdk';
export const events = defineEvents({
checkout_completed: {
description: '결제가 완료됨',
properties: {
order_id: { type: 'string', required: true },
amount: { type: 'number', required: true },
currency: {
type: 'string',
enum: ['KRW', 'USD'],
required: true,
},
},
},
});import { initAbto } from '@abto-app/sdk';
import { events } from './abto.events';
const abto = initAbto({
projectKey: 'public_project_key',
environment: 'development',
events,
});
abto.capture('checkout_completed', {
order_id: 'order_123',
amount: 49_000,
currency: 'KRW',
});defineEvents()에서 타입을 추론하므로 잘못된 이벤트 이름, required 누락, enum 위반을 개발 시점에 확인할 수 있다. 런타임 정책은 환경별로 다르다.
| 환경 | 미등록 이벤트 | 등록 schema drift |
|---|---|---|
| development | 전송하고 Discovered 경고 | 전송하고 drift 경고 |
| production | drop | required/type/enum 위반 drop |
알 수 없는 추가 속성은 막지 않는다. schema가 선언한 required/type/enum만 검사해 점진적 확장을 허용한다.
초기화와 autocapture
앱 루트에서 한 번 초기화하면 autocapture가 시작된다.
const abto = initAbto({
projectKey: 'public_project_key',
apiHost: 'https://api.abto.app',
environment: 'production',
events,
});기본 endpoint는 ${apiHost}/v1/browser/events다. 전송 envelope는 다음 모양이다.
{
"sent_at": "2026-07-15T04:10:03.000Z",
"batch": [
{
"uuid": "019b...",
"event": "$autocapture",
"timestamp": "2026-07-15T04:10:00.000Z",
"distinct_id": "user_123",
"properties": {
"$event_type": "click",
"$elements_chain": "button.cta:nth-child(1)",
"$session_id": "019b...",
"$window_id": "019b...",
"$pageview_id": "019b..."
}
}
]
}서버의 이벤트별 응답은 event UUID를 key로 사용한다.
{
"results": {
"019b5b74-11d0-7000-8000-000000000001": {
"result": "drop",
"code": "schema_type_mismatch"
}
}
}일반 fetch는 public project key를 Bearer header에 싣고, sendBeacon은 custom header를 지원하지 않으므로 ?api_key= query를 사용한다. 서버가 이 key에서 project_id와 account_id를 결정한다. $tenant_id를 포함한 client property는 분석 문맥이며 인증·project 귀속 값이 아니다.
수신 계약의 상한은 요청당 100 events다. SDK 기본값은 20이며 keepalive/beacon payload는 약 60 KiB 아래에서 전송한다. malformed request와 인증 실패는 요청 단위 4xx, 개별 validation/storage 실패는 2xx 응답의 UUID별 warning, drop, retry로 처리한다.
향후 PostgreSQL ingestion은 uuid/event/timestamp/distinct_id/properties/set/set_once를 각각 event_id/event_name/occurred_at/distinct_id/properties/person_set/person_set_once로 저장하고 서버 수신 시각을 received_at에 기록한다. (project_id, event_id)는 재전송 dedup key다.
annotation은 원시 $autocapture를 다른 이벤트로 바꾸지 않는다. 원시 상호작용을 보존하면서 분석 차원만 보강한다.
<button
data-abto-action="accept"
data-abto-surface="generator"
data-abto-node-key="resume.make"
data-abto-response-id="resp_123"
data-abto-request-id="req_123">
적용
</button>위 클릭은 $autocapture로 수집되며 $ai_action, $surface, $node_key, $response_id, $request_id가 함께 실린다. 업무 의미가 확정된 행동은 앱 코드에서 커스텀 이벤트 또는 AI 전용 메서드로 별도 기록한다.
개인정보 기본값
prompt, response, DOM text/value는 기본적으로 원문을 수집하지 않는다.
initAbto({
projectKey: 'public_project_key',
events,
capture: {
prompt: 'metadata_only',
response: 'metadata_only',
mask: 'all',
},
});위 값들이 생략됐을 때도 같은 안전한 기본값이 적용된다.
| annotation | 동작 |
|---|---|
| data-abto-no-capture | 자신과 하위 트리를 수집하지 않음 |
| data-abto-sensitive | 자신과 하위 text/value를 항상 전체 마스킹 |
| data-abto-include | 해당 요소의 text/value 수집을 명시적으로 허용 |
password, hidden input과 카드·비밀번호·SSN 계열 필드는 annotation과 무관하게 보호한다. full 원문 수집은 명시적 opt-in이며 고객의 동의·보존·삭제 정책과 함께 사용해야 한다.
브라우저에서 관측 가능한 AI 이벤트
브라우저가 확실히 아는 세 가지 사실만 전용 API로 제공한다.
const trace = abto.startLlmTrace({
nodeId: 'resume.make',
taskType: 'draft_generation',
surface: 'editor',
});
await trace.submitPrompt({
prompt: promptText,
language: 'ko',
});
const response = await fetch('/api/generate', {
method: 'POST',
headers: { 'content-type': 'application/json', ...trace.getHeaders() },
body: JSON.stringify({ prompt: promptText }),
});
trace.attachRequestId(response);
await trace.markResponseRendered({
responseId: 'resp_123',
timeToRenderMs: 1_380,
});
await trace.captureResponseInteraction('copied', {
responseId: 'resp_123',
source: 'copy_button',
});provider/model/token/cost/retry/fallback, 실제 첫 토큰 시점과 request 성공·실패는 Server SDK/Gateway가 소유한다. AI task 완료·이탈은 제품마다 의미가 다르므로 커스텀 이벤트 또는 분석 파생 지표로 둔다.
식별자와 세션
| 속성 | 수명과 역할 |
|---|---|
| $device_id | 프로젝트별 브라우저 설치, localStorage 유지 |
| $anonymous_id | 로그인 전 distinct identity |
| $user_id | identify()로 연결한 제품 사용자 |
| $session_id | 탭 사이에서 공유하는 논리 세션, 30분 idle 또는 24시간 max age에 회전 |
| $window_id | 탭/window별 ID, sessionStorage 유지 |
| $pageview_id | 페이지/SPA route 구간, pageview마다 회전 |
| $trace_id | 한 사용자 행동에서 서버 호출까지 연결 |
| $request_id | Gateway의 실제 provider 호출 PK |
abto.identify('user_123', 'tenant_123');
abto.reset(); // user/tenant 제거, device 유지
abto.forgetDevice(); // outbox와 device identity 제거전송과 재시도
- 이벤트는 localStorage outbox에 먼저 저장한다.
- 기본적으로 최대 20개씩
POST /v1/browser/events로 보낸다. - 일반 flush는
fetch, 페이지 이탈은 안전 크기에서sendBeacon을 우선 사용한다. - keepalive/beacon payload는 약 60 KiB 이내로 제한한다.
- 408, 429, 5xx와 이벤트별
retry만 지수 backoff로 재시도한다. - 영구 4xx와 이벤트별
drop은 outbox에서 제거한다. - 이벤트별
ok,warning,drop,retry응답을 UUID 기준으로 처리한다.
Public API (browser)
initAbto · defineEvents · identify · getIdentity · reset · forgetDevice · startLlmTrace · setNode · getTraceHeaders · client.capture · trace.submitPrompt · trace.markResponseRendered · trace.captureResponseInteraction · flush.
Node Server SDK
Node 서버에서는 별도 subpath를 사용한다. Browser SDK와 섞어 import하지 않는다.
import { createAbto } from '@abto-app/sdk/server';
const abto = createAbto({
abtoApiKey: process.env.ABTO_API_KEY,
providerKeys: {
openai: process.env.OPENAI_API_KEY,
},
gatewayBaseURL: 'https://gateway.abto.app/v1',
userId: process.env.ABTO_USER_ID,
});Server SDK의 Gateway/provider 계약은 서버 SDK 문서와 해당 PR에서 관리한다.
개발 검증
pnpm test
pnpm typecheck
pnpm build
node ../../examples/browser-smoke/collector.mjs실브라우저 검증 절차는 examples/browser-smoke/README.md를 따른다.
