Runtime
Runtime
createAdapterRuntime() is the main entry point. It wires all SDK components (API client, WebSocket, store, orchestrators) and manages their lifecycle.
Factory Function
import { createAdapterRuntime } from '@oro.ad/bridge-adapter-sdk'
const runtime = createAdapterRuntime(config)
AdapterConfig
interface AdapterConfig {
bridge: {
url: string // Bridge server URL
socketPath?: string // Custom Socket.IO path
clientId?: string // Client identifier
clientType?: 'adapter' | 'bot' | 'browser' | 'devtools' | 'media'
authToken?: string // Bearer token for an @oro.ad/auth-protected bridge
defaultHeaders?: Record<string, string> // Extra headers on every REST request
}
platform: PlatformAdapter // Your platform implementation
formatter: Formatter // Your formatter implementation
store?: AdapterStore // Custom store (default: SQLite under dataDir)
dataDir?: string // Directory for bundled SQLite DB (required unless `store` is provided)
replayLimitPerSession?: number // Max messages to replay per session (default: 200)
skipReplay?: boolean // Skip historical replay on start
reconciler?: Partial<ReconcilerConfig>
bridgeToPlatform?: Partial<BridgeToPlatformConfig>
platformToBridge?: Partial<PlatformToBridgeConfig>
dedup?: {
pendingOutboundTtlMs?: number // TTL for pending outbound dedup tokens (default: 10_000)
}
logger?: Logger // default: noopLogger
}
store or dataDirmust be provided. Omitting both throws an
AdapterConfigError from createAdapterRuntime.Authenticating to a protected bridge
When the bridge enforces @oro.ad/auth, set bridge.authToken. The runtime
forwards it as Authorization: Bearer <token> on every REST request and in the
Socket.IO handshake auth payload. Use bridge.defaultHeaders for additional
headers (or a custom Authorization scheme — it is merged on top of the token
header, so an explicit Authorization key wins). Both fields are optional and
leave the unauthenticated path unchanged.
const runtime = createAdapterRuntime({
bridge: {
url: 'https://bridge.example.com',
authToken: process.env.BRIDGE_TOKEN,
},
// ...platform, formatter, dataDir
})
Runtime Interface
interface Runtime {
readonly bridgeApi: BridgeApi
readonly bridgeClient: BridgeClient
readonly store: AdapterStore
readonly reconciler: Reconciler
readonly replayer: Replayer
readonly bridgeToPlatform: BridgeToPlatform
readonly platformToBridge: PlatformToBridge
start(): Promise<void>
stop(): Promise<void>
}
Startup Sequence
When you call runtime.start(), the following happens in order:
- Store initialized — SQLite database opened, tables created
- Bridge API — fetches initial session list
- WebSocket connected — connects to bridge, joins all session rooms
- Reconciler sync — creates/renames containers for all sessions
- Title repair — fixes containers with placeholder titles
- Replay — replays missed messages (unless
skipReplay: true) - Event handlers attached — bridge events routed to platform
- Platform started — your
PlatformAdapter.start()called - Session list listener — ongoing reconciliation on
sessions:list
Shutdown
await runtime.stop()
Calls PlatformAdapter.stop(), disconnects the WebSocket, and closes the store.
Configuration Overrides
All default configs are exported as DEFAULT_*_CONFIG constants — import and
inspect at runtime if you need to merge with your overrides programmatically.
ReconcilerConfig
interface ReconcilerConfig {
titleMaxLength: number // default: 40
titleFallback?: (session) => string // custom fallback when session.title is empty
guardAgainstEmptyIncomingTitle: boolean // default: true
}
BridgeToPlatformConfig
interface BridgeToPlatformConfig {
toolCallChronologicalOrdering: boolean // default: true
showThinkingBlocks: boolean // default: true
showSessionStatus: boolean // default: false
maxStreamEditsPerSecond: number // default: 1
}
PlatformToBridgeConfig
interface PlatformToBridgeConfig {
onContainerClosed: 'delete-session' | 'leave-room' | 'noop' // default: 'delete-session'
propagateRenames: boolean // default: true
uploadAttachments: boolean // default: true
adapterSource: string // default: 'adapter'
}
ReplayerConfig
interface ReplayerConfig {
limitPerSession: number // default: 200 (override via AdapterConfig.replayLimitPerSession)
skipIfNoContainer: boolean // default: true
}
Accessing Components
After creation, you can access individual components for advanced use cases:
const runtime = createAdapterRuntime(config)
// Direct API access
const sessions = await runtime.bridgeApi.listSessions()
// Direct store access
const mapping = runtime.store.containers.findByContainerId('123')
// Direct WebSocket access
runtime.bridgeClient.on('connect', () => { })
Graceful shutdown
async function shutdown() {
try { await runtime.stop() }
finally { process.exit(0) }
}
process.on('SIGTERM', shutdown)
process.on('SIGINT', shutdown)
runtime.stop() is idempotent and:
- Calls
PlatformAdapter.stop()(log-only on error — does not throw). - Closes the Socket.IO connection.
- Closes the SQLite store.
Pending outbound messages already POSTed to bridge are not cancelled —
the bridge continues processing them. Incoming events after stop() are
discarded because the socket is gone.
Utilities re-exported for adapter authors
The adapter SDK re-exports helpers that come in handy when building a
Formatter or custom orchestrator:
import {
chunkText, // split text to N chunks ≤ maxLen
truncateText, // ellipsis-truncate to ≤ maxLen
computeContainerTitle, // standard title-fallback logic
PendingOutbound, // dedup primitive — runtime creates one for you
toolSummary, // one-liner summary for tool cards
streamUserMessageToBlocks,
streamToolCallToBlocks,
streamResultToBlocks,
sessionErrorToBlocks,
bridgeMessageToBlocks,
} from '@oro.ad/bridge-adapter-sdk'