HealthCloud AMS SDK for NodeJS / TypeScript
Typed access to AMS Services: agent/department/team/skill/policy/guardrail CRUD, the data-source and tool catalogs, synthetic test-patient personas, conversation history, and the real-time WebSocket agent-chat runtime.
Installation
npm install @healthcloudai/hc-ams
Quick start
import { HCAmsSDK } from "@healthcloudai/hc-ams";
const sdk = new HCAmsSDK({ environment: "dev" });
const agents = await sdk.ams.getAgentsForTenant("<TENANT_ID>");
// { public: [...], orchestrator: [...] }
Authentication
ams.getAgentsForTenant is public and requires no access token.
Every other REST client (agents, departments, teams, skills,
policies, guardrails, dataSources, tools, testPersonas) requires a
Tenant Admin Bearer token, set via accessToken in the constructor or
sdk.setAccessToken(token). This SDK does not obtain that token itself —
get one from AMS's own sdk.auth.login(...) or from
@healthcloudai/hc-tenantadmin, and pass it in.
The WebSocket runtime (sdk.connectAgentChat(...) / WssClient) is
authenticated with a patient Health Cloud access token instead — obtain
one via sdk.testPersonas.login(credentialId) for testing, or from your
own patient auth flow in production.
REST clients
| Client | Resource | Notes |
|---|---|---|
sdk.ams |
Public application-agent discovery | No auth required |
sdk.agents |
Agent CRUD, transfer candidates, conversation history | create/update/delete may 405 if the tenant has disable_direct_agent_mutations enabled (see below) |
sdk.departments |
Department CRUD + activate/deactivate | |
sdk.teams |
Team CRUD | |
sdk.skills |
Skill CRUD | |
sdk.policies |
Policy CRUD | |
sdk.guardrails |
Guardrail CRUD | |
sdk.dataSources |
Data source catalog | Read-only — AMS exposes no create/update/delete |
sdk.tools |
Tool catalog | Read-only — AMS exposes no create/update/delete |
sdk.testPersonas |
Synthetic QA patient persona CRUD + login | login() mints a real Health Cloud patient token |
sdk.auth |
AMS's own Tenant Admin auth proxy (/api/auth/*) |
Resolves against a different base path than every other client |
const sdk = new HCAmsSDK({ environment: "dev", accessToken: tenantAdminToken });
const department = await sdk.departments.create({ name: "Cardiology", created_by_user_id: "admin-1" });
const agent = await sdk.agents.create({ name: "Cardiology Intake", department_id: department.id });
const history = await sdk.agents.getConversation(agent.id, `${tenantId}/${patientId}`);
Direct agent mutation
Some tenants run with disable_direct_agent_mutations enabled, in which
case AMS's plain POST/PUT/DELETE /agents routes respond 405 and real agent
authoring happens through the Vibe Coder plan/apply flow instead. That flow
is intentionally out of scope for this SDK — sdk.agents.create/update/delete
only cover the plain-CRUD path.
WebSocket agent-chat runtime
Since 3.5.1: the patient token is sent as the first WebSocket frame (
{"type":"auth","token":"..."}) right after the socket opens, never in the connect URL or as a WS subprotocol —chat.connect()doesn't resolve until the server repliesauth_ok. This is handled internally; you don't need to change any calling code that already passeshealthcloudTokenorgetAccessToken. It does require the AMS backend to be on the matching version (the corresponding server-side change is healthcloudservices/healthcloud-ams-services#280, merged via #288) — against an older backend,connect()will reject with an auth timeout instead of connecting.WssClientOptions.tokenTransport(0.0.1) is removed; if your code set it explicitly, delete that line.
const sdk = new HCAmsSDK({ environment: "dev", tenantId: "<TENANT_ID>", accessToken: tenantAdminToken });
// Mint a patient token for a synthetic test persona.
const session = await sdk.testPersonas.login(personaCredentialId);
// Connect and hold a multi-turn conversation with one agent.
const chat = sdk.connectAgentChat({
patientId: session.patient_id,
healthcloudToken: session.access_token,
agentId: "<AGENT_ID>", // omit to use the tenant's default agent
});
await chat.connect();
const turn = await chat.sendAndAwaitTurn("Hi, I'd like to schedule a check-up.");
console.log(turn.message, turn.transfers, turn.artifacts, turn.notifications, turn.activity);
chat.close();
Note: the HCAmsSDK instance's own accessToken (Tenant Admin) above
is only needed because this example uses testPersonas.login to mint a
synthetic test patient — a testing convenience, not a production
pattern. connectAgentChat/WssClient never read that accessToken;
they only ever use the healthcloudToken you pass them explicitly. A real
patient-facing app needs no Tenant Admin token at all — get the
patient token from your own patient auth (@healthcloudai/hc-sdk), e.g.:
import { HCSDK } from "@healthcloudai/hc-sdk";
import { HCAmsSDK } from "@healthcloudai/hc-ams";
const patientSdk = new HCSDK({ environment: "dev", tenantId: "<TENANT_ID>" });
const login = await patientSdk.auth.login({ email, password }); // real patient credentials
const amsSdk = new HCAmsSDK({ environment: "dev", tenantId: "<TENANT_ID>" }); // no accessToken needed
const chat = amsSdk.connectAgentChat({
patientId: login.fhir_patient_id,
healthcloudToken: login.access_token,
agentId: "<AGENT_ID>",
});
await chat.connect();
WssClient also exposes raw typed event subscription for finer-grained
control:
chat.on("turn_started", (e) => console.log("turn started", e.turn_id));
chat.on("chat", (e) => process.stdout.write(e.content));
chat.on("transfer", (e) => console.log("agent switched", e.to_agent_id));
The entire multi-agent conversation (orchestrator plus any number of
downstream specialist agents) lives on this one WebSocket connection —
agent switches arrive as transfer events, not reconnects.
For a reconnect (network drop, or your own periodic re-auth), pass
getAccessToken instead of a static healthcloudToken so a fresh token is
resolved on every connect() call rather than replaying a captured,
possibly-expired one:
const chat = sdk.connectAgentChat({
patientId,
getAccessToken: () => myAuthStore.getCurrentPatientToken(), // called on every connect()
agentId,
});
Guest chat before sign-in, then handoff to an authenticated conversation
For an agent configured with requires_auth: false (a public, pre-login
agent), no patient login or access token is needed. The connection still
requires a patientId to form its session ID; generate a random guest ID
for that — it is not a real FHIR patient ID, and the server never
resolves it against a real patient record for a requires_auth: false
agent.
const { public: publicAgents, orchestrator } = await amsSdk.ams.getAgentsForTenant(tenantId);
const guestId = crypto.randomUUID();
const guestChat = amsSdk.connectAgentChat({
patientId: guestId,
healthcloudToken: "", // no token needed for a requires_auth: false agent
agentId: publicAgents[0],
});
await guestChat.connect();
const guestTurn = await guestChat.sendAndAwaitTurn("What services are available?");
console.log(guestTurn.message);
Once the patient signs in, start a new connection with the real
patientId/healthcloudToken — and pass handoff with the guest
transcript so the orchestrator's first greeting can acknowledge it,
instead of the conversation starting as if nothing was said:
guestChat.close();
const login = await patientSdk.auth.login({ email, password });
const chat = amsSdk.connectAgentChat({
patientId: login.fhir_patient_id,
healthcloudToken: login.access_token,
agentId: orchestrator[0],
handoff: [
{ role: "user", content: "What services are available?" },
{ role: "assistant", content: guestTurn.message },
],
});
await chat.connect();
handoff is sent once, at connect time — the server reads it before
generating the first greeting, so the greeting itself can reference the
handed-off content (e.g. "I see you were asking about X — let's continue
from there"). It is not the same as sendHandoff(messages) (a
post-connect frame kept only for callers on the older contract): that one
can only silently append to history after the greeting has already gone
out.
On Node < 21 (no global WebSocket), pass a compatible implementation:
import WebSocket from "ws";
const chat = sdk.connectAgentChat({
patientId,
healthcloudToken,
WebSocketImpl: WebSocket as any,
});
See TEST_LIST.md for a full end-to-end flow: creating a
department/team/agents/test-persona, logging in as the persona, and
validating a multi-turn conversation's turns, transfers, artifacts,
notifications, and activity.
Configuration
| Option | Description |
|---|---|
environment |
"dev" | "uat" | "prod" — resolves the AMS base URL ({env-}amsservices.health.cloud/api/ams, no prefix for prod). |
tenantId |
Optional default tenant ID, used by connectAgentChat when no tenantId is passed per-call. |
accessToken |
Optional Tenant Admin bearer token for authenticated REST methods. |
baseUrlOverride |
Overrides the resolved AMS base URL (e.g. for local/staging testing). |
timeout |
Request timeout in milliseconds. Defaults to 30000. |