Menu
Public repository / 91bd2a37654909c5a3aef6ddd614991bd378ccb8
AkurAI-Framework / site/frontend/ai/runtime/index.js
Latest change 22a320a8e40a1822b386e2d5fd9bcf3537282876 - feat(ai): generative-UI / tool-call runtime + primitives by Ólafur Búi Ólafsson
// index.js — public entry point for the generative-UI / tool-call runtime.
//
// Re-exports the whole runtime API and wires the bridge to the chat core: it
// listens for `chat:toolcall` DOM events and renders the matched component into
// the message element. Importing this module is enough to activate the bridge.
//
// ── chat:toolcall CONTRACT ───────────────────────────────────────────────────
//
// document.dispatchEvent(new CustomEvent("chat:toolcall", { detail: {
// payload, // the tool-call payload object to render
// target, // (optional) HTMLElement to append the rendered node into
// ctx, // (optional) partial ctx; merged over the default ctx
// toolCallId, // (optional) threaded into receipts
// }}))
//
// On each event the runtime renders via renderToolPayload(payload, ctx) and:
// - appends the node to detail.target if present, and
// - dispatches `toolui:rendered` (detail = { element, payload, toolCallId }).
//
// The default ctx routes sendReceipt through receipts.js, so any interactive
// component reaches the assistant's tool-result channel out of the box.
import registry, {
registerToolComponent,
registerAll,
renderToolPayload,
getRegistered,
clearRegistry,
normalizeCtx,
} from "./registry.js";
import { sendReceipt, makeReceiptCtx, RECEIPT_ENDPOINT } from "./receipts.js";
import { renderApproval, genericApprovalComponent } from "./approvals.js";
import { createThreadList } from "./threadlist.js";
import { createBranchPicker } from "./branch.js";
import { createReasoning } from "./reasoning.js";
import { createSuggestions } from "./suggestions.js";
// The default ctx — receipts wired to the assistant tool-result channel.
function defaultCtx(extra) {
return normalizeCtx({
sendReceipt,
onAction(name, data) {
console.log("[runtime] action:", name, data);
},
theme: "dark",
...(extra || {}),
});
}
// Render a tool payload with the runtime default ctx (sendReceipt wired).
function render(payload, extraCtx) {
return renderToolPayload(payload, defaultCtx(extraCtx));
}
// The bridge handler. Exported so it can be re-bound or tested in isolation.
function handleToolCall(event) {
const detail = (event && event.detail) || {};
const ctx = defaultCtx({
...(detail.ctx || {}),
sendReceipt: (obj) =>
sendReceipt({ toolCallId: detail.toolCallId, ...obj }),
});
const el = renderToolPayload(detail.payload, ctx);
if (detail.target && typeof detail.target.appendChild === "function") {
detail.target.appendChild(el);
}
document.dispatchEvent(
new CustomEvent("toolui:rendered", {
detail: { element: el, payload: detail.payload, toolCallId: detail.toolCallId },
})
);
return el;
}
// Activate the chat-core bridge exactly once.
let bridged = false;
function activate() {
if (bridged || typeof document === "undefined") return;
bridged = true;
document.addEventListener("chat:toolcall", handleToolCall);
console.log("[runtime] chat:toolcall bridge active");
}
activate();
const ToolUiRuntime = {
// registry / dispatch
registerToolComponent,
registerAll,
renderToolPayload,
render,
getRegistered,
clearRegistry,
normalizeCtx,
// receipts
sendReceipt,
makeReceiptCtx,
RECEIPT_ENDPOINT,
// approvals
renderApproval,
genericApprovalComponent,
// primitives
createThreadList,
createBranchPicker,
createReasoning,
createSuggestions,
// bridge
handleToolCall,
activate,
registry,
version: "1.0.0",
};
if (typeof window !== "undefined") window.ToolUiRuntime = ToolUiRuntime;
export {
registerToolComponent,
registerAll,
renderToolPayload,
render,
getRegistered,
clearRegistry,
normalizeCtx,
sendReceipt,
makeReceiptCtx,
RECEIPT_ENDPOINT,
renderApproval,
genericApprovalComponent,
createThreadList,
createBranchPicker,
createReasoning,
createSuggestions,
handleToolCall,
activate,
};
export default ToolUiRuntime;