@mentingo/luma-sdk
v0.4.1
Published
Luma API SDK
Keywords
Readme
Luma SDK
TypeScript SDK for the Luma platform used by Mentingo.
It provides two separate clients:
- HTTP client for durable course authoring, Mentor chat, assets, administration and configuration
- Socket client for realtime audio/voice mentor flows
Installation
pnpm add @mentingo/[email protected]
# or
npm install @mentingo/[email protected]
# or
yarn add @mentingo/[email protected]Quick Start (HTTP)
import { createLumaClient } from "@mentingo/luma-sdk";
const client = createLumaClient({
baseURL: "https://your-luma-api.example.com",
apiKey: process.env.LUMA_API_KEY,
});
const session = await client.authoring.createSession({
commandId: crypto.randomUUID(),
courseId: "00000000-0000-4000-8000-000000000001",
actorId: "author-123",
language: "en",
});
const snapshot = await client.authoring.getSession({ sessionId: session.sessionId });
console.log(snapshot.tasks);Each client uses one apiKey. Supply an administration key for admin operations
or an organization feature key for AI and course operations. The API validates
the key's type and permissions; using a different SDK method does not grant
additional access. To perform both kinds of operations, create separate clients
with the appropriate keys.
The SDK sends that key as X-Admin-API-Key for luma.admin and as X-API-Key
for feature operations, matching the existing API contracts.
const luma = createLumaClient({
baseURL: "https://your-luma-api.example.com",
apiKey: process.env.LUMA_ADMIN_API_KEY,
});
const keys = await luma.admin.apiKeys.list({ organizationId: "org-123" });
const created = await luma.admin.apiKeys.create({
organizationId: "org-123",
name: "Course generation",
});
await luma.admin.apiKeys.update({
organizationId: "org-123",
apiKeyId: created.id,
name: "Updated course generation",
});
const configuration = await luma.admin.apiKeys.getConfiguration({
organizationId: "org-123",
apiKeyId: created.id,
});
const assignments = await luma.admin.apiKeys.listAssignments({
organizationId: "org-123",
apiKeyId: created.id,
});Quick Start (Socket)
import {
createLumaSocket,
LUMA_AUDIO_ACTIONS,
LUMA_AUDIO_FORMATS,
LUMA_SOCKET_MESSAGE_TYPES,
} from "@mentingo/luma-sdk";
const socket = createLumaSocket({
baseURL: "https://your-luma-api.example.com",
apiKey: process.env.LUMA_API_KEY,
socketData: {
sessionId: "session-123",
userId: "user-123",
lessonId: "lesson-123",
},
});
socket
.onServerConnected((payload) => console.log("connected", payload))
.onMentorTranscription((payload) => console.log("transcription", payload))
.onAudioOutputChunk((payload) => console.log("audio chunk", payload));
socket.connect();
socket.startAudio({
type: LUMA_SOCKET_MESSAGE_TYPES.AUDIO_START,
audioAction: LUMA_AUDIO_ACTIONS.VOICE_MENTOR,
meta: { sr: 16000, channels: 1, format: LUMA_AUDIO_FORMATS.PCM_S16LE },
});
socket.sendAudioChunk(
{
type: LUMA_SOCKET_MESSAGE_TYPES.AUDIO_CHUNK,
meta: { seq: 1, sr: 16000, samples: 320, tsMs: Date.now() },
},
new Uint8Array([0, 1, 2]),
);
socket.stopAudio();Recovery responses expose stable public constants for state handling:
import {
AUDIO_PROVIDER_STATES,
AUDIO_RECOVERY_STATES,
type AudioRecoveryPayload,
} from "@mentingo/luma-sdk";
socket.onAudioRecovered((payload: AudioRecoveryPayload) => {
if (payload.state === AUDIO_RECOVERY_STATES.MENTOR_ACTIVE) {
// Keep the playback UI active after reconnecting.
}
if (payload.providerState === AUDIO_PROVIDER_STATES.RESTARTED) {
// The transcription provider was recreated; replay unacknowledged chunks.
}
});HTTP Client API
createLumaClient(opts)
Options:
baseURL?: string- Luma API base URL.apiKey?: string- single client credential; sent asX-Admin-API-Keyforclient.adminandX-API-Keyfor feature operations. The API enforces its type and permissions.httpsAgent?: Agent- custom Node.js HTTPS agent.allowInsecureTls?: boolean- iftrue, usesrejectUnauthorized: false(dev only).
Namespaces:
client.mentor.streamChat(opts)- stream mentor chat through the public custom-runtime endpoint. PassvoiceSessionIdfor voice mentor sessions so Luma can forward mentor text directly to the voice session.client.mentor.chat(opts)- alias forclient.mentor.streamChat(opts).client.mentor.generateChat(opts, { signal? })- generate a native non-stream chat response, with optional cancellation.client.mentor.judge(opts)client.ai.createEmbeddings(opts)client.ai.generateJudgeConfiguration(opts)client.ai.validateJudgeConfiguration(opts)client.ai.generateTranslations(opts)client.ai.transcribeDictation(opts)client.configuration.get()client.admin.apiKeys.list(opts)client.admin.apiKeys.create(opts)client.admin.apiKeys.get(opts)client.admin.apiKeys.update(opts)client.admin.apiKeys.revoke(opts)client.admin.apiKeys.getConfiguration(opts)client.admin.apiKeys.listAssignments(opts)client.admin.apiKeys.updateAssignment(opts)client.admin.modelProfiles.list(opts)
The HTTP client intentionally exposes public API operations through namespaces instead of flat top-level methods.
Runtime Configuration
Use client.configuration.get() to inspect which public AI capabilities are enabled
for the API key. The SDK exports AiCapability, AiCapabilityMode,
AiCapabilityProvider, AiRuntimeConfiguration, and convenience constants such as
LUMA_AI_CAPABILITIES and LUMA_AI_CAPABILITY_MODES.
Custom model names should use the backend provider:model format, for example
openai:gpt-4.1-mini or ollama:llama3.1.
Voice TTS capabilities expose an optional vendor (TTSProvider) for core-mode
routing. API key settings use extensible non-secret metadata, for example
metadata: { textToSpeechProvider: TextToSpeechProvider.Elevenlabs } in
admin.apiKeys.update. Omitted metadata entries stay unchanged; setting an entry
to null removes it. Credentials remain in the write-only textToSpeechApiKey
field; hasTextToSpeechApiKey reports whether one is saved.
For voice mentor flows, use client.mentor.streamChat({ ..., voiceSessionId }) so streamed mentor text can be forwarded to the active socket session without a separate polling or relay step. Use client.mentor.generateChat(opts) for normal one-shot text responses. Tool-capable assistant calls use the native options below and do not attach a voice session.
Native Tool-Capable Chat
The existing Mentor client also carries assistant tool calls through Luma's native chat API. Luma resolves the API key's configured Custom chat profile; the SDK does not choose a vendor or contact an OpenAI chat-completions endpoint.
const result = await client.mentor.generateChat(
{
messages: [{ role: "user", content: "Read the course title." }],
tools: [
{
name: "read_course",
description: "Read an authorized course",
inputSchema: { type: "object", properties: { courseId: { type: "string" } } },
},
],
toolChoice: "required",
maxOutputTokens: 2500,
streamFormat: "events",
},
{ signal },
);
// result.toolCalls contains native { id, name, arguments } calls.
// Execute tools with the application's normal authorization checks, then append
// assistant toolCalls and role: "tool" / toolCallId messages to the next request.For streaming, call client.mentor.streamChat(opts, { signal }) with
streamFormat: "events". The existing /api/public/v1/ai/mentor/chat route returns
UTF-8 NDJSON records: text-delta, complete tool-call, finish, or a stable
error code. The response body remains the transport's readable stream; callers
must decode UTF-8 and newline framing incrementally rather than treating each
network chunk as one JSON event. The SDK does not execute tools or retry a stream.
Generation uses the existing /api/public/v1/ai/mentor/chat/generate route.
Calls omitting tool/event options retain the existing { message } generation
response and plain-text streaming behavior. streamChat already supports request
cancellation in SDK 0.4.0; the optional generation signal parameter is added by
SDK 0.4.1. Model execution, provider availability and fallback policy stay with
Luma and the consuming application. Deploy the matching Luma native tool-chat
contract before enabling event-mode callers.
Native Embeddings
Use the existing AI client to embed text with the API key's configured Custom embedding profile:
const result = await client.ai.createEmbeddings({
texts: ["Customer onboarding policy"],
dimensions: 1536,
});
// result.embeddings contains the vectors; result.model identifies the profile model.This calls POST /api/public/v1/ai/embeddings with the normal X-API-Key
transport. dimensions is optional and must be supported by the configured model.
Luma validates the returned vector count, dimensions and finite values. The model
identity comes from the same runtime resolution that produced the vectors.
Texts-only calls remain valid. Older Luma responses may omit model; consumers
that persist or compare vector spaces must reject missing identity rather than
assuming a model. Matching dimensions alone do not make embedding spaces compatible.
The public contract is generated from Mentingo AI PR #63. The SDK update adds types and transport regressions; published SDK 0.4.0 already forwards these additive body and response fields unchanged at runtime. No OpenAI-format embeddings relay is used.
Durable Course Authoring
Use client.authoring to create/list sessions, read snapshots and events, subscribe to SSE,
submit commands, upload source documents, answer authorized context requests, prepare frozen
exports, and record application receipts. Course changes remain reviewable proposals; the SDK
never applies them to the consuming application's database.
Snapshot tasks expose optional failure metadata with a stable code and recovery action.
Clients should use retry_failed_parts, answer_question, retry_provider, or service_fix
to present the appropriate next step rather than retrying every failure automatically.
Download generated visuals through client.authoring.downloadAsset({ sessionId, assetId, revision }).
The download is authenticated and revision-bound; import the bytes into the consuming application's
storage and render its native image node. Do not persist temporary download URLs.
Socket Client API
createLumaSocket(opts)
Options:
baseURL?: stringapiKey?: stringallowInsecureTls?: booleansocketData?: { sessionId?: string; userId?: string; lessonId?: string }
Emit helpers:
startAudio(payload)-> emitsstart_audiosendAudioChunk(payload, chunk)-> emitsaudio_chunksendClientSpeechStart(payload)-> emitsclient_speech_startsendClientSpeechEnd(payload)-> emitsclient_speech_endstopAudio(payload?)-> emitsaudio_stopsendMentorTextDelta(payload)-> emitsmentor_text_deltasendMentorTextEnd(payload)-> emitsmentor_text_endsendMentorTextError(payload)-> emitsmentor_text_errorsendPing(payload?)-> emitsping
Listener helpers:
onServerConnected(handler)-> listensserver:connectedonAudioStarted(handler)-> listensaudio:startedonAudioChunked(handler)-> listensaudio:chunkedonAudioRecovered(handler)-> listensaudio:recoveredonAudioStopped(handler)-> listensaudio:stoppedonMentorTranscription(handler)-> listensmentor:transcriptiononAudioOutputChunk(handler)-> listensaudio:output:chunkonAudioOutputInterrupted(handler)-> listensaudio:output:interruptedonAudioOutputError(handler)-> listensaudio:output:erroronAudioOutputComplete(handler)-> listensaudio:output:complete
Public Exports
- HTTP:
createLumaClient,LumaClient,LumaClientOptions, namespaced HTTP clients - Socket:
createLumaSocket,LumaSocket, socket payload/event types fromsrc/socket/types.ts - Shared API/domain types from
src/types.ts
Development
pnpm install
pnpm --filter @mentingo/luma-sdk typecheck
pnpm --filter @mentingo/luma-sdk test
pnpm --filter @mentingo/luma-sdk build
pnpm --filter @mentingo/luma-sdk lintRegenerate API client from OpenAPI schema
pnpm generate:clientUses src/api/api-schema-public.json to regenerate src/api/generated-api.ts.
Notes
- Built with
tsupand ships ESM + CJS. - HTTP auth uses the
X-API-Keyheader. - Keep API keys out of source control.
