Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
5.9 kB · 213 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214/* eslint-disable eslint-plugin-n/no-unsupported-features/node-builtins */
import type { SDKMessage } from '../entrypoints/agentSdkTypes.js'import type { SDKControlPermissionRequest, StdoutMessage,} from '../entrypoints/sdk/controlTypes.js'import type { RemotePermissionResponse } from '../remote/RemoteSessionManager.js'import { logForDebugging } from '../utils/debug.js'import { jsonParse, jsonStringify } from '../utils/slowOperations.js'import type { RemoteMessageContent } from '../utils/teleport/api.js'
export type DirectConnectConfig = { serverUrl: string sessionId: string wsUrl: string authToken?: string}
export type DirectConnectCallbacks = { onMessage: (message: SDKMessage) => void onPermissionRequest: ( request: SDKControlPermissionRequest, requestId: string, ) => void onConnected?: () => void onDisconnected?: () => void onError?: (error: Error) => void}
function isStdoutMessage(value: unknown): value is StdoutMessage { return ( typeof value === 'object' && value !== null && 'type' in value && typeof value.type === 'string' )}
export class DirectConnectSessionManager { private ws: WebSocket | null = null private config: DirectConnectConfig private callbacks: DirectConnectCallbacks
constructor(config: DirectConnectConfig, callbacks: DirectConnectCallbacks) { this.config = config this.callbacks = callbacks }
connect(): void { const headers: Record<string, string> = {} if (this.config.authToken) { headers['authorization'] = `Bearer ${this.config.authToken}` } // Bun's WebSocket supports headers option but the DOM typings don't this.ws = new WebSocket(this.config.wsUrl, { headers, } as unknown as string[])
this.ws.addEventListener('open', () => { this.callbacks.onConnected?.() })
this.ws.addEventListener('message', event => { const data = typeof event.data === 'string' ? event.data : '' const lines = data.split('\n').filter((l: string) => l.trim())
for (const line of lines) { let raw: unknown try { raw = jsonParse(line) } catch { continue }
if (!isStdoutMessage(raw)) { continue } const parsed = raw
// Handle control requests (permission requests) if (parsed.type === 'control_request') { if (parsed.request.subtype === 'can_use_tool') { this.callbacks.onPermissionRequest( parsed.request, parsed.request_id, ) } else { // Send an error response for unrecognized subtypes so the // server doesn't hang waiting for a reply that never comes. logForDebugging( `[DirectConnect] Unsupported control request subtype: ${parsed.request.subtype}`, ) this.sendErrorResponse( parsed.request_id, `Unsupported control request subtype: ${parsed.request.subtype}`, ) } continue }
// Forward SDK messages (assistant, result, system, etc.) if ( parsed.type !== 'control_response' && parsed.type !== 'keep_alive' && parsed.type !== 'control_cancel_request' && parsed.type !== 'streamlined_text' && parsed.type !== 'streamlined_tool_use_summary' && !(parsed.type === 'system' && parsed.subtype === 'post_turn_summary') ) { this.callbacks.onMessage(parsed) } } })
this.ws.addEventListener('close', () => { this.callbacks.onDisconnected?.() })
this.ws.addEventListener('error', () => { this.callbacks.onError?.(new Error('WebSocket connection error')) }) }
sendMessage(content: RemoteMessageContent): boolean { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { return false }
// Must match SDKUserMessage format expected by `--input-format stream-json` const message = jsonStringify({ type: 'user', message: { role: 'user', content: content, }, parent_tool_use_id: null, session_id: '', }) this.ws.send(message) return true }
respondToPermissionRequest( requestId: string, result: RemotePermissionResponse, ): void { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { return }
// Must match SDKControlResponse format expected by StructuredIO const response = jsonStringify({ type: 'control_response', response: { subtype: 'success', request_id: requestId, response: { behavior: result.behavior, ...(result.behavior === 'allow' ? { updatedInput: result.updatedInput } : { message: result.message }), }, }, }) this.ws.send(response) }
/** * Send an interrupt signal to cancel the current request */ sendInterrupt(): void { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { return }
// Must match SDKControlRequest format expected by StructuredIO const request = jsonStringify({ type: 'control_request', request_id: crypto.randomUUID(), request: { subtype: 'interrupt', }, }) this.ws.send(request) }
private sendErrorResponse(requestId: string, error: string): void { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { return } const response = jsonStringify({ type: 'control_response', response: { subtype: 'error', request_id: requestId, error, }, }) this.ws.send(response) }
disconnect(): void { if (this.ws) { this.ws.close() this.ws = null } }
isConnected(): boolean { return this.ws?.readyState === WebSocket.OPEN }}