(
transport: SSETransport,
sessionUrl: URL,
opts?: {
onEpochMismatch?: () => never
heartbeatIntervalMs?: number
heartbeatJitterFraction?: number
/**
* Per-instance auth header source. Omit to read the process-wide
* CLAUDE_CODE_SESSION_ACCESS_TOKEN (single-session callers — REPL,
* daemon). Required for concurrent multi-session callers.
*/
getAuthHeaders?: () => Record<string, string>
},
)
| 308 | private readonly getAuthHeaders: () => Record<string, string> |
| 309 | |
| 310 | constructor( |
| 311 | transport: SSETransport, |
| 312 | sessionUrl: URL, |
| 313 | opts?: { |
| 314 | onEpochMismatch?: () => never |
| 315 | heartbeatIntervalMs?: number |
| 316 | heartbeatJitterFraction?: number |
| 317 | /** |
| 318 | * Per-instance auth header source. Omit to read the process-wide |
| 319 | * CLAUDE_CODE_SESSION_ACCESS_TOKEN (single-session callers — REPL, |
| 320 | * daemon). Required for concurrent multi-session callers. |
| 321 | */ |
| 322 | getAuthHeaders?: () => Record<string, string> |
| 323 | }, |
| 324 | ) { |
| 325 | this.onEpochMismatch = |
| 326 | opts?.onEpochMismatch ?? |
| 327 | (() => { |
| 328 | // eslint-disable-next-line custom-rules/no-process-exit |
| 329 | process.exit(1) |
| 330 | }) |
| 331 | this.heartbeatIntervalMs = |
| 332 | opts?.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS |
| 333 | this.heartbeatJitterFraction = opts?.heartbeatJitterFraction ?? 0 |
| 334 | this.getAuthHeaders = opts?.getAuthHeaders ?? getSessionIngressAuthHeaders |
| 335 | // Session URL: https://host/v1/code/sessions/{id} |
| 336 | if (sessionUrl.protocol !== 'http:' && sessionUrl.protocol !== 'https:') { |
| 337 | throw new Error( |
| 338 | `CCRClient: Expected http(s) URL, got ${sessionUrl.protocol}`, |
| 339 | ) |
| 340 | } |
| 341 | const pathname = sessionUrl.pathname.replace(/\/$/, '') |
| 342 | this.sessionBaseUrl = `${sessionUrl.protocol}//${sessionUrl.host}${pathname}` |
| 343 | // Extract session ID from the URL path (last segment) |
| 344 | this.sessionId = pathname.split('/').pop() || '' |
| 345 | |
| 346 | this.workerState = new WorkerStateUploader({ |
| 347 | send: body => |
| 348 | this.request( |
| 349 | 'put', |
| 350 | '/worker', |
| 351 | { worker_epoch: this.workerEpoch, ...body }, |
| 352 | 'PUT worker', |
| 353 | ).then(r => r.ok), |
| 354 | baseDelayMs: 500, |
| 355 | maxDelayMs: 30_000, |
| 356 | jitterMs: 500, |
| 357 | }) |
| 358 | |
| 359 | this.eventUploader = new SerialBatchEventUploader<ClientEvent>({ |
| 360 | maxBatchSize: 100, |
| 361 | maxBatchBytes: 10 * 1024 * 1024, |
| 362 | // flushStreamEventBuffer() enqueues a full 100ms window of accumulated |
| 363 | // stream_events in one call. A burst of mixed delta types that don't |
| 364 | // fold into a single snapshot could exceed the old cap (50) and deadlock |
| 365 | // on the SerialBatchEventUploader backpressure check. Match |
| 366 | // HybridTransport's bound — high enough to be memory-only. |
| 367 | maxQueueSize: 100_000, |
nothing calls this directly
no test coverage detected