@antsomicorp/henos-embed-sdk
v0.1.0
Published
A framework-independent browser ESM package for mounting the real Henos chat in an iframe. No build step or runtime dependencies are needed. TypeScript declarations ship alongside the JavaScript.
Readme
Henos Embed SDK
A framework-independent browser ESM package for mounting the real Henos chat in an iframe. No build step or runtime dependencies are needed. TypeScript declarations ship alongside the JavaScript.
Install from this checkout with a package-manager file dependency:
{
"dependencies": {
"@henos/embed-sdk": "file:../deer-flow/packages/henos-embed-sdk"
}
}Mount plain chat in an element with a defined height:
import { mountHenosChat } from "@henos/embed-sdk";
const chat = mountHenosChat({
container: document.querySelector("#chat"),
url: "https://henos.example/embed/chat",
});
await chat.sendPrompt("Hello");
// On component unmount:
chat.destroy();Henos must permit the host application's exact origin through its operator configuration. Users sign in through Henos; do not pass server credentials to the SDK. The SDK adds the host's parentOrigin URL parameter automatically.
Context and host actions
Both callbacks are optional. Each chat submission requests fresh context through getContext({prompt}). Without that callback, context defaults to null, or to the latest JSON snapshot passed to setContext. When supplied, the callback is authoritative, including an intentional null return.
const chat = mountHenosChat({
container: document.querySelector("#chat"),
url: "https://henos.example/embed/chat",
getContext: async ({ prompt }) => ({
selectedItem: getCurrentSelection(),
revision: getCurrentRevision(),
}),
onAction: async ({ actionId, threadId, runId, context, action }) => {
// Validate the application's action schema, permissions and context revision.
// Return saved only after persistence has succeeded.
return { status: "rejected", message: "This action is not supported." };
},
onEvent: (event) => console.log(event),
});
await chat.setContext({ selectedItem: "example" });onAction returns {status, message, revision?}. Status is saved, applied, rejected, or error. The SDK does not interpret application action schemas. Without a handler, actions receive rejected. Repeated action IDs share a single result for the lifetime of a mount, including iframe reconnects; reusing an ID with different content is rejected. The host must still enforce its own persistent idempotency and concurrency checks.
setContext and sendPrompt return promises that resolve when their messages are posted after readiness. They do not acknowledge a completed model run or host mutation. destroy() removes the iframe and listeners, closes the channel, rejects pending commands, and suppresses late replies. It is safe to call repeatedly.
timeoutMs defaults to 30,000 and bounds readiness and host callback waits. A timeout or teardown cannot undo an application callback that has already started. An action that times out stays deduplicated; check the host state before any deliberate retry using a new ID. JSON payloads are bounded to 1,000,000 serialized characters and depth 64; functions, non-finite numbers, circular references, and non-JSON objects are rejected.
Protocol v1
All messages use {protocol:'henos-embed', version:1, type, id?, replyTo?, payload?}. The parent transfers a fresh MessagePort to the iframe's exact HTTP(S) origin with henos:init and {sessionId}. The child must validate the parent origin and source before accepting the port. On client initialization the child may send henos:hello; the SDK checks its exact origin and iframe source and repeats initialization, covering delayed hydration. The child acknowledges with ready on the port.
| Direction | Type | Payload / correlation |
| -------------- | ---------------- | ------------------------------------------------------------- |
| Child → parent | prepare | {prompt} with id |
| Parent → child | prepare | {context} or {error:{code,message}}, with replyTo |
| Child → parent | action.request | {actionId,threadId,runId,context,action} with id |
| Parent → child | action.result | {status,message,revision?} with replyTo |
| Parent → child | prompt.send | {prompt} |
| Parent → child | context.update | {context}; informational, preparation remains authoritative |
| Child → parent | event | {name,data?}; named application events only |
onEvent receives {type:'ready'}, {type:'error',payload:{code,message}}, or {type:'event',payload:{name,data?}}. Internal chat stream messages are not forwarded. Unknown protocol versions, unrecognized envelope types, and malformed requests are ignored. After reconnect, messages from the old port and late results are ignored.
Run transport tests with node --test packages/henos-embed-sdk/test/sdk.test.js from the repository root. These use simulated iframe/channel primitives and exercise the actual SDK; they do not substitute for browser authentication or a live model integration test.
sendPrompt() resolves when the prompt is sent to the frame. If chat is busy or loading history, onEvent receives an event with payload.name = "prompt.rejected"; the host can retry after the active run finishes.
Optional close control
Set showClose: true to show a close button in the embedded header (default:
false). Handle {type: "event", payload: {name: "close.requested", data: {}}}
in onEvent to hide the host panel. This is a local message through the existing
channel, with no API round trip. The SDK does not hide or destroy the iframe.
To preserve the draft and connection when reopening, keep the mount alive and
hide its container; call destroy() only when disposing of the integration.
The host can omit its duplicate title/close header when using this control.
onCloseRequest() handles the close button without parsing generic events.
Supplying onDisplayModeRequest(mode) enables the inline/popup toggle. The host
applies its layout, then calls chat.setDisplayMode(mode) to confirm the actual
mode (inline or popup). displayMode sets the initial mode. Confirmed state is
resent on iframe reconnect; requests alone never change the icon. These callbacks
also retain the existing generic onEvent notifications.
Completed embedded runs emit tool.result events with
{toolCallId, toolName, structuredContent, threadId, runId} from actual current-turn
MCP artifacts. Host apps validate their contract and scope before refreshing;
assistant text is not parsed. Reopening history does not replay these events.
This is a notification after the run, not a durable delivery queue or a second write.
