@hikigai/app-sdk
Application SDK
Official Node.js SDK for invoking healthcare AI agents in your applications. Full streaming, WebSocket, and session support with type-safe responses.
Installation
Choose your preferred package manager:
npm
npm install @hikigai/app-sdkyarn
yarn add @hikigai/app-sdkpnpm
pnpm add @hikigai/app-sdkQuick Start
import { AppClient } from '@hikigai/app-sdk'
// Initialize client
const client = new AppClient({
apiKey: process.env.HIKIGAI_API_KEY,
projectId: process.env.HIKIGAI_PROJECT_ID,
})
// Get an agent
const agent = await client.agent('medical-coder')
// Basic invocation
const response = await agent.invoke('Patient presents with fever and cough...')
console.log(response.content)
// Streaming responses
for await (const chunk of agent.stream('Tell me about type-2 diabetes')) {
process.stdout.write(chunk)
}
// Session-based conversations
const sessionAgent = agent.withSession('patient-123')
await sessionAgent.invoke('What is the treatment?')
await sessionAgent.invoke('Any contraindications?') // Remembers contextCore Concepts
AppClient
Main interface for agent invocation and management.
class AppClient {
constructor(options?: {
apiKey?: string // API key (or HIKIGAI_API_KEY env var)
projectId?: string // Project ID (or HIKIGAI_PROJECT_ID env var)
baseUrl?: string // API endpoint
timeout?: number // Request timeout in milliseconds
sonaUrl?: string // Optional SONA service URL
sonaApiKey?: string // Optional SONA API key
})
readonly storage: StorageClient
readonly identity: IdentityClient
readonly sona: SONAClient
readonly cloud: CloudClient
readonly events: EventsClient
agent(agentId: string): Promise<RuntimeAgent>
listAgents(options?: {
category?: string; tags?: string[]; search?: string
}): Promise<RuntimeAgent[]>
invoke(agentId: string, input: string | Record<string, unknown>,
options?: InvokeOptions): Promise<InvokeResponse>
stream(agentId: string, input: string | Record<string, unknown>,
options?: StreamOptions): AsyncIterable<string>
live(agentId: string, sessionId?: string, userId?: string,
context?: Record<string, unknown>): StreamSession
createSession(options?: {
externalUserRef?: string; ttlSeconds?: number;
appId?: string; metadata?: Record<string, unknown>
}): Promise<SessionContext>
session(sessionId: string, options?: { appId?: string }): SessionContext
listSessions(options?: {
userRef?: string; limit?: number; cursor?: string
}): Promise<{ sessions: unknown[]; nextCursor?: string }>
getAuthToken(): Promise<AuthTokenResponse>
close(): void
}RuntimeAgent
Represents a deployed agent ready for invocation.
interface RuntimeAgent {
id: string
name: string
slug: string
version?: string
description?: string
status: 'active' | 'pending' | string
endpoint?: string
invoke(input: string | Record<string, unknown>,
options?: InvokeOptions): Promise<InvokeResponse>
stream(input: string | Record<string, unknown>,
options?: StreamOptions): AsyncIterable<string>
withSession(sessionId: string): RuntimeAgent
liveSession(sessionId?: string): StreamSession
}
// InvokeOptions / StreamOptions
// sessionId?, provider?, model?, timeout?, connectors?, pluginContext? (invoke only)Invocation Methods
Multiple ways to interact with agents.
Basic Invocation
const response = await agent.invoke(
'Clinical note to analyze',
{
sessionId: 'patient-123',
timeout: 60000,
}
)
console.log(response.content) // Agent response
console.log(response.metadata.latencyMs) // Performance infoMCP Connectors
Pass linked MCP connector slugs in connectors. The platform injects apiKey and projectId from your AppClient credentials automatically.
import type { McpConnectors } from '@hikigai/app-sdk'
const connectors: McpConnectors = {
'idexx-mcp-server': {}, // platform auth injected automatically
}
const response = await agent.invoke('Prepare patient chart', { connectors })
// Optional HTTP headers (Claude Desktop style)
const withHeaders: McpConnectors = {
'idexx-mcp-server': {
headers: {
'X-API-Key': process.env.HIKIGAI_API_KEY!,
'X-Project-Id': process.env.HIKIGAI_PROJECT_ID!,
},
},
}MCP on Workflow Agents
After deploying a Sequential / Parallel / Loop agent with root connectors (Node AgentSDK → MCP on Sub-Agents), pass the same map on invoke/live. Tools attach to every LlmAgent leaf. Confirm with metadata.toolsCalled (from top-level gateway tools_called).
import type { McpConnectors } from '@hikigai/app-sdk'
const connectors: McpConnectors = {
'idexx-mcp-server': {},
}
const agent = await client.agent('vitals-fetch-subagent')
const response = await agent.invoke(
{ site_id: 1, patient_id: 'PATIENT001' },
{ connectors, timeout: 120_000 },
)
console.log(response.content) // summary / { response: '...' }
console.log(response.metadata.toolsCalled) // ['get_vitals_for_today']
console.log(response.metadata.latencyMs)
// Live — same connectors for leaf CallTool
// const session = await client.live('vitals-fetch-subagent', {
// connectors: { 'idexx-mcp-server': {} },
// })Agent Discovery
// Get specific agent
const agent = await client.agent('medical-coder')
// List all agents
const agents = await client.listAgents()
// Search agents
const filtered = await client.listAgents({
category: 'medical-coding',
tags: ['icd-10'],
search: 'coding',
})Streaming Responses
Process responses in real-time as they arrive.
// Stream text responses
for await (const chunk of agent.stream('Analyze this report...')) {
process.stdout.write(chunk)
}
// Collect streamed data
let fullResponse = ''
for await (const chunk of agent.stream(input)) {
fullResponse += chunk
}
console.log('Complete response:', fullResponse)Session Management
Multi-turn conversations with persistent context via withSession / sessionId.
const sessionAgent = agent.withSession('patient-789')
// First turn - establish context
await sessionAgent.invoke('I have been experiencing chest pain')
// Second turn - knows about chest pain
await sessionAgent.invoke('Is this serious?')
// Third turn - maintains full conversation history
await sessionAgent.invoke('What tests should I get?')Session Context Store
Server-side key/value context (SessionContext), separate from conversation memory on invoke.
const ctx = await client.createSession({
externalUserRef: 'dr-smith',
ttlSeconds: 3600,
})
await ctx.set('patient_id', 'MRN-12345')
console.log(await ctx.get('patient_id'))
console.log(await ctx.getAll())
const again = client.session(ctx.sessionId)
await again.update({ chief_complaint: 'chest pain' })
await again.touch(7200)WebSocket Live Sessions
liveHandoff on leaves when deploying (see Node AgentSDK → Workflow Agents). To reuse a marketplace agent as a leaf, deploy with source: 'marketplace' + name (see reuse marketplace). AppSDK only opens the session.Real-time bidirectional communication with agents via StreamSession. Pass connectors (same shape as invoke) to attach MCP tools on live.
const session = agent.liveSession(undefined, {
connectors: { 'epic-ehr': {} },
})
await session.connect()
await session.start('text') // or 'audio'
await session.sendText('Look up the patient chart')
// await session.sendAudio(pcmBuffer, 'audio/pcm;rate=16000')
for await (const event of session) {
console.log(event)
if (event.type === 'turn_complete') break
}
await session.end()
await session.close()Speech: Translate & Transcribe
Real-time speech-to-speech translation and speech-to-text transcription via client.speech. These are not agents — nothing to deploy, and no agent slug. Available to team organizations with GCP connected, billed to your own cloud project. Full protocol: API Reference → Speech.
Translate
import { AppClient } from '@hikigai/app-sdk'
const client = new AppClient({
apiKey: process.env.HIKIGAI_API_KEY!,
projectId: process.env.HIKIGAI_PROJECT_ID!,
})
const session = await client.speech.translate({
targetLanguage: 'es', // bare tags: es, ja, pt-BR
echoTargetLanguage: true, // default: echo when input is already Spanish
inputTranscription: true, // default
outputTranscription: true, // default
})
// Any source format — converted to 16 kHz mono PCM for you.
await session.sendAudio(muLawChunk, { encoding: 'mulaw', sampleRate: 8000 })
for await (const event of session) {
if (event.type === 'audio') speaker.write(event.audio) // 24 kHz PCM
if (event.type === 'transcript') console.log(event.source, event.text, event.language_code)
}Transcribe
const session = await client.speech.transcribe({
languages: ['en-US', 'es-ES'], // optional; omit to auto-detect
customVocabulary: ['Hikigai', 'Kubernetes'],
})
await session.sendAudio(pcm16k) // already 16 kHz mono PCM
for await (const event of session) {
if (event.type === 'transcript') {
console.log(event.text, event.language_code, event.final ? 'final' : 'interim')
}
}Code shapes differ: translate takes bare subtags (es), transcribe takes full locales (es-ES). Google caps transcription at 10 minutes; the gateway rotates the upstream transparently, so long sessions need no handling.
Changing language mid-conversation
Returns once the new language is live. A replacement model session is brought up before the old one is retired, so audio keeps flowing.
await session.setLanguage('fr') // no reconnect, no new tokenAudio helpers
import { toModelAudio, chunkAudio, pcmToWav } from '@hikigai/app-sdk'
const pcm = toModelAudio(raw, { encoding: 'mulaw', sampleRate: 8000 })
const pcm = toModelAudio(raw, { encoding: 'f32', sampleRate: 48000 })
const pcm = toModelAudio(raw, { sampleRate: 48000, channels: 2 })
for (const chunk of chunkAudio(pcm)) {
await session.sendAudio(chunk)
}
fs.writeFileSync('out.wav', pcmToWav(translated)) // 24 kHz by defaultBrowser and mobile clients
Mint a short-lived token server-side so your API key never reaches the client.
const token = await client.speech.mintToken({
endpoint: 'translate',
config: { targetLanguage: 'ja' },
locked: true, // pin the config — client cannot call configure
})
// Hand token.speechToken to the browser; it connects directly.Rooms
Create and use multi-party realtime rooms with RoomSession. Call join(name) — first join creates the room. Then publish, listen for frames, and optionally use rtcJoin / rtcOffer / rtcAnswer for WebRTC signalling. Same surface as Python AgentSDK / AppSDK.
import { RoomSession } from '@hikigai/app-sdk'
const room = new RoomSession({
baseUrl: 'https://backend.hikigaiplatform.io',
apiKey: process.env.HIKIGAI_API_KEY!,
projectId: process.env.HIKIGAI_PROJECT_ID!,
})
await room.connect()
await room.join('care-team-7') // creates the room if new
await room.publish('care-team-7', 'note', { text: 'Labs ready' })
for await (const frame of room) {
if (frame.type === 'message') console.log(frame.event, frame.data)
if (frame.type === 'error') break
}
await room.close()Even Realities G2 Glasses Integration
Complete guide to building apps for Even Realities G2 smart glasses powered by Hikigai agents. G2 apps run inside the Even phone app WebView and drive the glasses display (640×350 green monochrome) via Bluetooth with @evenrealities/even_hub_sdk. Keep Hikigai credentials on a server proxy — the page is public.
Architecture & Communication Flow
- Phone WebView: Loads your HTTPS page inside the Even mobile app.
- Agent Proxy: Receives questions from the WebView, holds credentials server-side, invokes via
@hikigai/app-sdk. - Formatting:
normalizeForGlasses()+paginateForGlasses()strip Markdown and chunk answers (starter uses 420 chars; SDK default is 2000). - Bluetooth Display: Even Hub SDK sends UI containers to the lenses and receives temple/mic events.
Starter template
Copy templates/even-g2 for a full Vite + Express + glasses UI starter. Useful scripts: npm run check (validate app.json), npm run qr / npm run pack (Even Hub CLI), npm run simulator when the Even Hub simulator is installed.
Environment variables
| Variable | Required | Notes |
|---|---|---|
HIKIGAI_API_KEY | Yes | Injected automatically on Hikigai deploy; set locally in .env. |
HIKIGAI_PROJECT_ID | Yes | Injected on deploy; set locally in .env. |
HIKIGAI_AGENT_ID | Yes | Agent slug or id. Set under Environment in the console (not auto-injected). |
HIKIGAI_PLATFORM_URL | No | Optional baseUrl override; injected on deploy. |
GLASSES_PAGE_CHARS | No | Chars per glasses page (starter default 420; SDK default is 2000). |
Local / deploy wiring
- Set Vite
base: './'so assets resolve under a path prefix or subdomain. - In dev, proxy
/apifrom Vite to Express (e.g.localhost:3000). - Call the proxy with relative URLs (
./api/askornew URL('api/ask', document.baseURI)) so the page works wherever Hikigai hosts it.
// vite.config.ts
export default defineConfig({
base: './',
server: { proxy: { '/api': 'http://localhost:3000' } },
})
// src/api.ts — relative so path-prefix and subdomain deploys both work
await fetch(new URL('api/ask', document.baseURI), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, sessionId }),
})app.json gotchas
package_id— reverse-DNS, unique on Even Hub (e.g.ai.hikigai.myapp).name≤ 20 chars,tagline≤ 50,description≤ 1024.- Typical permissions:
network(reach your proxy) +g2-microphone(hold-to-talk). Drop mic if you only use list/text. - Validate with
validateEvenG2Manifest/npm run checkbefore deploy.
SDK helpers (pure, no network)
import {
validateEvenG2Manifest, // same app.json rules as the deploy pipeline
paginateForGlasses, // split agent answers into display-sized pages
normalizeForGlasses, // strip markdown decorations agents emit
evenG2DevModeUrl, // validate the live URL used in the dev-mode QR
EVEN_G2_DISPLAY, // { width: 640, height: 350 }
EVEN_G2_MAX_TEXT_CHARS, // 2000 (official text-heavy template cap)
EVEN_G2_PERMISSIONS, // network, g2-microphone, phone-microphone, ...
EVEN_G2_LANGUAGES, // en, de, fr, es, it, zh, ja, ko
} from '@hikigai/app-sdk'
const check = validateEvenG2Manifest(JSON.parse(fs.readFileSync('app.json', 'utf8')))
if (!check.valid) throw new Error(check.error)
const pages = paginateForGlasses(agentAnswer, { maxChars: 420 })
const qrPayload = evenG2DevModeUrl(liveUrl)1. Backend Proxy (Express Example)
Create an API endpoint that receives queries from the Even App WebView and proxies them to your Hikigai agent. The starter template at templates/even-g2 ships this pattern.
import express from 'express'
import { AppClient, paginateForGlasses, normalizeForGlasses } from '@hikigai/app-sdk'
const app = express()
app.use(express.json())
const client = new AppClient({
apiKey: process.env.HIKIGAI_API_KEY,
projectId: process.env.HIKIGAI_PROJECT_ID,
})
const PAGE_CHARS = Number(process.env.GLASSES_PAGE_CHARS || 420)
app.post('/api/ask', async (req, res) => {
try {
const agent = await client.agent(process.env.HIKIGAI_AGENT_ID)
const response = await agent.invoke(req.body.question, {
sessionId: req.body.sessionId,
})
const cleanText = normalizeForGlasses(response.content)
const pages = paginateForGlasses(cleanText, { maxChars: PAGE_CHARS })
res.json({
answer: response.content,
pages,
sessionId: response.sessionId,
})
} catch (err) {
res.status(502).json({ error: err.message })
}
})2. Audio Input (Voice Queries)
Temple long-press records PCM on the glasses; wrap it as WAV (audio/wav) and send via attachments. The agent must allow audio in its input modalities — text-only agents reject voice. List/preset text questions work with any text agent.
app.post('/api/ask-audio', async (req, res) => {
const { audio_base64, mime_type, sessionId } = req.body
const agent = await client.agent(process.env.HIKIGAI_AGENT_ID)
const response = await agent.invoke('Answer the question in this recording.', {
sessionId,
attachments: [{ data_base64: audio_base64, mime_type: mime_type || 'audio/wav' }],
})
const cleanText = normalizeForGlasses(response.content)
res.json({
pages: paginateForGlasses(cleanText, { maxChars: PAGE_CHARS }),
sessionId: response.sessionId,
})
})3. Glasses interaction loop
Use @evenrealities/even_hub_sdk in the WebView. First paint uses createStartUpPageContainer; later screens use rebuildPageContainer.
import {
AudioInputSource,
OsEventTypeList,
waitForEvenAppBridge,
RebuildPageContainer,
TextContainerProperty,
CreateStartUpPageContainer,
} from '@evenrealities/even_hub_sdk'
const bridge = await waitForEvenAppBridge()
bridge.onEvenHubEvent(handleEvent)
// First draw → createStartUpPageContainer; later → rebuildPageContainer
await bridge.createStartUpPageContainer(/* menu with list of questions */)
async function handleEvent(event: EvenHubEvent) {
// list click → ask(selectedQuestion)
// text click → show next page from pages[]
// text double-click → back to menu
// temple long-press → audioControl(true, AudioInputSource.Glasses) + record PCM
// temple release → WAV → POST /api/ask-audio
if (event.sysEvent?.eventType === OsEventTypeList.LONG_PRESS_EVENT) {
await bridge.audioControl(true, AudioInputSource.Glasses)
}
}
async function showAnswerPage(heading: string, pageText: string, pageIndex: number, totalPages: number) {
const label = totalPages > 1 ? `${heading} (${pageIndex + 1}/${totalPages})` : heading
await bridge.rebuildPageContainer(new RebuildPageContainer({
containerTotalNum: 2,
textObject: [
new TextContainerProperty({
xPosition: 0, yPosition: 0, width: 640, height: 40,
containerID: 1, content: label, zOrderIndex: 1,
}),
new TextContainerProperty({
xPosition: 0, yPosition: 44, width: 640, height: 306,
containerID: 2, content: pageText, zOrderIndex: 2, isEventCapture: 1,
}),
],
}))
}Deploy & console APIs
Detect even-g2 via app.json + @evenrealities/even_hub_sdk. The console shows a Dev-Mode QR and .ehpk download. Full HTTP reference: API Reference → Even G2.
Multi-Cloud
client.cloud — deployment catalog and BYOC credentials for apps, agents, and MCP servers (GCP / AWS).
const catalog = await client.cloud.catalog({ workload: 'app' })
for (const service of catalog.services) {
console.log(service.id, service.displayName, service.regions[0]?.id)
}
const result = await client.cloud.validateTarget({
workload: 'app',
serviceId: 'gcp-cloud-run',
region: 'europe-west1',
config: { cpu: '2', memory: '1Gi' },
})
const creds = await client.cloud.listCredentials()Events & Webhooks
client.events — HMAC-signed webhooks (at-least-once) and a live event stream.
const sub = await client.events.createWebhook({
url: 'https://ci.example.com/hooks/hikigai',
eventTypes: ['agent.deployed', 'agent.deleted'],
description: 'deploy notifications',
})
console.log(sub.secret) // shown once — store it
const stream = client.events.stream(['job.*', 'agent.deployed'])
await stream.connect()
for await (const event of stream) {
console.log(event.type, event.data)
}
await stream.close()Storage
client.storage — platform object storage scoped to project + appId. Methods: upload, signedUrl, list, get, delete.
const uploaded = await client.storage.upload('app_123', fileBuffer, 'audio.wav', {
contentType: 'audio/wav',
metadata: { session: 's-9' },
ttl: 86400,
})
console.log(uploaded.objectId, uploaded.signedUrl)
const { signedUrl, expiresAt } = await client.storage.signedUrl(
'app_123',
uploaded.objectId,
3600,
)
const info = await client.storage.get('app_123', uploaded.objectId)
console.log(info.originalFilename, info.metadata)
const page = await client.storage.list('app_123', {
contentType: 'audio/wav',
status: 'active',
limit: 20,
page: 1,
})
for (const obj of page.objects) {
console.log(obj.objectId, obj.sizeBytes)
}
await client.storage.delete('app_123', uploaded.objectId)SONA Personalization
Access via client.sona (requires sonaUrl / sonaApiKey on AppClient). Pass pluginContext: { sona: { userId: '...' } } on invoke to get SONA metadata on response.plugins.sona.
const response = await agent.invoke('Draft discharge summary', {
pluginContext: { sona: { userId: 'dr-smith-uuid' } },
})
const outputId = (response.plugins?.sona as { outputId?: string })?.outputId
await client.sona.approveOutput(outputId!, { userId: 'dr-smith-uuid' })
await client.sona.submitEdit(outputId!, 'Revised text...', { userId: 'dr-smith-uuid' })
const prefs = await client.sona.getPreferences('dr-smith-uuid')
const suggestions = await client.sona.getSuggestions('dr-smith-uuid')Response Models
interface InvokeResponse {
content: string
agentId: string
agentVersion?: string
sessionId?: string
status: string
output?: Record<string, unknown>
confidence?: ClinicalConfidence // score: 0–1
safetyFlags: SafetyFlag[] // severity: low|medium|high|critical
citations: ClinicalCitation[]
metadata: InvocationMetadata
message?: string
plugins?: Record<string, unknown> // e.g. plugins.sona
}
interface InvocationMetadata {
invocationId: string
latencyMs?: number
timestamp: Date
status: string
tokensUsed?: number
toolsCalled: string[]
phiRedacted: boolean
traceId?: string
}
interface SafetyFlag {
category: string
severity: 'low' | 'medium' | 'high' | 'critical'
message: string
mitigation?: string
}End-User Identity
Add signup, login, MFA, QR badge login, and SSO for your app's end users. Each app is backed by a dedicated AWS Cognito pool the platform provisions for you; login returns standard OIDC tokens. Reach it all through client.identity.
IdentityClient
interface IdentityClient {
// Accounts & auth
signup(appId: string, email: string, password: string,
opts?: { firstName?: string; lastName?: string; role?: string; attributes?: Record<string, string>; metadata?: Record<string, unknown> }): Promise<Record<string, unknown>>
confirm(appId: string, email: string, code: string): Promise<Record<string, unknown>>
login(appId: string, email: string, password: string): Promise<TokenResult>
refresh(appId: string, refreshToken: string): Promise<TokenResult>
logout(appId: string, email: string): Promise<Record<string, unknown>>
forgotPassword(appId: string, email: string): Promise<Record<string, unknown>>
resetPassword(appId: string, email: string, code: string, newPassword: string): Promise<Record<string, unknown>>
changePassword(appId: string, accessToken: string, currentPassword: string, newPassword: string): Promise<Record<string, unknown>>
createUser(opts: { appId: string; email: string; password: string; firstName: string; lastName: string; role?: string; metadata?: Record<string, unknown> }): Promise<Record<string, unknown>>
getUser(appId: string, email: string): Promise<Record<string, unknown>>
listUsers(appId: string, opts?: { limit?: number; offset?: number }): Promise<Record<string, unknown>[]>
updateUser(appId: string, email: string, fields: Record<string, unknown>): Promise<Record<string, unknown>>
listAppUsers(appId: string, opts?: { limit?: number; offset?: number }): Promise<Record<string, unknown>[]>
deleteAppUser(appId: string, email: string): Promise<void>
// Runtime verification (Cognito/GCP)
verifyQr(appId: string, qrPayload: string): Promise<VerifiedUser>
verifyPin(appId: string, email: string, pin: string): Promise<VerifiedUser>
verifySso(appId: string, token: string): Promise<VerifiedUser>
// MFA (TOTP)
mfaAssociate(appId: string, accessToken: string): Promise<Record<string, unknown>>
mfaVerify(appId: string, accessToken: string, code: string, opts?: { deviceName?: string }): Promise<Record<string, unknown>>
mfaSetPreference(appId: string, accessToken: string, opts?: { enabled?: boolean }): Promise<Record<string, unknown>>
mfaRespond(appId: string, email: string, session: string, code: string): Promise<TokenResult>
// QR badge & SSO
issueQr(appId: string, email: string): Promise<Record<string, unknown>>
issueQrLogin(appId: string, email: string): Promise<Record<string, unknown>>
issuePin(appId: string, email: string, pin: string): Promise<Record<string, unknown>>
qrLogin(appId: string, qrPayload: string): Promise<TokenResult>
configureSso(appId: string, opts: { providerName: string; providerType: string; /* ... */ }): Promise<Record<string, unknown>>
ssoAuthorizeUrl(appId: string, redirectUri: string, opts?: { provider?: string }): Promise<Record<string, unknown>>
ssoExchange(appId: string, code: string, redirectUri: string): Promise<TokenResult>
// Setup (one-time, per app)
enableIdentity(appId: string, opts?: { mode?: string; enabledMethods?: string[] }): Promise<Record<string, unknown>>
getIdentityConfig(appId: string): Promise<Record<string, unknown>>
byoSetup(): Promise<Record<string, unknown>>
byoTest(roleArn: string, opts?: { region?: string }): Promise<Record<string, unknown>>
}Sign up, confirm, log in
attributes are Cognito profile fields (string values — e.g. phone_number in E.164). metadata is opaque app JSON stored as Cognito custom:metadata. Cognito/GCP is the source of truth (no EndUser DB index for profile).
import { AppClient } from '@hikigai/app-sdk'
const client = new AppClient({
apiKey: process.env.HIKIGAI_API_KEY,
projectId: process.env.HIKIGAI_PROJECT_ID,
})
// 1. Register the end user
await client.identity.signup('app_123', 'clinician@hospital.com', 'Temp#Pass2026', {
firstName: 'Asha',
lastName: 'Mehta',
role: 'clinician',
// attributes → Cognito fields (strings only; e.g. phone_number in E.164)
attributes: { phone_number: '+14155552671' },
// metadata → Cognito custom:metadata JSON
metadata: { doctorID: '6278383837' },
})
// 2. Confirm (skip if the pool auto-confirms)
await client.identity.confirm('app_123', 'clinician@hospital.com', '123456')
// 3. Log in -> OIDC tokens
const tokens = await client.identity.login('app_123', 'clinician@hospital.com', 'Temp#Pass2026')
console.log(tokens.accessToken)Forgot password
await client.identity.forgotPassword('app_123', 'clinician@hospital.com')
await client.identity.resetPassword('app_123', 'clinician@hospital.com', '123456', 'NewTemp#Pass2026')Change password (logged in)
const tokens = await client.identity.login('app_123', 'clinician@hospital.com', 'Temp#Pass2026')
await client.identity.changePassword(
'app_123', tokens.accessToken!, 'Temp#Pass2026', 'NewTemp#Pass2026',
)Update role / profile
await client.identity.updateUser('app_123', 'clinician@hospital.com', { role: 'doctor' })Multi-factor login
const result = await client.identity.login('app_123', 'clinician@hospital.com', 'Temp#Pass2026')
if (result.status === 'challenge') {
const code = await promptForTotpCode()
const tokens = await client.identity.mfaRespond(
'app_123', 'clinician@hospital.com', result.session!, code,
)
console.log(tokens.accessToken)
}QR badge login
// Admin: issue a badge for the user (HKG2 payload shown once)
const badge = await client.identity.issueQrLogin('app_123', 'clinician@hospital.com')
console.log(badge.qr_payload) // encode into the printed badge
// Kiosk: scan the badge, exchange it for tokens
const tokens = await client.identity.qrLogin('app_123', scannedPayload)
// Or verify without minting tokens
const user = await client.identity.verifyQr('app_123', scannedPayload)
console.log(user.firstName, user.role)Complete Example
import { AppClient } from '@hikigai/app-sdk'
const client = new AppClient()
async function clinicalWorkflow() {
const diagnosisAgent = await client.agent('diagnosis-assistant')
const codingAgent = await client.agent('clinical-coder')
const sessionId = 'patient-dr-smith-2026'
// Initial assessment
const assessment = await diagnosisAgent.invoke(
'Fever 38.5°C, cough, shortness of breath for 3 days',
{ sessionId }
)
console.log('Assessment:', assessment.content)
// Follow-up question - remembers context
const followUp = await diagnosisAgent.invoke(
'Any chest pain or sputum production?',
{ sessionId }
)
console.log('Follow-up:', followUp.content)
// Extract medical codes
const coding = await codingAgent.invoke(assessment.content)
console.log('Codes:', coding.content)
// Stream treatment plan
for await (const chunk of diagnosisAgent.stream(
'What is the recommended treatment?',
{ sessionId }
)) {
process.stdout.write(chunk)
}
}Error Handling
import {
HikigaiError,
AuthenticationError,
RateLimitError,
AgentNotFoundError,
InvocationError,
} from '@hikigai/app-sdk'
try {
const response = await agent.invoke('...')
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Invalid credentials')
} else if (error instanceof RateLimitError) {
console.error(`Rate limited. Retry after ${error.retryAfter}ms`)
} else if (error instanceof AgentNotFoundError) {
console.error('Agent not found')
} else if (error instanceof InvocationError) {
console.error(`Invocation failed: ${error.message}`)
} else if (error instanceof HikigaiError) {
console.error(`API error: ${error.message}`)
}
}