-
Notifications
You must be signed in to change notification settings - Fork 514
feat: assistant-stream - ObjectStream #1912
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?
Conversation
Your trial period has expired. To continue using this feature, please upgrade to a paid plan here or book a time to chat here. |
|
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.
Pull Request Overview
This PR introduces object stream functionality by adding SSE-based encoding/decoding and associated JSON utilities for handling object stream operations.
- Adds SSEEncoder and SSEDecoder classes to process SSE event streams.
- Implements object stream types, accumulator, and stream response handlers to support a reactive object streaming pattern.
- Exports new APIs via the core index for external consumption.
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
packages/assistant-stream/src/core/utils/stream/SSE.ts | Implements SSE event encoding and decoding using TransformStreams. |
packages/assistant-stream/src/core/json/types.ts | Defines types for object stream operations and chunks. |
packages/assistant-stream/src/core/json/createObjectStream.ts | Provides a function to create object streams with asynchronous execution. |
packages/assistant-stream/src/core/json/ObjectStreamResponse.ts | Implements response handling via object stream encoding and decoding. |
packages/assistant-stream/src/core/json/ObjectStreamAccumulator.ts | Provides immutable operations to accumulate and update object stream state. |
packages/assistant-stream/src/core/index.ts | Updates exports to include the new object stream functionality. |
execute, | ||
defaultValue = {}, | ||
}: CreateObjectStreamOptions) => { | ||
const [stream, controller] = getStreamControllerPair({ defaultValue }); |
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.
Pass the defaultValue directly to getStreamControllerPair (i.e., getStreamControllerPair(defaultValue)) instead of wrapping it in an object, as the function expects a ReadonlyJSONValue.
const [stream, controller] = getStreamControllerPair({ defaultValue }); | |
const [stream, controller] = getStreamControllerPair(defaultValue); |
Copilot uses AI. Check for mistakes.
const idx = Number(key); | ||
if (isNaN(idx)) | ||
throw new Error(`Expected array index at [${path.join(", ")}]`); | ||
if (idx > state.length || idx < 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.
Consider updating the array bounds check to use 'idx >= state.length' instead of 'idx > state.length' to prevent accidentally allowing an out-of-bounds index equal to the array length.
if (idx > state.length || idx < 0) | |
if (idx >= state.length || idx < 0) |
Copilot uses AI. Check for mistakes.
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.
PR Summary
Introduces a comprehensive JSON object streaming system with SSE (Server-Sent Events) support, enabling real-time state updates and text appending operations through a structured API.
- New
ObjectStreamAccumulator
in/packages/assistant-stream/src/core/json/ObjectStreamAccumulator.ts
manages state tree with path-based operations and type safety - Added SSE encoding/decoding in
/packages/assistant-stream/src/core/utils/stream/SSE.ts
with proper event formatting and headers - Implemented
ObjectStreamResponse
in/packages/assistant-stream/src/core/json/ObjectStreamResponse.ts
with customAssistant-Stream-Format
header versioning - Silent JSON parse error handling in SSE decoder could lead to lost messages without proper error propagation
- Missing documentation for the
Assistant-Stream-Format
versioning scheme (marked as TODO)
6 file(s) reviewed, 6 comment(s)
Edit PR Review Bot Settings | Greptile
const idx = Number(key); | ||
if (isNaN(idx)) | ||
throw new Error(`Expected array index at [${path.join(", ")}]`); | ||
if (idx > state.length || idx < 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.
logic: Array index equal to length should be allowed for appending to array
if (idx > state.length || idx < 0) | |
if (idx >= state.length || idx < 0) |
); | ||
case "append-text": | ||
return ObjectStreamAccumulator.updatePath(state, op.path, (current) => { | ||
if (current === undefined) return op.value; |
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.
style: Consider validating op.value is a string when current is undefined in append-text operation
if (current === undefined) return op.value; | |
if (current === undefined) { | |
if (typeof op.value !== "string") throw new Error(`Expected string value in append-text operation`); | |
return op.value; | |
} |
execute, | ||
defaultValue = {}, | ||
}: CreateObjectStreamOptions) => { | ||
const [stream, controller] = getStreamControllerPair({ defaultValue }); |
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.
logic: defaultValue is wrapped in an object unnecessarily - should be passed directly
const [stream, controller] = getStreamControllerPair({ defaultValue }); | |
const [stream, controller] = getStreamControllerPair(defaultValue); |
enqueue(operations: readonly ObjectStreamOperation[]) { | ||
this._accumulator.append(operations); | ||
|
||
this._controller.enqueue({ | ||
snapshot: this._accumulator.state, | ||
operations, | ||
}); | ||
return this; | ||
} |
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.
logic: enqueue returns this but return type is void in interface - could cause type errors
case "retry": | ||
eventBuffer.retry = Number(value); | ||
break; |
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.
logic: No validation for retry value being a positive number. Invalid values could cause reconnection issues.
case "retry": | |
eventBuffer.retry = Number(value); | |
break; | |
case "retry": | |
const retryNum = Number(value); | |
if (!isNaN(retryNum) && retryNum > 0) { | |
eventBuffer.retry = retryNum; | |
} | |
break; |
.pipeThrough( | ||
new TransformStream<T, string>({ | ||
transform(chunk, controller) { | ||
controller.enqueue(`data: ${JSON.stringify(chunk)}\n\n`); |
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.
style: JSON.stringify may throw for circular references or BigInts. Add try/catch to handle serialization errors.
📝 Documentation updates detected! You can review documentation updates here |
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.
mrge found 4 issues across 6 files. View them in mrge.io
@@ -0,0 +1,18 @@ | |||
import { ReadonlyJSONValue } from "../../utils"; |
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.
The import path for ReadonlyJSONValue is incorrect. The type is actually defined in '../../utils/json/json-value.ts'.
.pipeThrough( | ||
new TransformStream<T, string>({ | ||
transform(chunk, controller) { | ||
controller.enqueue(`data: ${JSON.stringify(chunk)}\n\n`); |
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.
Missing error handling for JSON.stringify which could throw on non-serializable data
execute, | ||
defaultValue = {}, | ||
}: CreateObjectStreamOptions) => { | ||
const [stream, controller] = getStreamControllerPair({ defaultValue }); |
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.
The function getStreamControllerPair expects defaultValue directly, but is being passed an object with a defaultValue property
if (response.headers.get("Content-Type") !== "text/event-stream") { | ||
throw new Error("Response is not an event stream"); | ||
} | ||
if (response.headers.get("Assistant-Stream-Format") !== "json-v0.1") { |
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.
Magic string "json-v0.1" appears multiple times in the codebase and should be extracted as a constant
execute, | ||
defaultValue = {}, | ||
}: CreateObjectStreamOptions) => { | ||
const [stream, controller] = getStreamControllerPair({ defaultValue }); |
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.
Potential bug: Passing an object { defaultValue }
to getStreamControllerPair
rather than the value itself. Likely use getStreamControllerPair(defaultValue)
.
const [stream, controller] = getStreamControllerPair({ defaultValue }); | |
const [stream, controller] = getStreamControllerPair(defaultValue); |
try { | ||
controller.enqueue(JSON.parse(event.data)); | ||
} catch (err) { | ||
console.error("Failed to parse SSE event data:", err); |
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.
JSON.parse errors are only logged here; consider propagating or handling the error to inform the consumer.
WalkthroughThis change introduces a set of new modules and exports to support a streaming protocol for JSON object operations over Server-Sent Events (SSE) within the assistant-stream package. The core index file is updated to export new streaming-related entities, including a function for creating object streams, a response wrapper, and a decoding utility. A new accumulator class is added to manage immutable JSON state through appendable operations. The protocol is defined through types for operations and chunks, supporting "set" and "append-text" operations on JSON paths. The streaming logic is implemented with utilities for encoding and decoding SSE streams, enabling transmission and reception of JSON operation streams. The createObjectStream utility provides a mechanism to emit incremental state snapshots and operations, while ObjectStreamResponse and fromObjectStreamResponse handle SSE-based response wrapping and decoding. All new entities are exported for public use, and error handling and stream cancellation are incorporated throughout the implementation. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (6)
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 5
🧹 Nitpick comments (6)
packages/assistant-stream/src/core/index.ts (1)
18-22
: Consider re-exporting the companion types for a smoother DX
createObjectStream
,ObjectStreamResponse
, andfromObjectStreamResponse
are now surfaced, but the related value/type helpers (ObjectStreamOperation
,ObjectStreamChunk
,ObjectStreamAccumulator
, etc.) remain hidden one level down. Call-sites will frequently need both the helpers and the primitives, so bubbling the types up through this barrel can prevent deep import paths like:import type { ObjectStreamChunk } from "@acme/assistant-stream/core/json/types";Optional refactor:
// index.ts export * from "./json/types"; // <- adds operation + chunk types export { ObjectStreamAccumulator } from "./json/ObjectStreamAccumulator";packages/assistant-stream/src/core/utils/stream/SSE.ts (3)
5-12
: Freeze the shared header object
static readonly headers
is currently mutable becauseHeaders
exposes mutators.
Accidental runtime mutation would leak to every response using the encoder.-static readonly headers = new Headers({ +static readonly headers: Readonly<Headers> = new Headers({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", });Or freeze it after construction:
const hdr = new Headers({ /* … */ }); Object.freeze(hdr); static readonly headers = hdr;
104-114
: Decoder: reconsider error strategy for unknown eventsThrowing on an unfamiliar
event
type terminates the entire stream, even though the client might be able to safely ignore those events (e.g.,ping
,heartbeat
, custom extensions).Safer pattern:
-default: - throw new Error(`Unknown SSE event type: ${event.event}`); +default: + // Ignore or expose via side-channel; do not kill consumer stream + return;This still surfaces malformed payloads while keeping the connection resilient.
106-110
: Surface JSON-parse failures to the consumer
console.error
hides deserialization problems and silently drops data.
Forward the error to the stream to let callers attach.catch
handlers or abort processing.- } catch (err) { - console.error("Failed to parse SSE event data:", err); + } catch (err) { + controller.error( + new Error(`Failed to parse SSE event data: ${(err as Error).message}`), + ); return; }packages/assistant-stream/src/core/json/ObjectStreamAccumulator.ts (1)
53-55
: Consider creating intermediate containers instead of throwing on missing parentsIf an operation targets a non-existing nested path (e.g.
["foo","bar"]
whenstate.foo
is stillundefined
), the current implementation throws.
Depending on the use-case you may want to auto-create intermediary objects/arrays (similar to lodash’sset
) so callers don’t need to pre-seed the structure.If strictness is desired, feel free to ignore this remark.
packages/assistant-stream/src/core/json/createObjectStream.ts (1)
53-61
: Guard againstcontroller
being undefined incancel
If the stream is cancelled before
start
executes (very unlikely but technically possible in spec-compliant runtimes),controller
would still beundefined
.- cancel(reason: unknown) { - controller.__internalCancel(reason); - }, + cancel(reason: unknown) { + if (controller) controller.__internalCancel(reason); + },Not critical, but eliminates a theoretical race.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
packages/assistant-stream/src/core/index.ts
(1 hunks)packages/assistant-stream/src/core/json/ObjectStreamAccumulator.ts
(1 hunks)packages/assistant-stream/src/core/json/ObjectStreamResponse.ts
(1 hunks)packages/assistant-stream/src/core/json/createObjectStream.ts
(1 hunks)packages/assistant-stream/src/core/json/types.ts
(1 hunks)packages/assistant-stream/src/core/utils/stream/SSE.ts
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
packages/assistant-stream/src/core/utils/stream/SSE.ts (2)
packages/assistant-stream/src/core/utils/stream/PipeableTransformStream.ts (1)
PipeableTransformStream
(1-10)packages/assistant-stream/src/core/utils/stream/LineDecoderStream.ts (1)
LineDecoderStream
(1-29)
packages/assistant-stream/src/core/json/createObjectStream.ts (3)
packages/assistant-stream/src/core/json/types.ts (2)
ObjectStreamOperation
(3-13)ObjectStreamChunk
(15-18)packages/assistant-stream/src/core/json/ObjectStreamAccumulator.ts (1)
ObjectStreamAccumulator
(4-76)packages/assistant-stream/src/core/utils/withPromiseOrValue.ts (1)
withPromiseOrValue
(1-20)
packages/assistant-stream/src/core/json/ObjectStreamResponse.ts (5)
packages/assistant-stream/src/core/utils/stream/PipeableTransformStream.ts (1)
PipeableTransformStream
(1-10)packages/assistant-stream/src/core/json/types.ts (2)
ObjectStreamChunk
(15-18)ObjectStreamOperation
(3-13)packages/assistant-stream/src/core/utils/stream/SSE.ts (2)
SSEEncoder
(4-26)SSEDecoder
(94-120)packages/assistant-stream/src/core/json/ObjectStreamAccumulator.ts (1)
ObjectStreamAccumulator
(4-76)packages/assistant-stream/src/core/index.ts (2)
ObjectStreamResponse
(20-20)fromObjectStreamResponse
(21-21)
packages/assistant-stream/src/core/json/ObjectStreamAccumulator.ts (1)
packages/assistant-stream/src/core/json/types.ts (1)
ObjectStreamOperation
(3-13)
🪛 Biome (1.9.4)
packages/assistant-stream/src/core/json/ObjectStreamAccumulator.ts
[error] 41-41: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 66-66: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 72-72: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: mrge · AI code reviewer
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/assistant-stream/src/core/json/types.ts (1)
3-18
: Type declarations look solidThe discriminated‐union design cleanly encodes the two operations and keeps the snapshot/ops pair immutable via
Readonly*
types. No correctness or style concerns here. 👍
if (line.startsWith(":")) return; // Ignore comments | ||
|
||
if (line === "") { | ||
if (dataLines.length > 0) { | ||
controller.enqueue({ | ||
event: eventBuffer.event || "message", | ||
data: dataLines.join("\n"), | ||
id: eventBuffer.id, | ||
retry: eventBuffer.retry, | ||
}); | ||
} | ||
eventBuffer = {}; | ||
dataLines = []; | ||
return; | ||
} | ||
|
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.
SSE parsing breaks on CRLF line endings
LineDecoderStream
splits on \n
only, so CRLF lines arrive as
'data: {...}\r'
. The trailing \r
prevents the empty-line sentinel (""
) from ever matching, so events never flush on Windows-style streams.
Minimal fix inside the event parser:
- transform(line, controller) {
+ transform(rawLine, controller) {
+ const line = rawLine.replace(/\r$/, ""); // strip CR
-if (line === "") {
+if (line.length === 0) {
-const [field, ...rest] = line.split(":");
+const [field, ...rest] = line.split(":", 2);
(Using a max-split of 2 avoids unintended splits when value
itself contains a colon.)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (line.startsWith(":")) return; // Ignore comments | |
if (line === "") { | |
if (dataLines.length > 0) { | |
controller.enqueue({ | |
event: eventBuffer.event || "message", | |
data: dataLines.join("\n"), | |
id: eventBuffer.id, | |
retry: eventBuffer.retry, | |
}); | |
} | |
eventBuffer = {}; | |
dataLines = []; | |
return; | |
} | |
// --- earlier in the file, inside the TransformStream setup ---------------- | |
transform(rawLine, controller) { | |
// strip trailing CR so that empty‐line checks work on CRLF inputs | |
const line = rawLine.replace(/\r$/, ""); | |
if (line.startsWith(":")) return; // Ignore comments | |
if (line.length === 0) { | |
if (dataLines.length > 0) { | |
controller.enqueue({ | |
event: eventBuffer.event || "message", | |
data: dataLines.join("\n"), | |
id: eventBuffer.id, | |
retry: eventBuffer.retry, | |
}); | |
} | |
eventBuffer = {}; | |
dataLines = []; | |
return; | |
} | |
// Only split into field and value once, even if the value contains ':' | |
const [field, ...rest] = line.split(":", 2); | |
// ...the rest of your parsing logic... | |
} |
const nextState = [...state]; | ||
nextState[idx] = this.updatePath(nextState[idx], rest, updater); | ||
|
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.
🛠️ Refactor suggestion
Replace this.updatePath
with an explicit class reference in static context
Using this
inside a static method is legal but usually discouraged and triggers Biome’s noThisInStatic
rule.
Referencing the class directly removes the warning and clarifies intent.
- const nextState = [...state];
- nextState[idx] = this.updatePath(nextState[idx], rest, updater);
+ const nextState = [...state];
+ nextState[idx] = ObjectStreamAccumulator.updatePath(
+ nextState[idx],
+ rest,
+ updater,
+ );
...
- const nextState = { ...(state as ReadonlyJSONObject) };
- nextState[key] = this.updatePath(nextState[key], rest, updater);
+ const nextState = { ...(state as ReadonlyJSONObject) };
+ nextState[key] = ObjectStreamAccumulator.updatePath(
+ nextState[key],
+ rest,
+ updater,
+ );
Also applies to: 71-73
🧰 Tools
🪛 Biome (1.9.4)
[error] 66-66: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
const _exhaustiveCheck: never = type; | ||
throw new Error(`Invalid operation type: ${_exhaustiveCheck}`); | ||
} |
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.
Wrap the _exhaustiveCheck
declaration to satisfy the no-switch-declarations rule
The const _exhaustiveCheck
variable is declared in the default
clause and therefore becomes visible to the other clauses, triggering the Biome noSwitchDeclarations
lint error.
A minimal fix is to wrap the default
body in a block.
- default:
- const _exhaustiveCheck: never = type;
- throw new Error(`Invalid operation type: ${_exhaustiveCheck}`);
+ default: {
+ const _exhaustiveCheck: never = type;
+ throw new Error(`Invalid operation type: ${_exhaustiveCheck}`);
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const _exhaustiveCheck: never = type; | |
throw new Error(`Invalid operation type: ${_exhaustiveCheck}`); | |
} | |
default: { | |
const _exhaustiveCheck: never = type; | |
throw new Error(`Invalid operation type: ${_exhaustiveCheck}`); | |
} |
🧰 Tools
🪛 Biome (1.9.4)
[error] 41-41: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
const [stream, controller] = getStreamControllerPair({ defaultValue }); | ||
|
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.
Incorrect argument passed to getStreamControllerPair
getStreamControllerPair
expects a ReadonlyJSONValue
, but an object with a defaultValue
property is supplied, causing the initial accumulator state to become { defaultValue: … }
.
- const [stream, controller] = getStreamControllerPair({ defaultValue });
+ const [stream, controller] = getStreamControllerPair(defaultValue);
Without this fix, every snapshot will be wrapped one level deeper than callers expect.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const [stream, controller] = getStreamControllerPair({ defaultValue }); | |
const [stream, controller] = getStreamControllerPair(defaultValue); |
if (response.headers.get("Content-Type") !== "text/event-stream") { | ||
throw new Error("Response is not an event stream"); | ||
} | ||
if (response.headers.get("Assistant-Stream-Format") !== "json-v0.1") { | ||
throw new Error("Unsupported Assistant-Stream-Format header"); |
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.
🛠️ Refactor suggestion
Relax strict equality on Content-Type
header
Some servers append a charset (text/event-stream; charset=utf-8
).
A strict comparison will reject perfectly valid responses.
- if (response.headers.get("Content-Type") !== "text/event-stream") {
+ const contentType = response.headers.get("Content-Type") ?? "";
+ if (!contentType.startsWith("text/event-stream")) {
throw new Error("Response is not an event stream");
}
The same logic can be applied to the custom Assistant-Stream-Format
header once the TODO is resolved.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (response.headers.get("Content-Type") !== "text/event-stream") { | |
throw new Error("Response is not an event stream"); | |
} | |
if (response.headers.get("Assistant-Stream-Format") !== "json-v0.1") { | |
throw new Error("Unsupported Assistant-Stream-Format header"); | |
const contentType = response.headers.get("Content-Type") ?? ""; | |
if (!contentType.startsWith("text/event-stream")) { | |
throw new Error("Response is not an event stream"); | |
} | |
if (response.headers.get("Assistant-Stream-Format") !== "json-v0.1") { | |
throw new Error("Unsupported Assistant-Stream-Format header"); | |
} |
execute, | ||
defaultValue = {}, | ||
}: CreateObjectStreamOptions) => { | ||
const [stream, controller] = getStreamControllerPair({ defaultValue }); |
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.
Potential bug: Passing { defaultValue }
instead of the raw defaultValue
. Likely should call getStreamControllerPair(defaultValue)
to avoid nesting.
const [stream, controller] = getStreamControllerPair({ defaultValue }); | |
const [stream, controller] = getStreamControllerPair(defaultValue); |
|
||
default: | ||
const _exhaustiveCheck: never = type; | ||
throw new Error(`Invalid operation type: ${_exhaustiveCheck}`); |
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.
For improved debugging, consider using op.type
in the error message instead of _exhaustiveCheck
in the default case.
throw new Error(`Invalid operation type: ${_exhaustiveCheck}`); | |
throw new Error(`Invalid operation type: ${type}`); |
execute, | ||
defaultValue = {}, | ||
}: CreateObjectStreamOptions) => { | ||
const [stream, controller] = getStreamControllerPair({ defaultValue }); |
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.
BUG: Passing { defaultValue }
wraps the default value in an extra object. Pass defaultValue
directly to getStreamControllerPair
.
const [stream, controller] = getStreamControllerPair({ defaultValue }); | |
const [stream, controller] = getStreamControllerPair(defaultValue); |
if (!response.ok) | ||
throw new Error(`Response failed, status ${response.status}`); | ||
if (!response.body) throw new Error("Response body is null"); | ||
if (response.headers.get("Content-Type") !== "text/event-stream") { |
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.
The strict header check for Content-Type
may fail if additional parameters (like charset) are included. Consider a more flexible check.
if (response.headers.get("Content-Type") !== "text/event-stream") { | |
if (!response.headers.get("Content-Type")?.startsWith("text/event-stream")) { |
@trag-bot didn't find any issues in the code! ✅✨ |
Pull request summary
|
Important
Introduces object stream handling with new classes and functions for encoding, decoding, and managing streams in the assistant-stream package.
ObjectStreamAccumulator
for managing object stream state inObjectStreamAccumulator.ts
.ObjectStreamResponse
andfromObjectStreamResponse
for handling object stream responses inObjectStreamResponse.ts
.createObjectStream
for creating object streams with a controller increateObjectStream.ts
.SSEEncoder
andSSEDecoder
for handling server-sent events inSSE.ts
.index.ts
to exportcreateObjectStream
,ObjectStreamResponse
,fromObjectStreamResponse
, andObjectStreamChunk
type.ObjectStreamOperation
andObjectStreamChunk
types intypes.ts
.This description was created by
for fc002be. You can customize this summary. It will automatically update as commits are pushed.