-
Notifications
You must be signed in to change notification settings - Fork 91
fix: Bump ai to ^5.0.52 to fix CVE-2025-48985 (GHSA-rwvc-j5jr-mgvh) #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect} from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { kv } from "@vercel/kv"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import TopBar from "@/components/top-bar"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import UnderlineLink from "@/components/underline-link"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { track } from '@vercel/analytics'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -63,4 +64,52 @@ export default function Error({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function StreamingText({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response: Response; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [text, setText] = useState(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!response.body) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const reader = response.body.getReader(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const decoder = new TextDecoder(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let completion = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const pump = async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { done, value } = await reader.read(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (done) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await kv.set(url, completion); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const chunk = decoder.decode(value, { stream: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const lines = chunk.split("\n").filter((line) => line.trim() !== ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const line of lines) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!line.startsWith("data:")) continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const data = line.slice(5).trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (data === "[DONE]") continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parsed = JSON.parse(data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const delta = parsed.choices?.[0]?.delta?.content; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof delta === "string") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| completion += delta; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setText((prev) => prev + delta); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ignore malformed JSON | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pump(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pump(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [response, url]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+78
to
+112
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift
Consider moving the KV persistence to a Server Action or API route that the client calls upon completion. 🤖 Prompt for AI Agents🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Missing If the component unmounts while streaming, the reader continues running and may attempt state updates on an unmounted component. Return a cleanup function that cancels the reader. 🛠️ Suggested fix useEffect(() => {
if (!response.body) return;
+ let cancelled = false;
const reader = response.body.getReader();
const decoder = new TextDecoder();
let completion = "";
const pump = async () => {
const { done, value } = await reader.read();
- if (done) {
+ if (done || cancelled) {
await kv.set(url, completion);
return;
}
// ... rest of pump
- pump();
+ if (!cancelled) pump();
};
pump();
+
+ return () => {
+ cancelled = true;
+ reader.cancel();
+ };
}, [response, url]);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return <>{text}</>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,5 @@ | ||
| import { Configuration, OpenAIApi } from "openai-edge"; | ||
| import { OpenAIStream } from "ai"; | ||
| import { StreamingText } from "./error"; | ||
| import { kv } from "@vercel/kv"; | ||
| import { Tokens } from "ai/react"; | ||
| import { Suspense } from "react"; | ||
| import Image from "next/image"; | ||
| import { Skeleton } from "@/components/ui/skeleton"; | ||
|
|
@@ -14,9 +12,6 @@ export const runtime = "edge"; | |
| import { Source, getData } from "@/lib/data"; | ||
|
|
||
|
|
||
| const apiConfig = new Configuration({ | ||
| apiKey: process.env.OPENAI_API_KEY!, | ||
| }); | ||
|
|
||
| type Article = { | ||
| title: string; | ||
|
|
@@ -47,7 +42,7 @@ export default async function Page({ | |
| params: { slug: string }; | ||
| searchParams: { [key: string]: string | string[] | undefined }; | ||
| }) { | ||
| const headersList = headers(); | ||
| const headersList = await headers(); | ||
| const ip = headersList.get("x-real-ip") || "default_ip"; | ||
|
|
||
| const url = | ||
|
|
@@ -253,19 +248,16 @@ async function Wrapper({ | |
| }), | ||
| }); | ||
|
|
||
| // Convert the response into a friendly text-stream | ||
| const stream = OpenAIStream(response, { | ||
| async onCompletion(completion) { | ||
| await kv.set(url, completion); | ||
| // await kv.expire(prompt, 60 * 10); | ||
| }, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| return "Well this sucks. Looks like I ran out of money to pay for summaries. Please be patient until a benevolent sponsor gives me either cash or sweet sweet OpenAI credits. If you would like to be that sponsor, feel free to reach out to contact@smry.ai!"; | ||
| } | ||
|
|
||
| return <Tokens stream={stream} />; | ||
| if (!response.body) { | ||
| return "No response body received from the API."; | ||
| } | ||
|
|
||
| // Stream the response body through the client-side Tokens replacement. | ||
| return <StreamingText response={response} url={url} />; | ||
|
Comment on lines
+255
to
+260
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🔴 Critical 🧩 Analysis chain🌐 Web query:
💡 Result: In Vercel AI SDK v5, the streamText function is the primary method for streaming AI responses in Next.js applications [1][2]. It is designed to handle text generation and tool-calling loops efficiently by returning a StreamTextResult object, which provides built-in methods to return streaming HTTP responses [1][3]. To implement a streaming response in a Next.js App Router API route (route handler), follow this pattern: 1. Import streamText and necessary helpers from the ai package [3][4]. 2. Create a POST request handler that extracts the required input (e.g., messages) from the request body [3][4]. 3. Call streamText with your model configuration [3][4]. 4. Return the result using one of the helper methods provided on the result object, such as toUIMessageStreamResponse or toTextStreamResponse [1][5]. Example implementation for a chat endpoint: import { convertToModelMessages, streamText, type UIMessage } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json; const result = streamText({ model: 'openai/gpt-4o', // Replace with your model messages: convertToModelMessages(messages), }); return result.toUIMessageStreamResponse; } Key changes and notes in v5: - Migration: The convertToCoreMessages function from previous versions has been renamed to convertToModelMessages [6]. - Helper Methods: The result object provides specialized methods for different streaming protocols. toUIMessageStreamResponse is standard for UI-integrated chat, while toTextStreamResponse is used for simple text streams [1][5]. - Multi-step control: The maxSteps parameter used in earlier versions has been replaced with the more flexible stopWhen parameter to control tool-calling loops [6][2]. - Error Handling: streamText suppresses errors by default to prevent server crashes, sending them as part of the stream. You can use the onError callback to log these errors [1]. - Performance: You can optionally define maxDuration in your route to handle longer generations (e.g., export const maxDuration = 30;) [5]. Citations:
🏁 Script executed: # Read the relevant file and surrounding context
cat -n app/proxy/page.tsx | sed -n '240,275p'Repository: caffo/SMRY Length of output: 1201 🏁 Script executed: # Find and inspect the StreamingText component definition
fd -t f 'StreamingText' --or -g '*StreamingText*'Repository: caffo/SMRY Length of output: 299 🏁 Script executed: # Search for StreamingText component definition in the codebase
rg -t tsx -t ts "component.*StreamingText|function StreamingText|const StreamingText" -A 10Repository: caffo/SMRY Length of output: 82 🏁 Script executed: # Find StreamingText component
find . -type f -name "*Stream*" | head -20Repository: caffo/SMRY Length of output: 2067 🏁 Script executed: # Search for StreamingText in the codebase
rg "StreamingText" -t js -t tsRepository: caffo/SMRY Length of output: 245 🏁 Script executed: # Read the StreamingText component definition
cat -n app/proxy/error.tsxRepository: caffo/SMRY Length of output: 4450
Consider:
🤖 Prompt for AI Agents |
||
| } catch (error) { | ||
| return `Well this sucks. Looks like an unexpected error occured, so no summary for this site :( No I won't tell you the error, that is private. Really, you insist? Fine. The error is ${error} Happy?`; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,7 @@ | |
| "@upstash/ratelimit": "^0.4.4", | ||
| "@vercel/analytics": "^1.1.1", | ||
| "@vercel/kv": "^1.0.0", | ||
| "ai": "^2.2.21", | ||
| "ai": "^5.0.52", | ||
| "class-variance-authority": "^0.7.0", | ||
| "clsx": "^2.0.0", | ||
| "compromise": "^14.10.0", | ||
|
|
@@ -31,7 +31,7 @@ | |
| "jsdom": "^22.1.0", | ||
| "lucide-react": "^0.290.0", | ||
| "marked": "^10.0.0", | ||
| "next": "14.0.0", | ||
| "next": "16.2.5", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🔴 Critical 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Check React version to ensure compatibility with Next.js 16
# Extract React and React-DOM versions from package.json
echo "Checking React versions in package.json..."
cat package.json | jq -r '.dependencies | {"react": .react, "react-dom": ."react-dom"}'
# Check if versions are compatible with Next 16 (requires React 19+)
react_version=$(cat package.json | jq -r '.dependencies.react // empty')
if [[ -n "$react_version" ]]; then
echo "Found React version: $react_version"
if [[ "$react_version" =~ ^(\^|~)?18\. ]] || [[ "$react_version" =~ ^(\^|~)?17\. ]]; then
echo "❌ ERROR: React $react_version is incompatible with Next.js 16.2.5"
echo "Next.js 15+ requires React 19 minimum"
exit 1
elif [[ "$react_version" =~ ^(\^|~)?19\. ]] || [[ "$react_version" =~ ^(\^|~)?[2-9][0-9]\. ]]; then
echo "✓ React version appears compatible"
fi
fiRepository: caffo/SMRY Length of output: 274 🌐 Web query:
💡 Result: Next.js 16 requires React 19.2 or higher for its App Router functionality, as it leverages the latest React Canary release [1][2][3]. When upgrading to or installing Next.js 16, the official documentation instructs users to install the latest versions of Citations:
🏁 Script executed: # Search for Next.js version requirements and documentation in common sources
curl -s "https://nextjs.org/docs" 2>/dev/null | grep -i "react.*19\|react.*18" | head -20 || echo "Could not fetch documentation"Repository: caffo/SMRY Length of output: 50367 Upgrade React to version 19 or higher for Next.js 16.2.5 compatibility. Next.js 16 requires React 19.2 or higher. The project currently uses React ^18.2.0, which is incompatible and will cause build or runtime failures. Update 🤖 Prompt for AI Agents |
||
| "node-html-markdown": "^1.3.0", | ||
| "node-html-parser": "^6.1.11", | ||
| "openai": "^4.17.0", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
SSE messages split across chunks will fail to parse.
The code splits each chunk by
\n, but a single SSEdata:line can span multiple chunks. For example, if one chunk ends mid-JSON (data: {"choi) and the next starts with the remainder (ces":...}\n), the parser will discard both fragments as malformed.Buffer incomplete lines and only parse when a complete line ending in
\nis received.🤖 Prompt for AI Agents