Transcription: OpenAgents Episode 133 - HTMX Server Sent Events, Part 2
June 9, 2026 ยท View on GitHub
Source: https://x.com/OpenAgentsInc/status/1847028603969560599 Wiki source: https://raw.githubusercontent.com/wiki/OpenAgentsInc/openagents/Video-Series.md Media title: OpenAgents - Episode 133: HTMX Server Sent Events, Part 2 We add streaming to our... Upload date: 20241017 Transcription model: gpt-4o-mini-transcribe Generated at: 2026-06-01T15:49:47Z
Machine-generated transcript. Review speaker labels and wording before using this as quote-grade source material.
[00:00] Speaker: Let's add streaming here. How do we add streaming? Well, back in episode 52, we did HTMX server-sent events. After that, we kind of pivoted to Livewire and Inertia and then Vercel SDK and all these other things, all of which were using basically server-sent events under the hood. But for example, like our V2 here, like this uses server-sent events via the Vercel AI SDK to stream the response. But if we're doing this very simple tech stack of Laravel Blade, Tailwind, just basic primitives taking that as far as it goes, for streaming, maybe we can implement server-sent events ourselves. So we did a little bit of that exploration back in episode 52. And let's just try to get some at least demo stream hooked up to this chat interface in this episode. Fortunately, we also have the ability now to drop docs like this into open agents and have it just like, just like code for us. We've got a fresh branch, SSE, that's pushed up to GitHub, not there, over here. I think we've got a branch called SSE. Yeah. Okay, so let's drop the HTMX link into, that's V1, I don't want V1. Give me the new stuff. WebSockets and SSE. SSE extension. Drop this into, where did it go? Okay, scrape this and summarize in one sentence. I guess I should actually give it the URL. There you go. And what other context can I give it here? Let's maybe update our test results so our auth tests are not passing. That's okay. Just grab this, throw that into there, just so we can see what functionality we have. And I'll say, now read this file and go look at, where do we manage the chat interface? Main content, message form. Yeah. And that goes to send message. This routes web. Make my face smaller. And any controllers needed to understand how we implement chat. I want to change it so our chat UI streams a demo response via HTMX and SSE and Laravel streaming response. So go do it. Okay, so I know that we're going to need to install HTMX, so let's do that now. Gimme, gimme, gimme. Put that in layouts and we'll vendor it later. I don't remember if we need to pull in, yep, a separate script. Now that we have examined the relevant code, let's implement the changes to use HTMX, SSE, and Laravel streaming responses for the chat UI. We'll need to modify the following files. Message form, routes, message controller. Let's start with the changes. Message form, call me right file. Is it going to go try and install HTMX without me? Blah, blah, blah. I don't need your words. Just like do it. HTMX SSE script and get pull. And just like show me it doing something. I'm curious to look at the network tab. What do? Whoa, something happened. OK. All right, something is happening twice, three times. OK, so I wanted to actually maybe put something in the message list here. So look at, boom, I want content streamed into messages like that. And it just keeps hitting the SSE demo endpoint every few seconds and giving this. But make it only stream one set of messages per user message. OK, now let's take a look at what it actually did. Modified the form to use HTMX for asynchronous submission. Added a new div with ID chat messages that uses the SSE extension to connect to the demo SSE endpoint. Updated browse web. Added a new route. Updated message controller. Modified the send message method to return a JSON response. With the rendered message HTML. Added a new SSE demo. Yeah, that doesn't seem right. Added a new SSE demo method that demonstrates how to use server-sent events with Laravel's stream response. Created a new partial view. Updated the layout. Now that we've seen the structure of the main content, let's modify our implementation to stream content into the messages area and only stream one set of messages per user message. We'll need to make changes to the following files. Blah, blah, blah, blah, blah, blah. Hmm. So we may need to learn about like HTMX headers and stuff. I know that there's a library called Laravel HTMX that does something. But this might just be helpers. We may just need to like learn about the headers. Because I think we should be returning HTML, not JSON. But let's see what it does and then we'll just yell at it and badger it and then it should eventually fix itself. Let's see what happens. Conflict in layout. That's funny. No, I will not accept your change. OK, what does that do? Oh, no, no. What the fuck is that? What's that? I mean, it clearly, I fucked up a rebase or something. Test. I can't understand what that is. You overwrote my layout. I reverted that. Look at my files before editing them. Oh, there we go. OK. Right now I see two containers with messages in it. Fix it. And what do I see happen when I do a network request? Test. Send message. I don't see. OK, preview. Data HTML. Fuck off. Oh, I see. Accept incoming change. Don't overwrite my shit. OK. OK, so it's like it's not flushing each time. It shows this at the bottom of all messages. I want it at the top, ideally as individual message components. You're streaming what? JSON? Wrong. You need HTML and swap it in. Scrape this. Also, all messages are coming at once at the end. You need to flush the output for that. After each message. I apologize for the oversight. You should get this book. It's really good. Hypermedia systems. Beautifully designed. Oops. Oh, look. Atlantis Pleb. Whoa. I'm right above the Primogen. What's up, the Primogen? There's me. Fuck yes. So I am the CEO of HTMX, along with these other 980 people. They might also be the CEO. Very nice. Come on. Oh, did it crap out? Hang on. OK, so in this case, we may want to go harvest some code from the Livewire stream command. You are not. It is not flushing. Use code based on this from the Livewire Livewire repo main branch. OK, that chat's fucked. OK, look at this. My stream code in message controller. Is not flushing. It's streaming everything at the end, not as it goes. Use code based on this from Livewire. I'm assuming Livewire Livewire is the. Yep. And then where is the actual? Code in the view. Message form. Also look at this. OK, let's see what this does. Certainly, I'll take a look at your stream code and suggest improvements based on the Livewire implementation. Let's start by examining the relevant files. Based on the analysis, I can see why your stream code is not flushing as expected. Please don't tell me that it's again duplicating. Humming. Error 405. OK, what is this 405 thing? Let's take a look at my logs. It doesn't make any logs.
[19:59] Speaker: Okay. I see messages coming to EventStream, but they don't show on the UI until the whole thing is done. The event stream... Did it look at main content? You need to look at event center content. And I see multiple calls to send message. The first is 200 with event stream, and then I see another one right after it. That's error 405. So, this is sloppy, but, you know, I don't care. I'll bring the HTMX book on the plane with me, so I'll learn more about it. We need to make some changes to ensure that the new messages are displayed correctly as they arrive. Yeah, so this is no problem at all with HTMX. I wasn't passing in the proper context to the agents and putting stuff in different places. Let's go clear out our database a little bit. The initial 200 response with the EventStream is correct. The subsequent 405 error might be caused by HTMX trying to process the response as HTML and attempting to swap it into the DOM. To prevent this, we need to stop HTMX from processing the response. Sounds smart. I don't know. If it works, it works. What is this? Test. Now I see nothing at all in the UI. This shows in the EventStream. Still a 405 on a second request. Do I need to use my human brain here, people? Oh, no. Ew, what the hell is this? Oh, you know what? I haven't even given this agent the doc. Test. Oh, yeah! Hey, that worked. Hey, that worked. Hey, that worked. Okay. Cool. Tried to fix the 405 error. Hope you didn't mess the other thing up. Okay, we don't have a 405. Nice. See, it figures it out eventually. You just gotta bash at it and yell at it and whine and... Yeah, okay. So... Great. Now I see this. It's just text. I want it showing up as message components. Like the other thing. Except I don't want separate messages. I want to see my message, and separately a system message, and for you to stream in individual words one at a time for a message. It all needs to go into a message component. Let's update partial message view to accommodate the new message structure. Let's try it. Testing. Oops, I gotta make enter submit it. Oh, yeah. Okay, well, it streams, but it's not putting it in a message component. Also, edit to save the system message at the end with all the content. So conceivably with SSE, we can do all sorts of cool streaming different pieces of data to different pieces of UI. We can have like a little ticker up here for showing who's online and just stream, stream stuff all over the place. I could see at some point doing WebSockets. I just have not gotten Laravel Reverb to work. Although apparently they're using it for the new Laravel cloud, so I guess it's production ready. I just haven't got to find the Laracast about it, I guess. Is there a Laracast about Reverb? Yeah. Uh-oh. Crapped out. Sometimes it's recoverable. Continue. Out of curiosity, what do we have so far? Oh, yeah. Nice. Okay, that's what I wanted. Whether or not I save it to the database doesn't really matter. But let's just try it. So I do want to be able to refresh and see the system. Come on. Yeah, it won't be hard at all now to just swap in, you know, we'll pass it to the LLM and just in the stream handler, stream each token to the client. But I'm really happy that we got that figured out. Okay, let's see if it works. Does this work? This is a demo response that will be streamed word by word to simulate an AI generating response in real time. What if I refresh it? It's not saving the database. I don't really give a shit. So blah, blah, blah, blah, blah. That works. And do another one. What if I do two at once? Oh, man. Oh! Vercel! Vercel AI SDK cannot do this. They cannot stream multiple responses at one time. HTMX for the win. See you soon.