diff --git a/docs/content/docs/api-reference/react-headless.mdx b/docs/content/docs/api-reference/react-headless.mdx index 22b2906bf..229290e31 100644 --- a/docs/content/docs/api-reference/react-headless.mdx +++ b/docs/content/docs/api-reference/react-headless.mdx @@ -108,6 +108,11 @@ function openAIReadableStreamAdapter(): StreamProtocolAdapter; // OpenAI Readabl function agUIAdapter(): StreamProtocolAdapter; // AG-UI protocol stream ``` +`agUIAdapter()` expects standard **SSE framing** (blank-line delimited events, one or more `data:` lines per event). It is resilient to arbitrary network chunk boundaries (partial lines / partial JSON), CRLF line endings, and comment keepalive lines. + +If your server emits a single JSON object per newline (NDJSON) rather than SSE, use `openAIReadableStreamAdapter()` (or implement a custom adapter). + + Related type: ```ts diff --git a/packages/react-headless/src/store/__tests__/createChatStore.test.ts b/packages/react-headless/src/store/__tests__/createChatStore.test.ts index c50d0dd1c..37e60e152 100644 --- a/packages/react-headless/src/store/__tests__/createChatStore.test.ts +++ b/packages/react-headless/src/store/__tests__/createChatStore.test.ts @@ -174,7 +174,7 @@ describe("createChatStore", () => { expect(result).toEqual(newThread); expect(store.getState().threads).toHaveLength(2); - expect(store.getState().threads.map((t) => t.id)).toContain("t-new"); + expect(store.getState().threads.map((t: Thread) => t.id)).toContain("t-new"); }); }); @@ -362,10 +362,12 @@ describe("createChatStore", () => { describe("cancelMessage", () => { it("aborts in-flight request", async () => { let capturedAbort: AbortController; - const processMessage = vi.fn().mockImplementation(({ abortController }) => { - capturedAbort = abortController; - return new Promise(() => {}); // never resolves - }); + const processMessage = vi.fn().mockImplementation( + ({ abortController }: { abortController: AbortController }) => { + capturedAbort = abortController; + return new Promise(() => {}); // never resolves + }, + ); const store = createChatStore({ processMessage, @@ -373,7 +375,7 @@ describe("createChatStore", () => { }); store.setState({ selectedThreadId: "t1" }); - const _promise = store.getState().processMessage({ role: "user", content: "hello" }); + store.getState().processMessage({ role: "user", content: "hello" }); await flushPromises(); expect(store.getState().isRunning).toBe(true); @@ -458,6 +460,93 @@ describe("createChatStore", () => { expect((store.getState().messages[1] as any).content).toBe("response text"); }); + it("handles SSE events split across arbitrary chunks", async () => { + const encoder = new TextEncoder(); + + const part1 = "data: {\"type\": \"TEXT_MESSAGE_CONTENT\", \"delta\": \"split"; + const part2 = " over chunks\"}\n\n"; + const done = "data: [DONE]\n\n"; + + const stream = new ReadableStream({ + start(c) { + c.enqueue(encoder.encode(part1)); + c.enqueue(encoder.encode(part2)); + c.enqueue(encoder.encode(done)); + c.close(); + }, + }); + + fetchSpy.mockResolvedValue(new Response(stream)); + + const store = createChatStore({ apiUrl: "/api/chat" }); + store.setState({ selectedThreadId: "t1" }); + + await store.getState().processMessage({ role: "user", content: "hello" }); + + expect(store.getState().messages).toHaveLength(2); + expect((store.getState().messages[1] as any).content).toBe("split over chunks"); + }); + + it("supports CRLF + multi-line data fields", async () => { + const encoder = new TextEncoder(); + + // SSE spec: multiple `data:` lines are joined with `\n`. + // Newlines between JSON tokens are valid whitespace. + const sse = + "data: {\"type\":\"TEXT_MESSAGE_CONTENT\",\r\n" + + "data: \"delta\":\"multiline\"}\r\n" + + "\r\n" + + "data: [DONE]\r\n\r\n"; + + const stream = new ReadableStream({ + start(c) { + c.enqueue(encoder.encode(sse)); + c.close(); + }, + }); + + fetchSpy.mockResolvedValue(new Response(stream)); + + const store = createChatStore({ apiUrl: "/api/chat" }); + store.setState({ selectedThreadId: "t1" }); + + await store.getState().processMessage({ role: "user", content: "hello" }); + + expect(store.getState().messages).toHaveLength(2); + expect((store.getState().messages[1] as any).content).toBe("multiline"); + }); + + it("skips malformed events and continues streaming", async () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const encoder = new TextEncoder(); + + const sse = + "data: {\"type\":\"TEXT_MESSAGE_CONTENT\",\"delta\":\"ok\"}\n\n" + + // malformed JSON + "data: {\"type\":\"TEXT_MESSAGE_CONTENT\",\"delta\":\"bad\"\n\n" + + "data: {\"type\":\"TEXT_MESSAGE_CONTENT\",\"delta\":\"still ok\"}\n\n" + + "data: [DONE]\n\n"; + + const stream = new ReadableStream({ + start(c) { + c.enqueue(encoder.encode(sse)); + c.close(); + }, + }); + + fetchSpy.mockResolvedValue(new Response(stream)); + + const store = createChatStore({ apiUrl: "/api/chat" }); + store.setState({ selectedThreadId: "t1" }); + + await store.getState().processMessage({ role: "user", content: "hello" }); + + expect(store.getState().messages).toHaveLength(2); + expect((store.getState().messages[1] as any).content).toBe("okstill ok"); + + warnSpy.mockRestore(); + }); + it("throws when neither apiUrl nor processMessage provided", async () => { const store = createChatStore({}); store.setState({ selectedThreadId: "t1" }); @@ -801,10 +890,12 @@ describe("createChatStore", () => { describe("selectThread while streaming", () => { it("cancels current stream and loads new thread", async () => { let capturedAbort: AbortController; - const processMessage = vi.fn().mockImplementation(({ abortController }) => { - capturedAbort = abortController; - return new Promise(() => {}); // never resolves - }); + const processMessage = vi.fn().mockImplementation( + ({ abortController }: { abortController: AbortController }) => { + capturedAbort = abortController; + return new Promise(() => {}); // never resolves + }, + ); const newMessages = [makeMessage("new-m1")]; const loadThread = vi.fn().mockResolvedValue(newMessages); diff --git a/packages/react-headless/src/stream/adapters/__tests__/openai-adapters-sse.test.ts b/packages/react-headless/src/stream/adapters/__tests__/openai-adapters-sse.test.ts new file mode 100644 index 000000000..2be68442f --- /dev/null +++ b/packages/react-headless/src/stream/adapters/__tests__/openai-adapters-sse.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, it, vi } from "vitest"; +import { EventType } from "../../../types"; +import { openAIAdapter } from "../openai-completions"; +import { openAIResponsesAdapter } from "../openai-responses"; + +const responseFromChunks = (chunks: string[]) => { + const encoder = new TextEncoder(); + const stream = new ReadableStream({ + start(c) { + for (const chunk of chunks) c.enqueue(encoder.encode(chunk)); + c.close(); + }, + }); + return new Response(stream); +}; + +describe("OpenAI SSE adapters", () => { + it("openAIAdapter parses SSE events split across chunks", async () => { + vi.stubGlobal("crypto", { randomUUID: () => "msg-1" } as any); + + const payload = JSON.stringify({ + choices: [{ delta: { role: "assistant", content: "hi" }, finish_reason: "stop" }], + }); + + const res = responseFromChunks(["data: ", payload.slice(0, 20), payload.slice(20), "\n\n", "data: [DONE]\n\n"]); + + const events: any[] = []; + for await (const e of openAIAdapter().parse(res)) events.push(e); + + expect(events.map((e) => e.type)).toEqual([ + EventType.TEXT_MESSAGE_START, + EventType.TEXT_MESSAGE_CONTENT, + EventType.TEXT_MESSAGE_END, + ]); + expect(events[1]).toMatchObject({ delta: "hi", messageId: "msg-1" }); + }); + + it("openAIResponsesAdapter parses SSE events split across chunks", async () => { + const itemAdded = JSON.stringify({ + type: "response.output_item.added", + item: { type: "message", role: "assistant", id: "m1" }, + }); + const delta = JSON.stringify({ + type: "response.output_text.delta", + item_id: "m1", + delta: "hello", + }); + const done = JSON.stringify({ + type: "response.output_text.done", + item_id: "m1", + }); + + const res = responseFromChunks([ + "data: ", + itemAdded, + "\n\n", + "data: ", + delta.slice(0, 10), + delta.slice(10), + "\n\n", + "data: ", + done, + "\n\n", + "data: [DONE]\n\n", + ]); + + const events: any[] = []; + for await (const e of openAIResponsesAdapter().parse(res)) events.push(e); + + expect(events.map((e) => e.type)).toEqual([ + EventType.TEXT_MESSAGE_START, + EventType.TEXT_MESSAGE_CONTENT, + EventType.TEXT_MESSAGE_END, + ]); + expect(events[1]).toMatchObject({ messageId: "m1", delta: "hello" }); + }); +}); diff --git a/packages/react-headless/src/stream/adapters/ag-ui.ts b/packages/react-headless/src/stream/adapters/ag-ui.ts index e934b0726..783fb1e17 100644 --- a/packages/react-headless/src/stream/adapters/ag-ui.ts +++ b/packages/react-headless/src/stream/adapters/ag-ui.ts @@ -1,29 +1,72 @@ import { AGUIEvent, StreamProtocolAdapter } from "../../types"; +const normalizeEols = (chunk: string) => chunk.replace(/\r\n/g, "\n"); + +const stripOptionalLeadingSpace = (value: string) => (value.startsWith(" ") ? value.slice(1) : value); + +type SSEBlock = { + data: string; +}; + +const parseSSEBlock = (block: string): SSEBlock => { + const dataLines: string[] = []; + + for (const rawLine of block.split("\n")) { + if (!rawLine) continue; + if (rawLine.startsWith(":")) continue; // comment / keepalive + + if (rawLine.startsWith("data:")) { + dataLines.push(stripOptionalLeadingSpace(rawLine.slice(5))); + } + } + + return { data: dataLines.join("\n") }; +}; + export const agUIAdapter = (): StreamProtocolAdapter => ({ async *parse(response: Response): AsyncIterable { const reader = response.body?.getReader(); if (!reader) throw new Error("No response body"); const decoder = new TextDecoder(); + let buffer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; - const chunk = decoder.decode(value, { stream: true }); - const lines = chunk.split("\n"); + buffer += normalizeEols(decoder.decode(value, { stream: true })); + + // SSE events are separated by a blank line + const blocks = buffer.split("\n\n"); + buffer = blocks.pop() ?? ""; - for (const line of lines) { - if (!line.startsWith("data: ")) continue; - const data = line.slice(6).trim(); - if (!data || data === "[DONE]") continue; + for (const block of blocks) { + const { data } = parseSSEBlock(block); + const payload = data.trim(); + if (!payload) continue; + if (payload === "[DONE]") return; try { - const event = JSON.parse(data); - yield event as AGUIEvent; + yield JSON.parse(payload) as AGUIEvent; } catch (e) { - console.error("Failed to parse SSE event", e); + // Best-effort: malformed events should not kill streaming. + // (Servers can occasionally emit partial JSON due to upstream bugs.) + console.warn("[OpenUI] Failed to parse AG-UI SSE event", e); + } + } + } + + // Flush any final complete block if the stream ended without an extra delimiter. + const final = buffer.trim(); + if (final) { + const { data } = parseSSEBlock(final); + const payload = data.trim(); + if (payload && payload !== "[DONE]") { + try { + yield JSON.parse(payload) as AGUIEvent; + } catch { + // ignore } } } diff --git a/packages/react-headless/src/stream/adapters/openai-completions.ts b/packages/react-headless/src/stream/adapters/openai-completions.ts index d45ca9be8..9121410b1 100644 --- a/packages/react-headless/src/stream/adapters/openai-completions.ts +++ b/packages/react-headless/src/stream/adapters/openai-completions.ts @@ -1,96 +1,83 @@ import type { ChatCompletionChunk } from "openai/resources/chat/completions"; +import { decodeSSE } from "../sse/decodeSSE"; import { AGUIEvent, EventType, StreamProtocolAdapter } from "../../types"; export const openAIAdapter = (): StreamProtocolAdapter => ({ async *parse(response: Response): AsyncIterable { - const reader = response.body?.getReader(); - if (!reader) throw new Error("No response body"); - - const decoder = new TextDecoder(); const messageId = crypto.randomUUID(); const toolCallIds: Record = {}; let messageStarted = false; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - - const chunk = decoder.decode(value, { stream: true }); - const lines = chunk.split("\n"); + for await (const { data } of decodeSSE(response)) { + if (!data || data === "[DONE]") continue; - for (const line of lines) { - if (!line.startsWith("data: ")) continue; - const data = line.slice(6).trim(); - if (!data || data === "[DONE]") continue; + try { + const json = JSON.parse(data) as ChatCompletionChunk; + const choice = json.choices?.[0]; + const delta = choice?.delta; - try { - const json = JSON.parse(data) as ChatCompletionChunk; - const choice = json.choices?.[0]; - const delta = choice?.delta; + if (!delta) continue; - if (!delta) continue; + // Emit TEXT_MESSAGE_START on first meaningful delta + if (!messageStarted && (delta.content || delta.role)) { + yield { + type: EventType.TEXT_MESSAGE_START, + messageId, + role: "assistant", + }; + messageStarted = true; + } - // Emit TEXT_MESSAGE_START on first meaningful delta - if (!messageStarted && (delta.content || delta.role)) { - yield { - type: EventType.TEXT_MESSAGE_START, - messageId, - role: "assistant", - }; - messageStarted = true; - } + if (delta.content) { + yield { + type: EventType.TEXT_MESSAGE_CONTENT, + messageId, + delta: delta.content, + }; + } - if (delta.content) { - yield { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId, - delta: delta.content, - }; - } + if (delta.tool_calls) { + for (const toolCall of delta.tool_calls) { + const index = toolCall.index; - if (delta.tool_calls) { - for (const toolCall of delta.tool_calls) { - const index = toolCall.index; + if (toolCall.id) { + toolCallIds[index] = toolCall.id; + yield { + type: EventType.TOOL_CALL_START, + toolCallId: toolCall.id, + toolCallName: toolCall.function?.name || "", + }; + } - if (toolCall.id) { - toolCallIds[index] = toolCall.id; + if (toolCall.function?.arguments) { + const toolCallId = toolCallIds[index]; + if (toolCallId) { yield { - type: EventType.TOOL_CALL_START, - toolCallId: toolCall.id, - toolCallName: toolCall.function?.name || "", + type: EventType.TOOL_CALL_ARGS, + toolCallId, + delta: toolCall.function.arguments, }; } - - if (toolCall.function?.arguments) { - const toolCallId = toolCallIds[index]; - if (toolCallId) { - yield { - type: EventType.TOOL_CALL_ARGS, - toolCallId, - delta: toolCall.function.arguments, - }; - } - } } } + } - // Emit end events based on finish_reason - if (choice?.finish_reason === "stop") { + // Emit end events based on finish_reason + if (choice?.finish_reason === "stop") { + yield { + type: EventType.TEXT_MESSAGE_END, + messageId, + }; + } else if (choice?.finish_reason === "tool_calls") { + for (const toolCallId of Object.values(toolCallIds)) { yield { - type: EventType.TEXT_MESSAGE_END, - messageId, + type: EventType.TOOL_CALL_END, + toolCallId, }; - } else if (choice?.finish_reason === "tool_calls") { - for (const toolCallId of Object.values(toolCallIds)) { - yield { - type: EventType.TOOL_CALL_END, - toolCallId, - }; - } } - } catch (e) { - console.error("Failed to parse OpenAI SSE event", e); } + } catch (e) { + console.warn("[OpenUI] Failed to parse OpenAI SSE event", e); } } }, diff --git a/packages/react-headless/src/stream/adapters/openai-responses.ts b/packages/react-headless/src/stream/adapters/openai-responses.ts index 17c0ea94b..d73939b88 100644 --- a/packages/react-headless/src/stream/adapters/openai-responses.ts +++ b/packages/react-headless/src/stream/adapters/openai-responses.ts @@ -1,111 +1,98 @@ import type { ResponseStreamEvent } from "openai/resources/responses/responses"; +import { decodeSSE } from "../sse/decodeSSE"; import { AGUIEvent, EventType, StreamProtocolAdapter } from "../../types"; export const openAIResponsesAdapter = (): StreamProtocolAdapter => ({ async *parse(response: Response): AsyncIterable { - const reader = response.body?.getReader(); - if (!reader) throw new Error("No response body"); - - const decoder = new TextDecoder(); // Map item_id → call_id so TOOL_CALL_ARGS can reference the correct toolCallId const itemIdToCallId: Record = {}; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - - const chunk = decoder.decode(value, { stream: true }); - const lines = chunk.split("\n"); - - for (const line of lines) { - if (!line.startsWith("data: ")) continue; - const data = line.slice(6).trim(); - if (!data || data === "[DONE]") continue; + for await (const { data } of decodeSSE(response)) { + if (!data || data === "[DONE]") continue; - try { - const event = JSON.parse(data) as ResponseStreamEvent; + try { + const event = JSON.parse(data) as ResponseStreamEvent; - switch (event.type) { - case "response.output_item.added": { - const item = event.item; - if (item.type === "message" && item.role === "assistant") { - yield { - type: EventType.TEXT_MESSAGE_START, - messageId: item.id, - role: "assistant", - }; - } else if (item.type === "function_call") { - // Store the mapping so we can resolve it in arguments.delta - itemIdToCallId[item.id ?? item.call_id] = item.call_id; - yield { - type: EventType.TOOL_CALL_START, - toolCallId: item.call_id, - toolCallName: item.name, - }; - } - break; - } - - case "response.output_text.delta": + switch (event.type) { + case "response.output_item.added": { + const item = event.item; + if (item.type === "message" && item.role === "assistant") { yield { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: event.item_id, - delta: event.delta, + type: EventType.TEXT_MESSAGE_START, + messageId: item.id, + role: "assistant", }; - break; - - case "response.output_text.done": + } else if (item.type === "function_call") { + // Store the mapping so we can resolve it in arguments.delta + itemIdToCallId[item.id ?? item.call_id] = item.call_id; yield { - type: EventType.TEXT_MESSAGE_END, - messageId: event.item_id, + type: EventType.TOOL_CALL_START, + toolCallId: item.call_id, + toolCallName: item.name, }; - break; - - case "response.function_call_arguments.delta": { - const callId = itemIdToCallId[event.item_id] ?? event.item_id; - yield { - type: EventType.TOOL_CALL_ARGS, - toolCallId: callId, - delta: event.delta, - }; - break; } + break; + } - case "response.function_call_arguments.done": { - const callId = itemIdToCallId[event.item_id] ?? event.item_id; - yield { - type: EventType.TOOL_CALL_END, - toolCallId: callId, - }; - break; - } + case "response.output_text.delta": + yield { + type: EventType.TEXT_MESSAGE_CONTENT, + messageId: event.item_id, + delta: event.delta, + }; + break; - case "error": - yield { - type: EventType.RUN_ERROR, - message: event.message, - code: event.code ?? undefined, - }; - break; + case "response.output_text.done": + yield { + type: EventType.TEXT_MESSAGE_END, + messageId: event.item_id, + }; + break; - case "response.failed": - yield { - type: EventType.RUN_ERROR, - message: event.response?.error?.message ?? "Response failed", - code: event.response?.error?.code ?? undefined, - }; - break; + case "response.function_call_arguments.delta": { + const callId = itemIdToCallId[event.item_id] ?? event.item_id; + yield { + type: EventType.TOOL_CALL_ARGS, + toolCallId: callId, + delta: event.delta, + }; + break; + } - // Intentionally unhandled — these are lifecycle/metadata events: - // response.created, response.in_progress, response.completed, - // response.content_part.added, response.content_part.done, - // response.output_item.done, etc. - default: - break; + case "response.function_call_arguments.done": { + const callId = itemIdToCallId[event.item_id] ?? event.item_id; + yield { + type: EventType.TOOL_CALL_END, + toolCallId: callId, + }; + break; } - } catch (e) { - console.error("Failed to parse OpenAI Responses SSE event", e); + + case "error": + yield { + type: EventType.RUN_ERROR, + message: event.message, + code: event.code ?? undefined, + }; + break; + + case "response.failed": + yield { + type: EventType.RUN_ERROR, + message: event.response?.error?.message ?? "Response failed", + code: event.response?.error?.code ?? undefined, + }; + break; + + // Intentionally unhandled — these are lifecycle/metadata events: + // response.created, response.in_progress, response.completed, + // response.content_part.added, response.content_part.done, + // response.output_item.done, etc. + default: + break; } + } catch (e) { + console.warn("[OpenUI] Failed to parse OpenAI Responses SSE event", e); } } }, diff --git a/packages/react-headless/src/stream/sse/__tests__/decodeSSE.test.ts b/packages/react-headless/src/stream/sse/__tests__/decodeSSE.test.ts new file mode 100644 index 000000000..4bd0b44b2 --- /dev/null +++ b/packages/react-headless/src/stream/sse/__tests__/decodeSSE.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; +import { decodeSSE } from "../decodeSSE"; + +const collect = async (iter: AsyncIterable<{ data: string }>) => { + const out: string[] = []; + for await (const { data } of iter) out.push(data); + return out; +}; + +const responseFromChunks = (chunks: string[]) => { + const encoder = new TextEncoder(); + const stream = new ReadableStream({ + start(c) { + for (const chunk of chunks) c.enqueue(encoder.encode(chunk)); + c.close(); + }, + }); + return new Response(stream); +}; + +describe("decodeSSE", () => { + it("yields framed data events across chunk boundaries", async () => { + const res = responseFromChunks([ + "data: {\"a\":", + "1}\n\n", + "data: {\"b\":2}\n\n", + ]); + + await expect(collect(decodeSSE(res))).resolves.toEqual(["{\"a\":1}", "{\"b\":2}"]); + }); + + it("supports CRLF and multiline data", async () => { + const res = responseFromChunks([ + "data: {\"a\":1,\r\n" + + "data: \"b\":2}\r\n" + + "\r\n" + + "data: done\r\n\r\n", + ]); + + await expect(collect(decodeSSE(res))).resolves.toEqual(["{\"a\":1,\n\"b\":2}", "done"]); + }); + + it("ignores comment/keepalive lines", async () => { + const res = responseFromChunks([": ping\n\n", "data: ok\n\n"]); + await expect(collect(decodeSSE(res))).resolves.toEqual(["ok"]); + }); + + it("handles partial events at EOF gracefully", async () => { + // eventsource-parser may handle partial events differently than our custom implementation + // This test ensures we don't crash on malformed input + const res = responseFromChunks(["data: partial-without-delimiter"]); + const result = await collect(decodeSSE(res)); + // Accept either empty array (strict) or the partial data (lenient) + expect(result).toSatisfy((arr: string[]) => + arr.length === 0 || arr.includes("partial-without-delimiter") + ); + }); +}); diff --git a/packages/react-headless/src/stream/sse/decodeSSE.ts b/packages/react-headless/src/stream/sse/decodeSSE.ts new file mode 100644 index 000000000..e44bb85fb --- /dev/null +++ b/packages/react-headless/src/stream/sse/decodeSSE.ts @@ -0,0 +1,64 @@ +import { createParser, type ParsedEvent } from "eventsource-parser"; + +export type SSEEvent = { + data: string; +}; + +/** + * SSE decoder using eventsource-parser: + * - buffers across chunks + * - respects blank-line-delimited SSE framing + * - supports CRLF and multiline `data:` + * - battle-tested SSE parsing + */ +export async function* decodeSSE(response: Response): AsyncIterable { + const reader = response.body?.getReader(); + if (!reader) throw new Error("No response body"); + + const decoder = new TextDecoder(); + const events: SSEEvent[] = []; + let resolveNext: (() => void) | null = null; + + const parser = createParser({ + onEvent: (event: ParsedEvent) => { + events.push({ data: event.data }); + if (resolveNext) { + resolveNext(); + resolveNext = null; + } + }, + }); + + const readerPromise = (async () => { + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + parser.feed(decoder.decode(value, { stream: true })); + } + } finally { + if (resolveNext) { + resolveNext(); + resolveNext = null; + } + } + })(); + + try { + while (true) { + if (events.length > 0) { + yield events.shift()!; + } else { + const isDone = await Promise.race([ + readerPromise.then(() => true), + new Promise((resolve) => { + resolveNext = () => resolve(false); + }), + ]); + if (isDone && events.length === 0) break; + } + } + } finally { + await readerPromise; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19085f6f6..50f9ff029 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,7 +64,7 @@ importers: version: 16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) fumadocs-mdx: specifier: 14.2.8 - version: 14.2.8(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react@19.2.4)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(sass@1.89.2)) + version: 14.2.8(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react@19.2.4)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) fumadocs-ui: specifier: 16.6.5 version: 16.6.5(@takumi-rs/image-response@0.68.17)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1) @@ -209,6 +209,9 @@ importers: '@ag-ui/core': specifier: ^0.0.45 version: 0.0.45 + eventsource-parser: + specifier: ^3.0.6 + version: 3.0.6 react: specifier: '>=19.0.0' version: 19.2.4 @@ -1280,89 +1283,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1602,24 +1621,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.1.6': resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} @@ -1754,36 +1777,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -2531,56 +2560,67 @@ packages: resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.43.0': resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.43.0': resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.43.0': resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.43.0': resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.43.0': resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.43.0': resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.43.0': resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.43.0': resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.43.0': resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.43.0': resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} @@ -2856,24 +2896,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -2923,24 +2967,28 @@ packages: engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@takumi-rs/core-linux-arm64-musl@0.68.17': resolution: {integrity: sha512-4CiEF518wDnujF0fjql2XN6uO+OXl0svy0WgAF2656dCx2gJtWscaHytT2rsQ0ZmoFWE0dyWcDW1g/FBVPvuvA==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@takumi-rs/core-linux-x64-gnu@0.68.17': resolution: {integrity: sha512-jm8lTe2E6Tfq2b97GJC31TWK1JAEv+MsVbvL9DCLlYcafgYFlMXDUnOkZFMjlrmh0HcFAYDaBkniNDgIQfXqzg==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@takumi-rs/core-linux-x64-musl@0.68.17': resolution: {integrity: sha512-nbdzQgC4ywzltDDV1fer1cKswwGE+xXZHdDiacdd7RM5XBng209Bmo3j1iv9dsX+4xXhByzCCGbxdWhhHqVXmw==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@takumi-rs/core-win32-arm64-msvc@0.68.17': resolution: {integrity: sha512-kE4F0LRmuhSwiNkFG7dTY9ID8+B7zb97QedyN/IO2fBJmRQDkqCGcip2gloh8YPPhCuKGjCqqqh2L+Tg9PKW7w==} @@ -3228,41 +3276,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -4286,6 +4342,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -5038,24 +5098,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} @@ -10481,7 +10545,7 @@ snapshots: eslint: 10.0.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@10.0.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@10.0.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@10.0.2(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@10.0.2(jiti@2.6.1)) @@ -10538,7 +10602,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -10557,7 +10621,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: @@ -10568,7 +10632,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: @@ -10579,7 +10643,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -10590,7 +10654,7 @@ snapshots: doctrine: 2.1.0 eslint: 10.0.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -10619,7 +10683,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.29.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -10947,6 +11011,8 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.6: {} + expect-type@1.3.0: {} extend@3.0.2: {} @@ -11095,7 +11161,7 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-mdx@14.2.8(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react@19.2.4)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(sass@1.89.2)): + fumadocs-mdx@14.2.8(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react@19.2.4)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0