๐ File detail
remote/sdkMessageAdapter.ts
๐งฉ .ts๐ 303 lines๐พ 9,060 bytes๐ text
โ Back to All Files๐ฏ Use case
This file lives under โremote/โ, which covers remote sessions, WebSockets, and SDK message bridging. On the API surface it exposes ConvertedMessage, convertSDKMessage, isSessionEndMessage, isSuccessResult, and getResultText โ mainly functions, hooks, or classes. It composes internal code from entrypoints, types, and utils (relative imports).
Generated from folder role, exports, dependency roots, and inline comments โ not hand-reviewed for every path.
๐ง Inline summary
import type { SDKAssistantMessage, SDKCompactBoundaryMessage, SDKMessage, SDKPartialAssistantMessage,
๐ค Exports (heuristic)
ConvertedMessageconvertSDKMessageisSessionEndMessageisSuccessResultgetResultText
๐ฅ๏ธ Source preview
import type {
SDKAssistantMessage,
SDKCompactBoundaryMessage,
SDKMessage,
SDKPartialAssistantMessage,
SDKResultMessage,
SDKStatusMessage,
SDKSystemMessage,
SDKToolProgressMessage,
} from '../entrypoints/agentSdkTypes.js'
import type {
AssistantMessage,
Message,
StreamEvent,
SystemMessage,
} from '../types/message.js'
import { logForDebugging } from '../utils/debug.js'
import { fromSDKCompactMetadata } from '../utils/messages/mappers.js'
import { createUserMessage } from '../utils/messages.js'
/**
* Converts SDKMessage from CCR to REPL Message types.
*
* The CCR backend sends SDK-format messages via WebSocket. The REPL expects
* internal Message types for rendering. This adapter bridges the two.
*/
/**
* Convert an SDKAssistantMessage to an AssistantMessage
*/
function convertAssistantMessage(msg: SDKAssistantMessage): AssistantMessage {
return {
type: 'assistant',
message: msg.message,
uuid: msg.uuid,
requestId: undefined,
timestamp: new Date().toISOString(),
error: msg.error,
}
}
/**
* Convert an SDKPartialAssistantMessage (streaming) to a StreamEvent
*/
function convertStreamEvent(msg: SDKPartialAssistantMessage): StreamEvent {
return {
type: 'stream_event',
event: msg.event,
}
}
/**
* Convert an SDKResultMessage to a SystemMessage
*/
function convertResultMessage(msg: SDKResultMessage): SystemMessage {
const isError = msg.subtype !== 'success'
const content = isError
? msg.errors?.join(', ') || 'Unknown error'
: 'Session completed successfully'
return {
type: 'system',
subtype: 'informational',
content,
level: isError ? 'warning' : 'info',
uuid: msg.uuid,
timestamp: new Date().toISOString(),
}
}
/**
* Convert an SDKSystemMessage (init) to a SystemMessage
*/
function convertInitMessage(msg: SDKSystemMessage): SystemMessage {
return {
type: 'system',
subtype: 'informational',
content: `Remote session initialized (model: ${msg.model})`,
level: 'info',
uuid: msg.uuid,
timestamp: new Date().toISOString(),
}
}
/**
* Convert an SDKStatusMessage to a SystemMessage
*/
function convertStatusMessage(msg: SDKStatusMessage): SystemMessage | null {
if (!msg.status) {
return null
}
return {
type: 'system',
subtype: 'informational',
content:
msg.status === 'compacting'
? 'Compacting conversationโฆ'
: `Status: ${msg.status}`,
level: 'info',
uuid: msg.uuid,
timestamp: new Date().toISOString(),
}
}
/**
* Convert an SDKToolProgressMessage to a SystemMessage.
* We use a system message instead of ProgressMessage since the Progress type
* is a complex union that requires tool-specific data we don't have from CCR.
*/
function convertToolProgressMessage(
msg: SDKToolProgressMessage,
): SystemMessage {
return {
type: 'system',
subtype: 'informational',
content: `Tool ${msg.tool_name} running for ${msg.elapsed_time_seconds}sโฆ`,
level: 'info',
uuid: msg.uuid,
timestamp: new Date().toISOString(),
toolUseID: msg.tool_use_id,
}
}
/**
* Convert an SDKCompactBoundaryMessage to a SystemMessage
*/
function convertCompactBoundaryMessage(
msg: SDKCompactBoundaryMessage,
): SystemMessage {
return {
type: 'system',
subtype: 'compact_boundary',
content: 'Conversation compacted',
level: 'info',
uuid: msg.uuid,
timestamp: new Date().toISOString(),
compactMetadata: fromSDKCompactMetadata(msg.compact_metadata),
}
}
/**
* Result of converting an SDKMessage
*/
export type ConvertedMessage =
| { type: 'message'; message: Message }
| { type: 'stream_event'; event: StreamEvent }
| { type: 'ignored' }
type ConvertOptions = {
/** Convert user messages containing tool_result content blocks into UserMessages.
* Used by direct connect mode where tool results come from the remote server
* and need to be rendered locally. CCR mode ignores user messages since they
* are handled differently. */
convertToolResults?: boolean
/**
* Convert user text messages into UserMessages for display. Used when
* converting historical events where user-typed messages need to be shown.
* In live WS mode these are already added locally by the REPL so they're
* ignored by default.
*/
convertUserTextMessages?: boolean
}
/**
* Convert an SDKMessage to REPL message format
*/
export function convertSDKMessage(
msg: SDKMessage,
opts?: ConvertOptions,
): ConvertedMessage {
switch (msg.type) {
case 'assistant':
return { type: 'message', message: convertAssistantMessage(msg) }
case 'user': {
const content = msg.message?.content
// Tool result messages from the remote server need to be converted so
// they render and collapse like local tool results. Detect via content
// shape (tool_result blocks) โ parent_tool_use_id is NOT reliable: the
// agent-side normalizeMessage() hardcodes it to null for top-level
// tool results, so it can't distinguish tool results from prompt echoes.
const isToolResult =
Array.isArray(content) && content.some(b => b.type === 'tool_result')
if (opts?.convertToolResults && isToolResult) {
return {
type: 'message',
message: createUserMessage({
content,
toolUseResult: msg.tool_use_result,
uuid: msg.uuid,
timestamp: msg.timestamp,
}),
}
}
// When converting historical events, user-typed messages need to be
// rendered (they weren't added locally by the REPL). Skip tool_results
// here โ already handled above.
if (opts?.convertUserTextMessages && !isToolResult) {
if (typeof content === 'string' || Array.isArray(content)) {
return {
type: 'message',
message: createUserMessage({
content,
toolUseResult: msg.tool_use_result,
uuid: msg.uuid,
timestamp: msg.timestamp,
}),
}
}
}
// User-typed messages (string content) are already added locally by REPL.
// In CCR mode, all user messages are ignored (tool results handled differently).
return { type: 'ignored' }
}
case 'stream_event':
return { type: 'stream_event', event: convertStreamEvent(msg) }
case 'result':
// Only show result messages for errors. Success results are noise
// in multi-turn sessions (isLoading=false is sufficient signal).
if (msg.subtype !== 'success') {
return { type: 'message', message: convertResultMessage(msg) }
}
return { type: 'ignored' }
case 'system':
if (msg.subtype === 'init') {
return { type: 'message', message: convertInitMessage(msg) }
}
if (msg.subtype === 'status') {
const statusMsg = convertStatusMessage(msg)
return statusMsg
? { type: 'message', message: statusMsg }
: { type: 'ignored' }
}
if (msg.subtype === 'compact_boundary') {
return {
type: 'message',
message: convertCompactBoundaryMessage(msg),
}
}
// hook_response and other subtypes
logForDebugging(
`[sdkMessageAdapter] Ignoring system message subtype: ${msg.subtype}`,
)
return { type: 'ignored' }
case 'tool_progress':
return { type: 'message', message: convertToolProgressMessage(msg) }
case 'auth_status':
// Auth status is handled separately, not converted to a display message
logForDebugging('[sdkMessageAdapter] Ignoring auth_status message')
return { type: 'ignored' }
case 'tool_use_summary':
// Tool use summaries are SDK-only events, not displayed in REPL
logForDebugging('[sdkMessageAdapter] Ignoring tool_use_summary message')
return { type: 'ignored' }
case 'rate_limit_event':
// Rate limit events are SDK-only events, not displayed in REPL
logForDebugging('[sdkMessageAdapter] Ignoring rate_limit_event message')
return { type: 'ignored' }
default: {
// Gracefully ignore unknown message types. The backend may send new
// types before the client is updated; logging helps with debugging
// without crashing or losing the session.
logForDebugging(
`[sdkMessageAdapter] Unknown message type: ${(msg as { type: string }).type}`,
)
return { type: 'ignored' }
}
}
}
/**
* Check if an SDKMessage indicates the session has ended
*/
export function isSessionEndMessage(msg: SDKMessage): boolean {
return msg.type === 'result'
}
/**
* Check if an SDKResultMessage indicates success
*/
export function isSuccessResult(msg: SDKResultMessage): boolean {
return msg.subtype === 'success'
}
/**
* Extract the result text from a successful SDKResultMessage
*/
export function getResultText(msg: SDKResultMessage): string | null {
if (msg.subtype === 'success') {
return msg.result
}
return null
}