Guides
Quick Start
Build your first platform adapter in 15 minutes.
Quick Start
Build a minimal platform adapter that connects a console-based "platform" to ORO Claude Bridge.
Prerequisites
- Node.js 18+
- ORO Claude Bridge running at
http://localhost:3000
1. Create Project
mkdir my-adapter && cd my-adapter
npm init -y
npm install @oro.ad/bridge-adapter-sdk
2. Implement PlatformAdapter
Create platform.ts:
import type {
PlatformAdapter,
PlatformHandlers,
ContainerId,
PlatformMessageId,
FormattedContent,
PlatformFile,
DownloadedFile,
SessionSummary,
MessageAttachment,
} from '@oro.ad/bridge-adapter-sdk'
export class ConsolePlatform implements PlatformAdapter {
readonly supportsNativeStreaming = false
private handlers?: PlatformHandlers
private nextId = 1
async start(handlers: PlatformHandlers): Promise<void> {
this.handlers = handlers
// Read from stdin
process.stdin.setEncoding('utf-8')
process.stdin.on('data', async (data: string) => {
const text = data.trim()
if (!text) return
// Format: <containerId> <message>
const [containerId, ...rest] = text.split(' ')
const content = rest.join(' ')
await handlers.onMessage({
updateId: String(this.nextId++),
containerId: containerId!,
platformMessageId: String(this.nextId++),
text: content,
files: [],
sender: { id: 'console-user', displayName: 'Console' },
})
})
}
async stop(): Promise<void> {
process.stdin.removeAllListeners()
}
async createContainer(session: SessionSummary): Promise<ContainerId> {
const id = String(this.nextId++)
console.log(`[container:${id}] Created: ${session.title || session.conversationId}`)
return id
}
async renameContainer(id: ContainerId, title: string): Promise<void> {
console.log(`[container:${id}] Renamed: ${title}`)
}
async closeContainer(id: ContainerId): Promise<void> {
console.log(`[container:${id}] Closed`)
}
async deleteContainer(id: ContainerId): Promise<void> {
console.log(`[container:${id}] Deleted`)
}
async postMessage(id: ContainerId, content: FormattedContent): Promise<PlatformMessageId[]> {
const msgId = String(this.nextId++)
console.log(`[container:${id}] ${content.text}`)
return [msgId]
}
async editMessage(id: ContainerId, msgId: PlatformMessageId, content: FormattedContent): Promise<void> {
// Overwrite last line for streaming effect
process.stdout.write(`\r[container:${id}] ${content.text?.slice(0, 80)}...`)
}
async downloadPlatformFile(file: PlatformFile): Promise<DownloadedFile> {
throw new Error('Console platform does not support files')
}
async postAttachment(id: ContainerId, attachment: MessageAttachment): Promise<PlatformMessageId> {
const msgId = String(this.nextId++)
console.log(`[container:${id}] π ${attachment.filename}`)
return msgId
}
}
3. Implement Formatter
Create formatter.ts:
import type { Formatter, SemanticBlock, FormattedContent } from '@oro.ad/bridge-adapter-sdk'
export class ConsoleFormatter implements Formatter {
render(blocks: SemanticBlock[]): FormattedContent[] {
const lines: string[] = []
for (const block of blocks) {
switch (block.kind) {
case 'text':
lines.push(block.text)
break
case 'code':
lines.push(`\`\`\`${block.lang || ''}\n${block.text}\n\`\`\``)
break
case 'tool_card':
lines.push(`π§ ${block.name}: ${block.summary}`)
if (block.result) {
lines.push(` β ${block.result.content.slice(0, 200)}`)
}
break
case 'result_card':
if (block.costUsd != null) {
lines.push(`π° Cost: $${block.costUsd.toFixed(4)}`)
}
break
case 'error_card':
lines.push(`β Error: ${block.error}`)
break
default:
break
}
}
return [{ kind: 'text', text: lines.join('\n'), parseMode: 'plain' }]
}
renderStreaming(text: string): FormattedContent {
return { kind: 'text', text, parseMode: 'plain' }
}
}
4. Wire Everything
Create index.ts:
import { createAdapterRuntime } from '@oro.ad/bridge-adapter-sdk'
import { ConsolePlatform } from './platform'
import { ConsoleFormatter } from './formatter'
const runtime = createAdapterRuntime({
bridge: { url: 'http://localhost:3000' },
platform: new ConsolePlatform(),
formatter: new ConsoleFormatter(),
dataDir: './data',
skipReplay: true,
logger: {
debug: () => {},
info: (msg, meta) => console.error(`[info] ${msg}`, meta ? JSON.stringify(meta) : ''),
warn: (msg, meta) => console.error(`[warn] ${msg}`, meta ? JSON.stringify(meta) : ''),
error: (msg, meta) => console.error(`[error] ${msg}`, meta ? JSON.stringify(meta) : ''),
},
})
process.on('SIGINT', async () => {
await runtime.stop()
process.exit(0)
})
await runtime.start()
console.error('Adapter ready. Type: <containerId> <message>')
5. Run
npx tsx index.ts
You should see containers created for each bridge session. Type a container ID followed by a message to send it to Claude.
Next Steps
- See the Telegram adapter example for a production-quality implementation
- Read the PlatformAdapter reference for all interface methods
- Explore Semantic Blocks for rich formatting