UNPKG

@copilotkit/runtime

Version:

<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />

280 lines (278 loc) • 8.71 kB
import "reflect-metadata"; import { Observable } from "rxjs"; import { EventType, Middleware } from "@ag-ui/client"; import clarinet from "clarinet"; //#region src/v2/runtime/open-generative-ui-middleware.ts const TOOL_NAME = "generateSandboxedUi"; const ACTIVITY_TYPE = "open-generative-ui"; /** * Tracks incremental JSON parsing state for a single tool call's arguments. * Emits activity events via the onEvent callback as parameters complete. */ var ArgsParser = class { constructor(toolCallId, onEvent) { this.currentKey = null; this.depth = 0; this.currentArrayKey = null; this.snapshotEmitted = false; this.streamingHtmlKey = false; this.htmlEmittedLength = 0; this.htmlArrayEmitted = false; this.params = {}; this.messageId = `${toolCallId}-activity`; this.onEvent = onEvent; this.parser = clarinet.parser(); this.parser.onopenobject = (key) => { this.depth++; if (key !== void 0 && this.depth === 1) { this.currentKey = key; this.initHtmlStreaming(key); } }; this.parser.onkey = (key) => { if (this.depth === 1) { this.currentKey = key; this.initHtmlStreaming(key); } }; this.parser.onvalue = (value) => { if (this.depth === 1 && this.currentKey) if (this.currentArrayKey) { const strValue = String(value); if (this.currentArrayKey === "jsExpressions") { if (!this.params.jsExpressions) this.params.jsExpressions = []; this.params.jsExpressions.push(strValue); } else if (this.currentArrayKey === "placeholderMessages") { if (!this.params.placeholderMessages) this.params.placeholderMessages = []; this.params.placeholderMessages.push(strValue); } this.emitArrayItemDelta(this.currentArrayKey, strValue); } else if (this.streamingHtmlKey) { const fullHtml = value != null ? String(value) : ""; this.params.html = fullHtml || void 0; this.emitPendingHtml(fullHtml); this.emitParamDelta("htmlComplete", true); this.streamingHtmlKey = false; } else this.setParam(this.currentKey, value); }; this.parser.onopenarray = () => { if (this.depth === 1 && this.currentKey) { const key = this.currentKey; if (key === "jsExpressions" || key === "placeholderMessages") { this.currentArrayKey = key; if (key === "jsExpressions") this.params.jsExpressions = []; else this.params.placeholderMessages = []; this.emitParamDelta(key, []); } } }; this.parser.onclosearray = () => { if (this.depth === 1) { if (this.currentArrayKey === "jsExpressions") this.emitParamDelta("jsExpressionsComplete", true); this.currentArrayKey = null; } }; this.parser.oncloseobject = () => { this.depth--; }; this.parser.onerror = (err) => { console.warn("[OpenGenerativeUI] JSON parse error in streaming args, resuming:", err?.message ?? err); this.parser.error = null; this.parser.resume(); }; } write(chunk) { this.parser.write(chunk); this.flushHtmlChunks(); } initHtmlStreaming(key) { if (key === "html") { this.streamingHtmlKey = true; this.htmlEmittedLength = 0; this.htmlArrayEmitted = false; } } /** * Read clarinet's internal textNode buffer to emit html chunks incrementally. * Called after every write() so partial string content is emitted as it streams in. */ flushHtmlChunks() { if (!this.streamingHtmlKey) return; const textNode = this.parser.textNode; if (typeof textNode !== "string") return; if (textNode.length === this.htmlEmittedLength) return; this.emitPendingHtml(textNode); } /** * Emit accumulated html content since the last emission. * Called by flushHtmlChunks and directly when html completes. */ emitPendingHtml(textNode) { const newContent = textNode.slice(this.htmlEmittedLength); if (newContent.length === 0) return; if (!this.htmlArrayEmitted) { this.htmlArrayEmitted = true; this.emitParamDelta("html", []); } this.emitArrayItemDelta("html", newContent); this.htmlEmittedLength = textNode.length; } setParam(key, value) { switch (key) { case "initialHeight": this.params.initialHeight = typeof value === "number" ? value : void 0; this.emitSnapshot(); break; case "css": this.params.css = value != null ? String(value) : void 0; this.emitParamDelta("css", this.params.css); this.emitParamDelta("cssComplete", true); break; case "jsFunctions": this.params.jsFunctions = value != null ? String(value) : void 0; this.emitParamDelta("jsFunctions", this.params.jsFunctions); this.emitParamDelta("jsFunctionsComplete", true); break; } } emitSnapshot() { if (this.snapshotEmitted) return; this.snapshotEmitted = true; const event = { type: EventType.ACTIVITY_SNAPSHOT, messageId: this.messageId, activityType: ACTIVITY_TYPE, content: { initialHeight: this.params.initialHeight, generating: true } }; this.onEvent(event); } emitParamDelta(key, value) { const event = { type: EventType.ACTIVITY_DELTA, messageId: this.messageId, activityType: ACTIVITY_TYPE, patch: [{ op: "add", path: `/${key}`, value }] }; this.onEvent(event); } emitArrayItemDelta(arrayKey, value) { const event = { type: EventType.ACTIVITY_DELTA, messageId: this.messageId, activityType: ACTIVITY_TYPE, patch: [{ op: "add", path: `/${arrayKey}/-`, value }] }; this.onEvent(event); } }; var OpenGenerativeUIMiddleware = class extends Middleware { run(input, next) { return this.processStream(this.runNextWithState(input, next)); } processStream(source) { return new Observable((subscriber) => { let heldRunFinished = null; const activeParsers = /* @__PURE__ */ new Map(); const heldToolCallEvents = /* @__PURE__ */ new Map(); const flushedToolCalls = /* @__PURE__ */ new Set(); const flushHeldEvents = (toolCallId) => { if (flushedToolCalls.has(toolCallId)) return; flushedToolCalls.add(toolCallId); const held = heldToolCallEvents.get(toolCallId); if (held) { for (const e of held) subscriber.next(e); heldToolCallEvents.delete(toolCallId); } }; const subscription = source.subscribe({ next: (eventWithState) => { const event = eventWithState.event; if (heldRunFinished) { subscriber.next(heldRunFinished.event); heldRunFinished = null; } if (event.type === EventType.RUN_FINISHED) { heldRunFinished = eventWithState; return; } if (event.type === EventType.TOOL_CALL_START) { const startEvent = event; if (startEvent.toolCallName === TOOL_NAME) { heldToolCallEvents.set(startEvent.toolCallId, [event]); activeParsers.set(startEvent.toolCallId, new ArgsParser(startEvent.toolCallId, (activityEvent) => { subscriber.next(activityEvent); flushHeldEvents(startEvent.toolCallId); })); return; } } if (event.type === EventType.TOOL_CALL_ARGS) { const argsEvent = event; const parser = activeParsers.get(argsEvent.toolCallId); if (parser) { if (!flushedToolCalls.has(argsEvent.toolCallId)) heldToolCallEvents.get(argsEvent.toolCallId).push(event); else subscriber.next(event); parser.write(argsEvent.delta); return; } } if (event.type === EventType.TOOL_CALL_END) { const endEvent = event; const parser = activeParsers.get(endEvent.toolCallId); if (parser) { const completeEvent = { type: EventType.ACTIVITY_DELTA, messageId: parser.messageId, activityType: ACTIVITY_TYPE, patch: [{ op: "add", path: "/generating", value: false }] }; subscriber.next(completeEvent); if (!flushedToolCalls.has(endEvent.toolCallId)) heldToolCallEvents.get(endEvent.toolCallId).push(event); else subscriber.next(event); return; } } subscriber.next(event); }, error: (err) => { for (const [, events] of heldToolCallEvents) for (const event of events) subscriber.next(event); heldToolCallEvents.clear(); if (heldRunFinished) { subscriber.next(heldRunFinished.event); heldRunFinished = null; } subscriber.error(err); }, complete: () => { heldToolCallEvents.forEach((_, toolCallId) => { flushHeldEvents(toolCallId); }); if (heldRunFinished) { subscriber.next(heldRunFinished.event); heldRunFinished = null; } activeParsers.clear(); subscriber.complete(); } }); return () => subscription.unsubscribe(); }); } }; //#endregion export { OpenGenerativeUIMiddleware }; //# sourceMappingURL=open-generative-ui-middleware.mjs.map