{"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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"}