SDK Reference
The Gambi SDK exposes four distinct surfaces:
createGambi()for inference through the room-scoped OpenAI-compatible endpointscreateClient()for management operations against the native/v1APIcreateParticipantSession()for running a tunnel-backed participant session- discovery helpers for local-network hub and room resolution
Installation
Section titled “Installation”npm install gambi-sdk# orbun add gambi-sdkSDK surfaces
Section titled “SDK surfaces”| Surface | Primary use | Transport |
|---|---|---|
createGambi() | application inference | /rooms/:code/v1/* |
createClient() | operational control | /v1/* |
createParticipantSession() | participant runtime | management API + participant tunnel |
| discovery helpers | local-network room and hub resolution | mDNS + management API |
createGambi(options)
Section titled “createGambi(options)”Create a provider instance for application inference.
import { createGambi } from "gambi-sdk";
const gambi = createGambi({ roomCode: "ABC123", hubUrl: "http://localhost:3000",});| Option | Type | Default | Description |
|---|---|---|---|
roomCode | string | — | room code to connect to |
hubUrl | string | http://localhost:3000 | hub URL |
defaultProtocol | "openResponses" | "chatCompletions" | "openResponses" | default protocol for top-level routing helpers |
Model routing
Section titled “Model routing”The provider offers three routing strategies, all compatible with AI SDK v5.
import { generateText } from "ai";
await generateText({ model: gambi.any(), prompt: "..." });await generateText({ model: gambi.model("llama3"), prompt: "..." });await generateText({ model: gambi.participant("worker-1"), prompt: "..." });| Helper | Selects | Use when |
|---|---|---|
gambi.any() | a random available participant | any online participant is acceptable |
gambi.model(name) | the first available participant exposing name | you need a specific model but not a specific machine |
gambi.participant(id) | the participant with that id | you need exact targeting (affinity, debugging, benchmarking) |
A participant is available only when its tunnel is connected, it is not offline, and it is not already handling another request.
Protocol selection
Section titled “Protocol selection”The provider defaults to openResponses.
gambi.any();gambi.model("llama3");gambi.participant("worker-1");
gambi.openResponses.any();gambi.chatCompletions.any();Use chatCompletions only when you need explicit compatibility. New integrations should prefer the default Responses path.
Streaming
Section titled “Streaming”Streaming works through the AI SDK like any other provider.
import { streamText } from "ai";
const result = streamText({ model: gambi.any(), prompt: "Write a haiku about local-first infrastructure.",});
for await (const chunk of result.textStream) { process.stdout.write(chunk);}You can also call the inference API directly with fetch and stream: true against ${gambi.baseURL}/responses or ${gambi.baseURL}/chat/completions.
gambi.listModels()
Section titled “gambi.listModels()”Return room models in an OpenAI-compatible list shape.
Each GambiModel includes a gambi extension with:
nicknamemodelendpointcapabilitiesconnection
connection exposes the participant tunnel state seen by the hub.
gambi.listParticipants()
Section titled “gambi.listParticipants()”Return management-plane participant summaries for the room.
gambi.baseURL
Section titled “gambi.baseURL”Return the room-scoped OpenAI-compatible base URL.
createClient(options)
Section titled “createClient(options)”Create the management client.
import { createClient } from "gambi-sdk";
const client = createClient({ hubUrl: "http://localhost:3000",});All management methods return { data, meta } envelopes.
Namespaces
Section titled “Namespaces”| Namespace | Methods |
|---|---|
client.rooms | create, get, list |
client.participants | upsert, list, remove, heartbeat |
client.events | watchRoom |
client.rooms.create(input)
Section titled “client.rooms.create(input)”const created = await client.rooms.create({ name: "Demo", password: "secret", defaults: { temperature: 0.4 },});
console.log(created.data.room.code);console.log(created.data.hostId);client.rooms.get(roomCode)
Section titled “client.rooms.get(roomCode)”const { data } = await client.rooms.get("ABC123");console.log(data.participantCount, data.passwordProtected);client.rooms.list()
Section titled “client.rooms.list()”const { data } = await client.rooms.list();for (const room of data) { console.log(room.code, room.name, room.participantCount);}client.participants.upsert(roomCode, participantId, input)
Section titled “client.participants.upsert(roomCode, participantId, input)”Create or update a participant registration.
const result = await client.participants.upsert("ABC123", "worker-1", { nickname: "worker-1", model: "llama3", endpoint: "http://localhost:11434", capabilities: { openResponses: "supported", chatCompletions: "supported", },});
console.log(result.data.tunnel.url);Input fields:
| Field | Type | Required | Description |
|---|---|---|---|
nickname | string | yes | display name |
model | string | yes | model name |
endpoint | string | yes | participant-local endpoint URL |
password | string | no | room password |
specs | object | no | machine specs |
config | RuntimeConfig | no | participant runtime defaults |
capabilities | object | no | protocol capability summary |
Return fields:
| Field | Type | Description |
|---|---|---|
participant | ParticipantSummary | public participant state |
roomId | string | internal room identifier |
tunnel | TunnelBootstrap | one-time tunnel bootstrap |
This method is retry-safe because the underlying management route is idempotent. The tunnel bootstrap token is single-use and short-lived.
client.participants.list(roomCode)
Section titled “client.participants.list(roomCode)”const { data } = await client.participants.list("ABC123");for (const participant of data) { console.log(participant.id, participant.status, participant.connection);}client.participants.heartbeat(roomCode, participantId)
Section titled “client.participants.heartbeat(roomCode, participantId)”Send one heartbeat. Call this every ten seconds while your own participant runtime is alive. The hub marks a participant offline after thirty seconds without a heartbeat.
setInterval(() => { client.participants.heartbeat("ABC123", "worker-1").catch(console.error);}, 10_000);Both cadence and timeout come from HEALTH_CHECK_INTERVAL and PARTICIPANT_TIMEOUT in @gambi/core. If you use createParticipantSession(), this loop is managed for you.
client.participants.remove(roomCode, participantId)
Section titled “client.participants.remove(roomCode, participantId)”await client.participants.remove("ABC123", "worker-1");client.events.watchRoom({ roomCode, signal? })
Section titled “client.events.watchRoom({ roomCode, signal? })”Watch room events as an async iterable.
const controller = new AbortController();
for await (const event of client.events.watchRoom({ roomCode: "ABC123", signal: controller.signal,})) { console.log(event.type, event.data);}Event types:
connectedroom.createdparticipant.joinedparticipant.updatedparticipant.leftparticipant.offlinellm.requestllm.completellm.error
See Observability for the payload shapes of the llm.* events.
createParticipantSession(options)
Section titled “createParticipantSession(options)”Create and manage a tunnel-backed participant runtime.
import { createParticipantSession } from "gambi-sdk";
const session = await createParticipantSession({ hubUrl: "http://localhost:3000", roomCode: "ABC123", participantId: "worker-1", nickname: "worker-1", endpoint: "http://localhost:11434", model: "llama3", authHeaders: { Authorization: `Bearer ${process.env.PROVIDER_TOKEN}`, },});
await session.waitUntilClosed();What it does:
- Probe the local endpoint.
- Register the participant through the management API.
- Open the participant tunnel.
- Forward tunnel requests to the local provider endpoint.
- Keep sending management heartbeats every ten seconds and tunnel pings on the same cadence.
authHeaders stay local to the participant runtime. They are applied only when the runtime talks to the provider endpoint.
For a walkthrough, see Custom participant runtime.
Session return shape
Section titled “Session return shape”| Field | Type | Description |
|---|---|---|
participant | registered participant summary | |
roomId | room identifier | |
tunnel | tunnel bootstrap details | |
close() | best-effort graceful shutdown | |
waitUntilClosed() | await final session close reason |
Close reasons:
"closed"— shutdown initiated byclose()or a received signal"heartbeat_failed"— the management heartbeat loop failed repeatedly"tunnel_closed"— the WebSocket tunnel was closed from either side
Discovery helpers
Section titled “Discovery helpers”Discovery remains optional. createGambi() and createClient() stay explicit — they never perform implicit discovery.
Available helpers:
discoverHubs(options?)discoverRooms(options?)resolveGambiTarget(options?)
Common options (DiscoveryOptions):
| Option | Type | Description |
|---|---|---|
hubUrl | string | skip mDNS and query this hub directly |
timeoutMs | number | how long to wait for mDNS responses |
fetchFn | typeof fetch | custom fetch (tests, proxies) |
browseServices | BrowseServicesLike | custom mDNS browser |
resolveGambiTarget() also accepts roomCode and roomName for picking a single room.
Example
Section titled “Example”import { createGambi, resolveGambiTarget } from "gambi-sdk";
const target = await resolveGambiTarget({ roomCode: "ABC123", timeoutMs: 1500,});
const gambi = createGambi({ hubUrl: target.hubUrl, roomCode: target.roomCode,});DiscoveryError
Section titled “DiscoveryError”Thrown when discovery cannot produce a unique target.
Codes:
NO_HUBS_FOUNDNO_ROOMS_FOUNDROOM_NOT_FOUNDAMBIGUOUS_ROOM_MATCH(inspecterror.matches)
Errors
Section titled “Errors”Management operations throw ClientError.
Important fields:
status— HTTP statuscode— Gambi error code (e.g.ROOM_NOT_FOUND,PARTICIPANT_TUNNEL_NOT_CONNECTED)hint— operator-readable next stepdetails— optional structured payload from the hubrequestId— correlates with hub logs and SSE events
import { ClientError } from "gambi-sdk";
try { await client.rooms.get("ZZZZZZ");} catch (error) { if (error instanceof ClientError && error.code === "ROOM_NOT_FOUND") { console.warn(error.hint); return; } throw error;}