-
Notifications
You must be signed in to change notification settings - Fork 76
feat(server): make DefaultExecutionEventBus cross-runtime compatible #100
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
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.
Summary of Changes
Hello @lingkaix, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
I've refactored the DefaultExecutionEventBus
to ensure it can operate seamlessly across various JavaScript runtimes, including Node.js, Bun, Deno, serverless environments like Cloudflare Workers, and even browsers. This change addresses its previous dependency on Node.js's EventEmitter
, making the event bus truly universal.
Highlights
- Cross-Runtime Compatibility: The DefaultExecutionEventBus has been refactored to remove its reliance on Node.js's EventEmitter, making it usable in diverse JavaScript environments.
- Custom Event Handling: A custom event listener mechanism has been implemented within DefaultExecutionEventBus using Map and Set to manage event listeners, replacing the inherited EventEmitter functionality.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command>
or @gemini-code-assist <command>
. Below is a summary of the supported commands.
Feature | Command | Description |
---|---|---|
Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/
folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
/gemini review |
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.
Code Review
This pull request refactors DefaultExecutionEventBus
to be cross-runtime compatible by removing its dependency on Node.js's EventEmitter
. The new implementation is self-contained and uses standard Map and Set objects to manage event listeners. The change is well-implemented, but I found a critical issue in the finished
method's implementation which passes an unsafe and incorrect object to listeners, subtly changing behavior from the previous implementation. My review includes a suggestion to fix this to align with the previous behavior and ensure type safety.
// For finished event, we don't pass an event object | ||
listener({} as AgentExecutionEvent); |
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 implementation of the finished
event is unsafe and its behavior has subtly changed. The listener is called with {} as AgentExecutionEvent
, which is not a valid AgentExecutionEvent
and will cause runtime errors for listeners that inspect the event object. The kind
property, present on all types within AgentExecutionEvent
, will be undefined
.
The previous EventEmitter
implementation would call the listener with no arguments (i.e., undefined
for the first parameter). Calling with an empty object {}
changes this behavior, which could break listeners that check for the event's existence (e.g., if (event)
).
To maintain previous behavior and avoid unsafe type casting, it's better to call the listener without arguments. This requires a cast to any
to bypass the strict type signature, which also highlights a potential design issue in the ExecutionEventBus
interface where finished
listeners are expected to receive an AgentExecutionEvent
.
// For finished event, we don't pass an event object | |
listener({} as AgentExecutionEvent); | |
// For finished event, we don't pass an event object, matching previous EventEmitter behavior. | |
(listener as any)(); |
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.
As suggested, lets make the event
attribute as optional.
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.
Code Review
This pull request refactors DefaultExecutionEventBus
to remove its dependency on the Node.js events
module, making it compatible with various JavaScript runtimes like Deno, Bun, and browsers. The new implementation is self-contained and uses a Map
and Set
to manage event listeners.
The refactoring is well-done, but I've identified a type safety issue in the handling of the finished
event. The current implementation passes an empty object cast as an AgentExecutionEvent
, which violates the type contract and could lead to runtime errors. I've provided suggestions to make the event handling more robust and type-safe by adjusting the EventListener
type and how parameterless events are dispatched.
| Task | ||
| TaskStatusUpdateEvent | ||
| TaskArtifactUpdateEvent; | ||
export type EventListener = (event: AgentExecutionEvent) => void; |
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.
To safely handle events that don't have a payload, like the finished
event, the EventListener
type should be updated to make the event
parameter optional. This prevents type-safety issues when dispatching parameter-less events and makes the API more robust.
export type EventListener = (event: AgentExecutionEvent) => void; | |
export type EventListener = (event?: AgentExecutionEvent) => void; |
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.
+1
// For finished event, we don't pass an event object | ||
listener({} as AgentExecutionEvent); |
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.
Casting an empty object {}
to AgentExecutionEvent
is not type-safe and can lead to runtime errors in consumers of this event bus, as it lacks the kind
discriminator property. The finished
event is a signal and should not carry a payload. In conjunction with making the event
parameter optional in EventListener
, this should be changed to call the listener without any arguments.
// For finished event, we don't pass an event object | |
listener({} as AgentExecutionEvent); | |
// For finished event, we don't pass an event object | |
listener(); |
} | ||
|
||
once(eventName: "event" | "finished", listener: EventListener): this { | ||
const onceWrapper = (event: AgentExecutionEvent) => { |
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.
To align with the proposed change to EventListener
(making the event
parameter optional), the onceWrapper
function should also handle an optional event
argument to maintain type consistency across the event bus implementation.
const onceWrapper = (event: AgentExecutionEvent) => { | |
const onceWrapper = (event?: AgentExecutionEvent) => { |
The suggestion from Gemini maybe alright. But we need to modify ExecutionEventBus to be like: Also, I appreciate if anyone would like to discuss and clarify:
Thank you! |
Thanks for raising this PR. Will review it this week. |
publish(event: AgentExecutionEvent): void; | ||
on(eventName: "event" | "finished", listener: EventListener): this; | ||
off(eventName: "event" | "finished", listener: EventListener): this; | ||
once(eventName: "event" | "finished", listener: EventListener): 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.
Let's remove this once method. I don't this is used.
| Task | ||
| TaskStatusUpdateEvent | ||
| TaskArtifactUpdateEvent; | ||
export type EventListener = (event: AgentExecutionEvent) => void; |
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.
+1
// For finished event, we don't pass an event object | ||
listener({} as AgentExecutionEvent); |
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.
As suggested, lets make the event
attribute as optional.
} | ||
|
||
publish(event: AgentExecutionEvent): void { | ||
const listeners = this.eventListeners.get("event"); |
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 following code is copied across both publish & finished. Can we just encapsulate this as _emit().
Description
The original DefaultExecutionEventBus implementation was bound to Node.js. I refactored it to be cross-runtime compatible. So that it works on Node.js, Bun, Deno, serverless runtimes like Cloudflare Worker, and even Browsers.
CONTRIBUTING
Guide.fix:
which represents bug fixes, and correlates to a SemVer patch.feat:
represents a new feature, and correlates to a SemVer minor.feat!:
, orfix!:
,refactor!:
, etc., which represent a breaking change (indicated by the!
) and will result in a SemVer major.