Hikigai

@hikigai/agent-sdk

Agent SDK

Official Node.js SDK for deploying and managing healthcare AI agents on Hikigai. Full type safety with Zod validation and modern async/await patterns.

v0.2.11Node.js 18+Full TypeScriptZod SchemasJobs · Storage · Rooms · Events

Installation

Choose your preferred package manager:

npm

npm install @hikigai/agent-sdk

yarn

yarn add @hikigai/agent-sdk

pnpm

pnpm add @hikigai/agent-sdk

Quick Start

import { AgentClient, InputSchema, OutputSchema } from '@hikigai/agent-sdk'

const client = new AgentClient({
  apiKey: process.env.HIKIGAI_API_KEY,
  projectId: process.env.HIKIGAI_PROJECT_ID,
})

const agent = await client.deploy({
  name: 'medical-coder',
  displayName: 'Medical Coding Assistant',
  description: 'Extracts ICD-10 and CPT codes from clinical notes',
  instruction: 'You are a medical coding expert. Extract ICD-10 and CPT codes.',
  model: 'claude-3.5-sonnet',
  category: 'coding-billing',
  version: '1.0.0',
  inputSchema: new InputSchema({
    clinical_note: { type: 'string', required: true },
  }),
  outputSchema: new OutputSchema({
    icd_codes: { type: 'array', items: { type: 'string' } },
    cpt_codes: { type: 'array', items: { type: 'string' } },
  }),
})

console.log(`Deployed: ${agent.id} (${agent.slug})`)

// Dev helpers
const result = await client.invoke(agent.id, {
  clinical_note: 'Patient presents with fever and cough...',
})
console.log(result)

for await (const chunk of client.stream(agent.id, {
  clinical_note: 'Patient presents with fever and cough...',
})) {
  process.stdout.write(chunk)
}

Core Concepts

AgentClient

Main interface for agent deployment and management.

class AgentClient {
  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
  })

  readonly jobs: JobsClient
  readonly storage: StorageClient
  readonly events: EventsClient
  readonly identityVerification: IdentityVerificationClient

  deploy(config: AgentConfigInput, options?: {
    timeout?: number
    pollInterval?: number
    cloudService?: string            // optional; omit to use org default
    region?: string
    cloudCredentialId?: string
    cloudCredentialName?: string     // Cloud accounts name; omit for org default
  }): Promise<DeployedAgent>
  listAgents(): Promise<DeployedAgent[]>
  getAgent(agentId: string): Promise<DeployedAgent>
  updateAgent(agentId: string, update: Record<string, unknown>): Promise<DeployedAgent>
  deleteAgent(agentId: string): Promise<void>
  invoke(agentId: string, input: string | Record<string, unknown>, options?: {
    sessionId?: string; provider?: string; model?: string
  }): Promise<Record<string, unknown>>
  stream(agentId: string, input: string | Record<string, unknown>, options?: {
    sessionId?: string; provider?: string; model?: string
  }): AsyncIterable<string>
  getAuthToken(): Promise<AuthTokenResponse>
  close(): void
}

Jobs (client.jobs)

Background job queue for long-running agent work. Methods: enqueue, get, list, cancel, replay, wait, stream.

const job = await client.jobs.enqueue('agent.invoke', {
  input: 'Batch-code these notes',
}, { agentId: agent.id, priority: 'standard' })

const detail = await client.jobs.wait(job.id)
console.log(detail.status, detail.result)

// Also available:
// await client.jobs.get(job.id)
// await client.jobs.list({ status: 'failed', page: 1 })
// await client.jobs.cancel(job.id)
// await client.jobs.replay(job.id)
// for await (const event of client.jobs.stream(job.id)) { ... }

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, 'note.pdf', {
  contentType: 'application/pdf',
  metadata: { visitId: 'v-42' },
  ttl: 86400, // optional object TTL (seconds)
})
console.log(uploaded.objectId, uploaded.sizeBytes, 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, info.status)

const page = await client.storage.list('app_123', {
  contentType: 'application/pdf',
  status: 'active',
  limit: 20,
  page: 1,
})
for (const obj of page.objects) {
  console.log(obj.objectId, obj.originalFilename, obj.sizeBytes)
}
console.log(page.total, page.totalPages)

await client.storage.delete('app_123', uploaded.objectId)

Rooms (RoomSession)

Multi-party realtime rooms — create/join with join(name), then publish and listen. Optional WebRTC signalling via rtcJoin / rtcOffer / rtcAnswer. Standalone class (not on AgentClient). Same surface as Python / AppSDK.

import { RoomSession } from '@hikigai/agent-sdk'

const room = new RoomSession({
  baseUrl: process.env.HIKIGAI_BASE_URL ?? 'https://backend.hikigaiplatform.io',
  apiKey: process.env.HIKIGAI_API_KEY!,
  projectId: process.env.HIKIGAI_PROJECT_ID!,
})

await room.connect()
await room.join('rounds-42')  // creates the room if new
await room.publish('rounds-42', 'update', { status: 'in_progress' })
await room.who('rounds-42')

for await (const frame of room) {
  if (frame.type === 'message') console.log(frame.event, frame.data, frame.seq)
  if (frame.type === 'error') break
}

await room.close()

// WebRTC signalling (after join):
// await room.rtcJoin('call-1')
// await room.rtcOffer('call-1', peerId, sdp)
// await room.rtcAnswer('call-1', peerId, sdp)
// await room.rtcCandidate('call-1', peerId, candidate)

Configuration

AgentConfig

Comprehensive configuration for agent deployment (Zod-validated).

interface AgentConfig {
  // Identity
  name: string                    // 3-64 chars, lowercase, hyphens only
  displayName: string             // 3-100 chars
  description: string             // 10-500 chars
  longDescription?: string

  // Core
  agentType?: 'llm' | 'sequential' | 'parallel' | 'loop'  // Default: 'llm'
  instruction: string
  model: string                   // Default: 'claude-3.5-sonnet'

  // Schemas
  inputSchema: InputSchema | Record<string, unknown>
  outputSchema: OutputSchema | Record<string, unknown>

  // Modality
  inputModality?: string          // text | audio | combinations
  outputModality?: string
  vad?: VadConfig                 // Live automatic VAD (audio input only)

  // Tools & connectors (root connectors → every Sequential/Parallel/Loop leaf)
  tools?: unknown[]
  connectors?: ConnectorConfig[]  // see #mcp-subagents
  subAgents?: SubAgentConfig[]

  // Runtime
  timeout?: number                // Default: 60
  memoryMb?: number              // Default: 512
  minInstances?: number          // Default: 0
  maxInstances?: number          // Default: 10
  // Omit cloudProvider to use Organization settings → Cloud accounts (deploy default)
  cloudProvider?: string         // 'gcp' | 'aws'

  // Versioning & compliance
  version: string
  changelog?: string
  hipaaCompliant?: boolean       // Default: true
  category?: string
  tags?: string[]
}

VadConfig

Automatic voice activity detection for live audio agents. Pass when inputModality includes audio. Defaults: 800 ms / low / low. Include VAD on every same-name deploy() to preserve or change it.

interface VadConfig {
  silenceDurationMs?: number              // 200–2000, default 800
  endOfSpeechSensitivity?: 'high' | 'low' // default 'low'
  startOfSpeechSensitivity?: 'high' | 'low' // default 'low'
}

await client.deploy({
  ...,
  inputModality: 'audio',
  outputModality: 'text',
  vad: {
    silenceDurationMs: 800,
    endOfSpeechSensitivity: 'low',
    startOfSpeechSensitivity: 'low',
  },
})

Input / Output Schemas

Field types: string, integer, number, boolean, array, object. Optional description, required, enum, items, properties.

import { InputSchema, OutputSchema } from '@hikigai/agent-sdk'

const inputSchema = new InputSchema({
  patient_id: { type: 'string', required: true },
  age: { type: 'integer', required: true, minimum: 0, maximum: 150 },
  symptoms: { type: 'array', items: { type: 'string' } },
})

const outputSchema = new OutputSchema({
  diagnosis: { type: 'string' },
  confidence: { type: 'integer', minimum: 0, maximum: 100 },
  recommendations: { type: 'array', items: { type: 'string' } },
})

Workflow Agents (Sequential / Parallel / Loop)

Set agentType to 'sequential', 'parallel', or 'loop' with nested subAgents. Text workflows advance on their own. Live pipelines need liveHandoff on leaves. Open live sessions with AppSDK live().

TypeBehaviorliveHandoff
sequentialLeaves one after anotherPer-leaf task_completed or after_output
parallelConcurrent branches (mic teed)Same; prefer text-only outs
loopRepeat until escalate / maxIterationsSame as sequential

SubAgentConfig

One leaf (or nested workflow) under a Sequential / Parallel / Loop root.

interface SubAgentConfig {
  name: string
  agentType?: 'llm' | 'sequential' | 'parallel' | 'loop'
  model?: string
  instruction?: string
  description?: string
  tools?: string[]
  inputModality?: string          // default 'text'
  outputModality?: string         // default 'text'
  liveHandoff?: 'task_completed' | 'after_output'  // default 'task_completed'
  subAgents?: SubAgentConfig[]    // nested workflows
  maxIterations?: number          // loop only
  source?: 'marketplace'          // reuse marketplace agent
  sourceAgentId?: string
}

liveHandoff: task_completed (default, multi-turn) or after_output (one-shot after model reply). Ignored for simple live LlmAgent roots.

await client.deploy({
  name: 'clinical-live-pipeline',
  displayName: 'Clinical Live Pipeline',
  description: 'Sequential live intake with one-shot leaves',
  agentType: 'sequential',
  model: 'gemini-2.5-flash-native-audio-preview-12-2025',
  instruction: 'Orchestrate the live clinical pipeline.',
  version: '1.0.0',
  inputModality: 'audio',
  outputModality: 'audio',
  inputSchema: { type: 'object', properties: { audio: { type: 'string' } } },
  outputSchema: { type: 'object', properties: { note: { type: 'string' } } },
  subAgents: [
    {
      name: 'voice-intake',
      model: 'gemini-2.5-flash-native-audio-preview-12-2025',
      instruction: 'Wait for the user, then acknowledge in under 8 words.',
      inputModality: 'audio',
      outputModality: 'audio',
      liveHandoff: 'after_output',
    },
    {
      name: 'note-composer',
      model: 'gemini-2.5-flash-native-audio-preview-12-2025',
      instruction: 'Emit a short SOAP note from context, then stop.',
      inputModality: 'text',
      outputModality: 'text',
      liveHandoff: 'after_output',
    },
  ],
})

Reuse marketplace agents as sub-agents

Pass instruction / model for your own leaves. Set source: 'marketplace' with the marketplace agent name (or slug) to reuse it — no UUID, instruction, or schemas. The platform hydrates those at deploy time. Optionally pass sourceAgentId instead.

await client.deploy({
  name: 'cpt2-note-workflow',
  displayName: 'CPT2 Note Workflow',
  description: 'Clean a clinical note, then reuse CPT2 from the marketplace',
  agentType: 'sequential',
  model: 'gemini-2.5-flash',
  instruction: 'Pass the note through prep, then CPT Category II extraction.',
  version: '1.0.0',
  category: 'coding-billing',
  inputSchema: { type: 'object', properties: { input: { type: 'string' } }, required: ['input'] },
  outputSchema: { type: 'object', properties: { output: { type: 'string' } }, required: ['output'] },
  subAgents: [
    {
      name: 'note_cleaner',
      model: 'gemini-2.5-flash',
      instruction: 'Clean and structure the note for CPT II coding. Do not invent facts.',
      description: 'Prepares notes for coding',
    },
    {
      name: 'cpt2_code_agent', // marketplace name or slug
      source: 'marketplace',
    },
  ],
})
// Invoke with App SDK: client.agent('cpt2-note-workflow').invoke({ input: '...' })

MCP on Sub-Agents

Declare connectors on the root agent config. They serialize as mcp_connectors and at invoke/live attach to every LlmAgent leaf (Sequential / Parallel / Loop) — not only a simple LLM root. Prefer declaring at deploy so leaf outputSchema is not baked (ADK skips CallTool when schema is set).

await client.deploy({
  name: 'vitals-fetch-subagent',
  displayName: 'Vitals Fetch (Subagent MCP)',
  description: 'Sequential: MCP fetch leaf, then summarizer',
  instruction: 'Run fetcher then summarizer. Do not answer yourself.',
  model: 'gemini-2.5-flash',
  agentType: 'sequential',
  category: 'monitoring',
  version: '1.0.0',
  // Root connectors → every leaf can CallTool
  connectors: [
    {
      slug: 'idexx-mcp-server',
      toolFilter: ['get_vitals_for_today'],
      required: true,
    },
  ],
  subAgents: [
    {
      name: 'vitals-fetcher',
      model: 'gemini-2.5-flash',
      instruction: 'Call get_vitals_for_today once with site_id and patient_id.',
    },
    {
      name: 'vitals-summarizer',
      model: 'gemini-2.5-flash',
      instruction: 'Emit {"response": "<summary>"}. Do not call tools.',
    },
  ],
  inputSchema: /* site_id, patient_id */,
  outputSchema: /* response */,
})

// Invoke with AppSDK — see /docs/sdk/node-appsdk#mcp-workflow-agents

Next: Node AppSDK → MCP on Workflow Agents.

Tools

Add tools to your agents: functions, OpenAPI specs, MCP, or built-ins.

Function Tools

import { tool } from '@hikigai/agent-sdk'

const searchDatabase = tool(function searchDatabase(query: string): string {
  return JSON.stringify(results)
})

await client.deploy({
  // ...
  tools: [searchDatabase],
})

OpenAPI Tools

import { OpenAPITool } from '@hikigai/agent-sdk'

const weatherApi = new OpenAPITool({
  specUrl: 'https://api.weather.com/openapi.json',
  operationId: 'getCurrentWeather',
})

MCP Tools

Static MCPTool entries, or prefer root connectors for project MCP (see MCP on Sub-Agents).

import { MCPTool } from '@hikigai/agent-sdk'

const ehrLookup = new MCPTool('epic-ehr', 'get_patient')
// or: new MCPTool({ serverName: 'epic-ehr', toolName: 'get_patient' })

Built-in Tools

await client.deploy({
  // ...
  tools: ['web_search', 'execute_code', 'read_file'],
})

DeployedAgent

interface DeployedAgent {
  id: string
  name: string
  slug: string
  version: string
  displayName?: string
  description?: string
  deploymentStatus: 'active' | 'pending' | 'error'
  deploymentType: 'config_based' | 'adk' | 'file'
  endpointUrl?: string
  cloudProvider?: string
  region?: string
  hipaaCompliant: boolean
  hipaaVerified: boolean
  createdAt?: Date
  deployedAt?: Date
}

List / Get / Update / Delete

const agents = await client.listAgents()
const agent = await client.getAgent('agent-id-or-slug')
await client.updateAgent(agent.id, { description: 'Updated description' })
await client.deleteAgent(agent.id)

// Dev helpers
const result = await client.invoke(agent.id, 'Hello')
for await (const chunk of client.stream(agent.id, 'Summarize')) {
  process.stdout.write(chunk)
}

Multi-Cloud Deploy

Whatever you set as the default under Organization settings → Cloud accounts is used when you deploy. Omit cloud options to use that default. Pass cloudProvider: 'gcp' or 'aws' only when you need to override it. Personal workspaces with no cloud account fall back to Hikigai's platform cloud; team orgs need a default account (or an explicit override) before deploy.

// Uses Organization settings → Cloud accounts default
const agent = await client.deploy(config)

// Override provider when needed
await client.deploy({ ...config, cloudProvider: 'aws' })

// Or pick a specific account by name
await client.deploy(config, {
  cloudCredentialName: 'my-aws-account',
})

Catalog / BYOC validation: Node AppSDK → Multi-Cloud.

Events & Webhooks

client.events — HMAC-signed webhooks (at-least-once) and a live event stream (at-most-once). React to agent.deployed / agent.deleted / job.* instead of polling.

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

// Also: listWebhooks, getWebhook, updateWebhook, deleteWebhook, rotateSecret

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()

Complete Examples

import {
  AgentClient,
  InputSchema,
  OutputSchema,
  tool,
} from '@hikigai/agent-sdk'

const client = new AgentClient()

// Define tools
const searchCodes = tool(function search(q: string) {
  return JSON.stringify([{ code: 'E11.9', desc: 'Type 2 diabetes' }])
})

// Deploy agent
const agent = await client.deploy({
  name: 'icd-cpt-coder',
  displayName: 'ICD-10 & CPT Coder',
  description: 'Medical coding assistant',
  instruction: 'You are a medical coding expert...',
  model: 'claude-3.5-sonnet',
  category: 'coding-billing',
  version: '1.0.0',
  inputSchema: new InputSchema({
    clinical_note: { type: 'string', required: true },
  }),
  outputSchema: new OutputSchema({
    icd10_codes: { type: 'array' },
    cpt_codes: { type: 'array' },
  }),
  tools: [searchCodes],
  hipaaCompliant: true,
})

console.log(`Deployed: ${agent.slug}`)

Error Handling

import {
  HikigaiError,
  AuthenticationError,
  RateLimitError,
  ValidationError,
  DeploymentError,
} from '@hikigai/agent-sdk'

try {
  const agent = await client.deploy(config)
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid API key')
  } else if (error instanceof RateLimitError) {
    console.error(`Rate limited. Retry after ${error.retryAfter}ms`)
  } else if (error instanceof ValidationError) {
    console.error(`Validation error: ${error.message}`)
  } else if (error instanceof HikigaiError) {
    console.error(`API error: ${error.message}`)
  }
}