@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;" />
1 lines • 17.8 kB
Source Map (JSON)
{"version":3,"file":"open-generative-ui-middleware.mjs","names":[],"sources":["../../../src/v2/runtime/open-generative-ui-middleware.ts"],"sourcesContent":["import {\n Middleware,\n RunAgentInput,\n AbstractAgent,\n BaseEvent,\n EventType,\n ToolCallStartEvent,\n ToolCallArgsEvent,\n ActivitySnapshotEvent,\n ActivityDeltaEvent,\n} from \"@ag-ui/client\";\nimport { Observable } from \"rxjs\";\nimport clarinet from \"clarinet\";\n\nconst TOOL_NAME = \"generateSandboxedUi\";\nconst ACTIVITY_TYPE = \"open-generative-ui\";\n\n/**\n * Parsed parameters from the generateSandboxedUi tool call.\n */\nexport interface GenerateSandboxedUIParams {\n initialHeight?: number;\n placeholderMessages?: string[];\n css?: string;\n html?: string;\n jsFunctions?: string;\n jsExpressions?: string[];\n}\n\n/**\n * Callback invoked by ArgsParser whenever a parameter (or array item) finishes parsing.\n */\nexport type OnParamEvent = (event: BaseEvent) => void;\n\n/**\n * Tracks incremental JSON parsing state for a single tool call's arguments.\n * Emits activity events via the onEvent callback as parameters complete.\n */\nexport class ArgsParser {\n private parser: ReturnType<typeof clarinet.parser>;\n private currentKey: string | null = null;\n private depth = 0;\n private currentArrayKey: string | null = null;\n private snapshotEmitted = false;\n\n // Streaming html state — reads parser.textNode to emit incremental chunks\n private streamingHtmlKey = false;\n private htmlEmittedLength = 0;\n private htmlArrayEmitted = false;\n\n public readonly params: GenerateSandboxedUIParams = {};\n public readonly messageId: string;\n private readonly onEvent: OnParamEvent;\n\n constructor(toolCallId: string, onEvent: OnParamEvent) {\n this.messageId = `${toolCallId}-activity`;\n this.onEvent = onEvent;\n this.parser = clarinet.parser();\n\n this.parser.onopenobject = (key: string | undefined) => {\n this.depth++;\n if (key !== undefined && this.depth === 1) {\n this.currentKey = key;\n this.initHtmlStreaming(key);\n }\n };\n\n this.parser.onkey = (key: string) => {\n if (this.depth === 1) {\n this.currentKey = key;\n this.initHtmlStreaming(key);\n }\n };\n\n this.parser.onvalue = (value: string | boolean | number | null) => {\n if (this.depth === 1 && this.currentKey) {\n if (this.currentArrayKey) {\n const strValue = String(value);\n if (this.currentArrayKey === \"jsExpressions\") {\n if (!this.params.jsExpressions) this.params.jsExpressions = [];\n this.params.jsExpressions.push(strValue);\n } else if (this.currentArrayKey === \"placeholderMessages\") {\n if (!this.params.placeholderMessages)\n this.params.placeholderMessages = [];\n this.params.placeholderMessages.push(strValue);\n }\n this.emitArrayItemDelta(this.currentArrayKey, strValue);\n } else if (this.streamingHtmlKey) {\n // HTML string completed — flush any remaining content immediately + htmlComplete\n const fullHtml = value != null ? String(value) : \"\";\n this.params.html = fullHtml || undefined;\n this.emitPendingHtml(fullHtml);\n this.emitParamDelta(\"htmlComplete\", true);\n this.streamingHtmlKey = false;\n } else {\n this.setParam(this.currentKey, value);\n }\n }\n };\n\n this.parser.onopenarray = () => {\n if (this.depth === 1 && this.currentKey) {\n const key = this.currentKey;\n if (key === \"jsExpressions\" || key === \"placeholderMessages\") {\n this.currentArrayKey = key;\n if (key === \"jsExpressions\") this.params.jsExpressions = [];\n else this.params.placeholderMessages = [];\n // Emit a delta to create the array in the activity content.\n // Subsequent \"add\" ops with path \"/<key>/-\" append to this array.\n this.emitParamDelta(key, []);\n }\n }\n };\n\n this.parser.onclosearray = () => {\n if (this.depth === 1) {\n if (this.currentArrayKey === \"jsExpressions\") {\n this.emitParamDelta(\"jsExpressionsComplete\", true);\n }\n this.currentArrayKey = null;\n }\n };\n\n this.parser.oncloseobject = () => {\n this.depth--;\n };\n\n this.parser.onerror = (err: Error) => {\n console.warn(\n \"[OpenGenerativeUI] JSON parse error in streaming args, resuming:\",\n err?.message ?? err,\n );\n // Reset error state so parsing can continue with the next chunk\n this.parser.error = null;\n this.parser.resume();\n };\n }\n\n write(chunk: string): void {\n this.parser.write(chunk);\n this.flushHtmlChunks();\n }\n\n private initHtmlStreaming(key: string): void {\n if (key === \"html\") {\n this.streamingHtmlKey = true;\n this.htmlEmittedLength = 0;\n this.htmlArrayEmitted = false;\n }\n }\n\n /**\n * Read clarinet's internal textNode buffer to emit html chunks incrementally.\n * Called after every write() so partial string content is emitted as it streams in.\n */\n private flushHtmlChunks(): void {\n if (!this.streamingHtmlKey) return;\n const textNode = (this.parser as any).textNode;\n if (typeof textNode !== \"string\") return;\n if (textNode.length === this.htmlEmittedLength) return;\n\n this.emitPendingHtml(textNode);\n }\n\n /**\n * Emit accumulated html content since the last emission.\n * Called by flushHtmlChunks and directly when html completes.\n */\n private emitPendingHtml(textNode: string): void {\n const newContent = textNode.slice(this.htmlEmittedLength);\n if (newContent.length === 0) return;\n\n if (!this.htmlArrayEmitted) {\n this.htmlArrayEmitted = true;\n this.emitParamDelta(\"html\", []);\n }\n this.emitArrayItemDelta(\"html\", newContent);\n this.htmlEmittedLength = textNode.length;\n }\n\n private setParam(key: string, value: string | boolean | number | null): void {\n switch (key) {\n case \"initialHeight\":\n this.params.initialHeight =\n typeof value === \"number\" ? value : undefined;\n this.emitSnapshot();\n break;\n case \"css\":\n this.params.css = value != null ? String(value) : undefined;\n this.emitParamDelta(\"css\", this.params.css);\n this.emitParamDelta(\"cssComplete\", true);\n break;\n case \"jsFunctions\":\n this.params.jsFunctions = value != null ? String(value) : undefined;\n this.emitParamDelta(\"jsFunctions\", this.params.jsFunctions);\n this.emitParamDelta(\"jsFunctionsComplete\", true);\n break;\n }\n }\n\n private emitSnapshot(): void {\n if (this.snapshotEmitted) return;\n this.snapshotEmitted = true;\n\n const event: ActivitySnapshotEvent = {\n type: EventType.ACTIVITY_SNAPSHOT,\n messageId: this.messageId,\n activityType: ACTIVITY_TYPE,\n content: { initialHeight: this.params.initialHeight, generating: true },\n };\n this.onEvent(event);\n }\n\n private emitParamDelta(key: string, value: unknown): void {\n const event: ActivityDeltaEvent = {\n type: EventType.ACTIVITY_DELTA,\n messageId: this.messageId,\n activityType: ACTIVITY_TYPE,\n patch: [{ op: \"add\", path: `/${key}`, value }],\n };\n this.onEvent(event);\n }\n\n private emitArrayItemDelta(arrayKey: string, value: string): void {\n const event: ActivityDeltaEvent = {\n type: EventType.ACTIVITY_DELTA,\n messageId: this.messageId,\n activityType: ACTIVITY_TYPE,\n patch: [{ op: \"add\", path: `/${arrayKey}/-`, value }],\n };\n this.onEvent(event);\n }\n}\n\n/**\n * Extract EventWithState type from Middleware.runNextWithState return type\n */\ntype ExtractObservableType<T> = T extends Observable<infer U> ? U : never;\ntype RunNextWithStateReturn = ReturnType<Middleware[\"runNextWithState\"]>;\ntype EventWithState = ExtractObservableType<RunNextWithStateReturn>;\n\nexport class OpenGenerativeUIMiddleware extends Middleware {\n run(input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> {\n return this.processStream(this.runNextWithState(input, next));\n }\n\n private processStream(\n source: Observable<EventWithState>,\n ): Observable<BaseEvent> {\n return new Observable<BaseEvent>((subscriber) => {\n let heldRunFinished: EventWithState | null = null;\n // Track active generateSandboxedUi tool call IDs → their streaming parser\n const activeParsers = new Map<string, ArgsParser>();\n // Hold genui tool call events until the first activity event is emitted\n const heldToolCallEvents = new Map<string, BaseEvent[]>();\n const flushedToolCalls = new Set<string>();\n\n const flushHeldEvents = (toolCallId: string) => {\n if (flushedToolCalls.has(toolCallId)) return;\n flushedToolCalls.add(toolCallId);\n const held = heldToolCallEvents.get(toolCallId);\n if (held) {\n for (const e of held) {\n subscriber.next(e);\n }\n heldToolCallEvents.delete(toolCallId);\n }\n };\n\n const subscription = source.subscribe({\n next: (eventWithState) => {\n const event = eventWithState.event;\n\n if (heldRunFinished) {\n subscriber.next(heldRunFinished.event);\n heldRunFinished = null;\n }\n\n if (event.type === EventType.RUN_FINISHED) {\n heldRunFinished = eventWithState;\n return;\n }\n\n // Hold TOOL_CALL_START for genui until the first activity event\n if (event.type === EventType.TOOL_CALL_START) {\n const startEvent = event as ToolCallStartEvent;\n if (startEvent.toolCallName === TOOL_NAME) {\n heldToolCallEvents.set(startEvent.toolCallId, [event]);\n activeParsers.set(\n startEvent.toolCallId,\n new ArgsParser(startEvent.toolCallId, (activityEvent) => {\n subscriber.next(activityEvent);\n flushHeldEvents(startEvent.toolCallId);\n }),\n );\n return;\n }\n }\n\n // Hold or emit TOOL_CALL_ARGS for genui tool calls\n if (event.type === EventType.TOOL_CALL_ARGS) {\n const argsEvent = event as ToolCallArgsEvent;\n const parser = activeParsers.get(argsEvent.toolCallId);\n if (parser) {\n if (!flushedToolCalls.has(argsEvent.toolCallId)) {\n heldToolCallEvents.get(argsEvent.toolCallId)!.push(event);\n } else {\n subscriber.next(event);\n }\n parser.write(argsEvent.delta);\n return;\n }\n }\n\n // Hold or emit TOOL_CALL_END for genui tool calls\n if (event.type === EventType.TOOL_CALL_END) {\n const endEvent = event as { toolCallId: string } & BaseEvent;\n const parser = activeParsers.get(endEvent.toolCallId);\n if (parser) {\n // Mark generation complete\n const completeEvent: ActivityDeltaEvent = {\n type: EventType.ACTIVITY_DELTA,\n messageId: parser.messageId,\n activityType: ACTIVITY_TYPE,\n patch: [{ op: \"add\", path: \"/generating\", value: false }],\n };\n subscriber.next(completeEvent);\n\n if (!flushedToolCalls.has(endEvent.toolCallId)) {\n heldToolCallEvents.get(endEvent.toolCallId)!.push(event);\n } else {\n subscriber.next(event);\n }\n return;\n }\n }\n\n subscriber.next(event);\n },\n error: (err) => {\n // Flush any held tool call events so downstream sees them before the error\n for (const [, events] of heldToolCallEvents) {\n for (const event of events) {\n subscriber.next(event);\n }\n }\n heldToolCallEvents.clear();\n\n if (heldRunFinished) {\n subscriber.next(heldRunFinished.event);\n heldRunFinished = null;\n }\n subscriber.error(err);\n },\n complete: () => {\n // Flush any remaining held tool call events (e.g. parser never emitted)\n heldToolCallEvents.forEach((_, toolCallId) => {\n flushHeldEvents(toolCallId);\n });\n\n if (heldRunFinished) {\n subscriber.next(heldRunFinished.event);\n heldRunFinished = null;\n }\n activeParsers.clear();\n subscriber.complete();\n },\n });\n\n return () => subscription.unsubscribe();\n });\n }\n}\n"],"mappings":"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"}