BridgeApi
BridgeApi
Typed HTTP client for the Bridge REST API. All methods return promises and throw BridgeApiError on non-2xx responses.
Constructor
import { BridgeApi } from '@oro.ad/bridge-sdk'
const api = new BridgeApi({
baseUrl: 'http://localhost:3000',
logger: myLogger, // optional Logger
timeoutMs: 30_000, // optional default client-side timeout
defaultHeaders: { // optional
'X-Client-Id': 'my-app'
}
})
| Option | Type | Default | Description |
|---|---|---|---|
baseUrl | string | required | Bridge server URL (trailing slash stripped) |
fetch | Fetch | globalThis.fetch | Custom fetch implementation |
logger | Logger | noopLogger | Optional structured logger |
defaultHeaders | Record<string, string> | {} | Headers sent with every request |
timeoutMs | number | — | Default per-request client-side timeout; raises BridgeTimeoutError via AbortController |
BridgeApiError (non-2xx), BridgeConnectError (transport),
BridgeTimeoutError (timeout exceeded), or BridgeValidationError (bad local input, e.g. unreadable attachment file). See errors.Session Management
listSessions
const sessions: SessionSummary[] = await api.listSessions()
Returns all active sessions with their status, title, model, message count, and loop state.
getSession
const session = await api.getSession('claude_abc123')
createSession
const { conversationId } = await api.createSession({
title: 'My Session'
})
adoptSession
Attach an existing Claude Code session to the bridge.
const result = await api.adoptSession({
claudeSessionId: 'sess_xyz',
title: 'Adopted Session'
})
updateSession / updateSessionTitle
Updates mutable session fields — title, model, contextSummary, loop (see UpdateSessionBody). Fields absent from the body are left untouched; null for model / contextSummary resets to the bridge default / unpins the summary.
const { loop } = await api.updateSession(conversationId, { title: 'New Title', model: null })
// or shorthand:
await api.updateSessionTitle(conversationId, 'New Title')
deleteSession
await api.deleteSession(conversationId)
Session Control
compactSession
Triggers context compaction (summarization) for a session.
await api.compactSession(conversationId)
clearSession
Clears all messages from a session.
await api.clearSession(conversationId)
setLoopMode
Enable or disable autonomous loop mode.
const { loop } = await api.setLoopMode(conversationId, {
enabled: true,
config: {
trigger: 'idle_timeout',
idleTimeoutSec: 300,
resumePrompt: 'Continue working on the task.',
maxCostUsdPerHour: 5.0
}
})
getAutoMode / setAutoMode
Read or toggle auto-mode (the bridge compacts the conversation itself past a token threshold). The bridge broadcasts session:auto_changed to the session room.
const { autoMode } = await api.getAutoMode(conversationId)
await api.setAutoMode(conversationId, true) // { ok, conversationId, autoMode }
reviveSession
Restores a conversation's transcript in ~/.claude from the bridge's history mirror so --resume can continue it. Idempotent; tool results are not restored.
const { revived, lines, path } = await api.reviveSession(conversationId)
setSessionMcpServers
Declares HTTP MCP servers for one conversation — added to its --mcp-config on every run. Replaces the previous declaration; {} revokes it. At most eight servers; header values are never echoed back.
const { servers } = await api.setSessionMcpServers(conversationId, {
docs: { type: 'http', url: 'https://mcp.example.com', headers: { authorization: `Bearer ${token}` } }
})
// servers: ['docs']
searchSessions
Full-text search over message content (the bridge's history mirror). Titles and projects are not searched. Queries shorter than two characters return []. At most one hit per conversation, newest first.
const hits: SessionSearchHit[] = await api.searchSessions('deploy', {
limit: 20, // 1–200, default 50
signal: controller.signal // cancel a stale query
})
// [{ bridgeSessionId, snippet, role, timestamp }]
Messages
sendMessage
const { messageId } = await api.sendMessage(conversationId, {
content: 'Hello Claude!',
attachments: [],
senderId: 'user_123',
senderNickname: '@john'
})
getMessages
const { messages, total } = await api.getMessages(conversationId, {
limit: 50,
before: 'msg_xyz' // cursor pagination
})
getMessage
const message: BridgeMessage = await api.getMessage(conversationId, messageId)
getMessageResult
Waits for the assistant to complete processing a message.
const result: MessageResult = await api.getMessageResult(
conversationId,
messageId,
{ timeoutMs: 120_000 }
)
console.log(result.content) // assistant response
console.log(result.costUsd) // cost in USD
console.log(result.usage) // token usage breakdown
Attachments
uploadAttachment
Upload from file path or raw bytes.
// From file
const attachment = await api.uploadAttachment(conversationId, {
filepath: '/path/to/image.png',
filename: 'image.png',
mimeType: 'image/png'
})
// From buffer
const attachment = await api.uploadAttachment(conversationId, {
data: buffer,
filename: 'doc.pdf',
mimeType: 'application/pdf'
})
downloadAttachment
const bytes: Uint8Array = await api.downloadAttachment(attachmentId)
deleteAttachment
await api.deleteAttachment(attachmentId)
getAttachmentPreview
Server-highlighted preview of a text attachment. Non-text attachments answer 415.
const preview: AttachmentPreview = await api.getAttachmentPreview(attachmentId)
// { id, filename, mimeType, size, lang, highlighted, reason?, html } — html is an escaped <pre>…</pre>
Voice
transcribePreview
Transcribes a chunk of audio without creating an attachment — live text while the user is still speaking.
const { text } = await api.transcribePreview(conversationId, {
data: chunk,
filename: 'chunk.webm',
mimeType: 'audio/webm'
})
synthesize
Text-to-speech. Returns null when the bridge has no synthesis (204) or refuses — fall back to browser speech. Network errors and timeouts still throw.
const audio: Blob | null = await api.synthesize({ text: 'Hello', voice: 'ru_RU-dmitri-medium', lengthScale: 1.1 })
readVoiceProfile / writeVoiceProfile
Per-user voice settings stored in the user's profile. readVoiceProfile returns null when there is none or the profile is unavailable; writeVoiceProfile returns the bridge's ok. The bridge replaces the whole object — always send the full profile.
const profile = await api.readVoiceProfile() // VoiceProfile | null
const saved: boolean = await api.writeVoiceProfile({ ...profile, voiceName: 'ru_RU-dmitri-medium' })
routeVoice
Picks which live session in cwd should receive a spoken phrase. Only the addressee — send the words yourself with sendMessage. null when there are no live sessions there (204).
const route: VoiceRoute | null = await api.routeVoice({ cwd: '/work/project', text: 'run the tests' })
// { sessionId, fromCache }
Identity
getIdentity
Identity of the bridge itself (the agent), not of the caller. An unconfigured identity comes back as a synthetic local (isLocal: true) without the agent section.
const me: BridgeIdentity = await api.getIdentity()
// { sub, username, email, groups, isAgent, isLocal, description?, capabilities?, appearance?, agent?, ... }
getAppearance / setAppearance
Read or write the agent's appearance (oro id appearance). null clears it. On write the bridge re-announces the card to the hub and broadcasts identity:changed. 400 — malformed appearance, 503 — no oro client or identity on the machine.
const { appearance } = await api.getAppearance() // AgentAppearance | null
await api.setAppearance({ v: 1, head: 'visor', paint: 'teal', metal: 'steel', lens: 'navy', glow: 'cyan' }) // fields: @oro.ad/agent-look
await api.setAppearance(null)
setCard
Writes the agent card. Only the fields passed are changed; description: null clears it. At least one field is required.
const card: AgentCard = await api.setCard({ description: 'Reviews MRs', capabilities: ['review'] })
// { ok, description, capabilities }
Skills
listSkills
Agent skills. Without cwd — user scope (~/.claude/skills); with cwd — the project scope (<cwd>/.claude/skills). These are separate lists, not a filter.
const { scope, skills, syncedAt } = await api.listSkills()
const project = await api.listSkills({ cwd: '/work/project' })
deleteSkill
Removes a single skill from the user scope. 404 if it does not exist.
await api.deleteSkill('my-skill')
installSkillPackage / uninstallSkillPackage
Installs (apm install … --target claude) or removes an apm package. Target is exactly one of global: true, workDir, or conversationId (see ApmTarget). Can take minutes — give it a generous timeout. An apm failure is a 502 with the apm output in the error body.
const res: ApmResult = await api.installSkillPackage(
{ packageRef: 'org/skills-pack', global: true },
{ timeoutMs: 300_000 }
)
await api.uninstallSkillPackage({ packageRef: 'org/skills-pack', global: true })
updateSkillPackages
Updates the target's apm packages (apm update --yes).
await api.updateSkillPackages({ conversationId }, { timeoutMs: 300_000 })
syncSkills
Writes skills as files into ~/.claude/skills — additive, other skills stay. Returns the user scope list after the write.
const { skills, syncedAt } = await api.syncSkills({
skills: [{ name: 'greet', files: { 'SKILL.md': '---\nname: greet\n---\n…' } }]
})
Bridge State
listClients
Sockets connected to the bridge right now. The same list is pushed as the clients:list event.
const { clients } = await api.listClients()
// [{ socketId, app, clientType, user, connectedAt, conversations }]
getHubLink
Bridge ↔ hub link state from the open /reactive/link route (outside the /api/** auth gate), so it works for anonymous viewers.
const link: HubLinkStatus = await api.getHubLink()
// { state, hubUrl, lastConnectedAt, reconnects }
getReactiveStatus
The bridge's reactive loop: hub link, agent identity, claude preflight, counters, and recent inbox dispatches (full entries for the agent owner only).
const status: ReactiveStatus = await api.getReactiveStatus()
// { link, identity, spawnHealthy, claudeCommand, counters, recentDispatches }
deliverInbox
Delivers a hub channel message into the bridge — the same routing as live delivery (dedup by seq, dispatcher picks the session). Only the bridge owner or a trusted service may call it; answers 202.
const res: InboxDeliveryResult = await api.deliverInbox({
channelId: 'ch_1',
fromAgentId: 'agent_2',
messageId: 'm_42',
seq: 42,
content: 'Please review !12'
})
// { status: 'delivered', routed, deduped, dispatcherUsed }
Credentials
getCredentialStatus
Health of the bridge's Claude credentials (process state, not per session). Read it explicitly on startup — credentials:reauth_required fires only once, on the transition.
const s: CredentialStatus = await api.getCredentialStatus()
// { loaded, expiresAt, expiresInMs, reauthRequired, lastCheckedAt, lastError }
reauthCredentials
Reinstalls the organization's credential bundle (oro auth-restore). Failure is ok: false in a 200 response, not an HTTP error; reauthWizardNeeded means the owner has to sign in again.
const r: CredentialReauthResult = await api.reauthCredentials({ timeoutMs: 60_000 })
if (!r.ok) console.warn(r.error)
Health & Info
health
Liveness probe and the only route outside the auth gate. version / workDir are absent on bridges older than 0.4.0-rc.5.
await api.health() // { ok: true, version?, workDir? }
info
const info = await api.info()
// { version, uptime, claudeVersion, workDir, activeSessions }
Timeouts & cancellation
Set a default timeout on construction (applies to every request) or per-call
(overrides the default). Exceeded timeouts throw BridgeTimeoutError:
const api = new BridgeApi({ baseUrl, timeoutMs: 15_000 })
// Per-call override — useful for attachment uploads or long polls:
await api.uploadAttachment(id, file, { timeoutMs: 60_000 })
await api.downloadAttachment(id, { timeoutMs: 60_000 })
getMessageResult has its own timeoutMs that's passed to the server as the
long-poll deadline. Set BridgeApiOptions.timeoutMs at least as high to
avoid the client timing out before the server responds.
Error handling
import {
BridgeApiError,
BridgeConnectError,
BridgeTimeoutError,
BridgeValidationError,
} from '@oro.ad/bridge-sdk'
try {
await api.sendMessage(id, body)
}
catch (err) {
if (err instanceof BridgeTimeoutError) { /* retry / surface */ }
else if (err instanceof BridgeApiError && err.status === 404) { /* gone */ }
else if (err instanceof BridgeConnectError) { /* network down */ }
else if (err instanceof BridgeValidationError) { /* bad input */ }
else throw err
}