-
Notifications
You must be signed in to change notification settings - Fork 66
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
refactor to simplicity #14
Conversation
WalkthroughThis pull request represents a significant architectural refactoring of the core system, moving away from an event-driven architecture towards a more modular, task-oriented approach. The changes involve removing event-related interfaces, restructuring client and processing logic, and introducing new input/output management mechanisms. The modifications simplify event handling, enhance content processing, and provide more flexible ways of registering and executing tasks across different platforms like Twitter. Changes
Sequence DiagramsequenceDiagram
participant Core
participant Processor
participant TwitterClient
participant RoomManager
Core->>Processor: Register inputs/outputs
Processor->>TwitterClient: Create mentions input
Processor->>TwitterClient: Create timeline input
Core->>Core: Process inputs periodically
TwitterClient->>RoomManager: Check mentions
Processor->>Processor: Classify and enrich content
Processor->>Core: Suggest potential outputs
Core->>TwitterClient: Execute tweet output
Poem
Finishing Touches
🪧 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: 9
🔭 Outside diff range comments (1)
packages/core/src/core/processor.ts (1)
Line range hint
156-185
: Mitigate potential prompt injection vulnerabilities inenrichContent
As with
classifyContent
, including rawcontentStr
in the LLM prompt can expose the system to prompt injection attacks. Sanitizing or encodingcontentStr
before including it in the prompt can prevent malicious input from manipulating the LLM.Apply this diff to sanitize the content:
const prompt = `Analyze the following content and provide enrichment: - Content: "${contentStr}" + Content: "${this.escapePromptContent(contentStr)}" Related Context: ${relatedMemories.map((m: SearchResult) => `- ${m.content}`).join("\n")} Provide a JSON response with: 1. A brief summary (max 100 chars) 2. Key topics mentioned (max 5) 3. Sentiment analysis 4. Named entities 5. Detected intent/purpose Response format: { "summary": "Brief summary here", "topics": ["topic1", "topic2"], "sentiment": "positive|negative|neutral", "entities": ["entity1", "entity2"], "intent": "purpose of the content" } Return only valid JSON, no other text.`; + private escapePromptContent(content: string): string { + return content.replace(/[{}[\]<>\|`"]/g, ''); + }
🧹 Nitpick comments (7)
packages/core/src/core/consciousness.ts (2)
221-231
: Consider logging low-confidence thoughts for future insights.Currently, low-confidence thoughts result in a default message and are not stored. Logging these thoughts could provide valuable data over time for analyzing patterns or adjusting the confidence threshold.
237-246
: Enhance error metadata for better debugging.In the error handling block, consider adding more detailed information to the
metadata
, such as the stack trace or an error code. This additional context can aid in troubleshooting and diagnosing issues more effectively.packages/core/src/types/index.ts (1)
38-40
: Use@deprecated
JSDoc annotation instead of inlineTODO
commentTo properly indicate that
CoTActionType
is deprecated and to enable tooling support, replace the inline// TODO: deprecate
comment with the@deprecated
JSDoc annotation.Apply this diff to make the change:
-// TODO: deprecate +/** + * @deprecated + */ export type CoTActionType = | "GRAPHQL_FETCH" | "EXECUTE_TRANSACTION" | "SYSTEM_PROMPT";packages/core/src/core/core.ts (1)
10-14
: Enhance type safety by specifying concrete types instead ofany
The use of
any
reduces type safety and can lead to runtime errors. Consider making theInput
andOutput
interfaces generic to specify concrete types forresponse
and function parameters.Here's how you might define generic interfaces:
-export interface Input { +export interface Input<TResponse> { name: string; - handler: (...args: any[]) => Promise<any>; + handler: () => Promise<TResponse>; - response: any; // Type of response expected + response: TResponse; // Type of response expected interval?: number; // Time in milliseconds between runs, if not provided runs once } -export interface Output { +export interface Output<TInput, TResponse> { name: string; - handler: (data: any) => Promise<any>; + handler: (data: TInput) => Promise<TResponse>; - response: any; // Type of response expected + response: TResponse; // Type of response expected schema: JSONSchemaType<TInput>; // Schema to validate input data }This change enhances type safety and makes the code more maintainable.
Also applies to: 19-23
packages/core/src/clients/twitter-client.ts (1)
234-248
: Move example usage to documentation or separate fileIncluding example usage code within the main class file can clutter the codebase. Consider moving these examples to a dedicated documentation file or code examples directory.
packages/core/src/core/processor.ts (2)
102-105
: Define specific type forcontent
parameter inclassifyContent
The
classifyContent
method usescontent: any
. To enhance type safety, consider defining a specific type or interface forcontent
.
151-154
: Define specific type forcontent
parameter inenrichContent
Similarly, the
enrichContent
method usescontent: any
. Defining a specific type forcontent
can improve type checking and reduce potential runtime errors.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
packages/core/src/clients/base-client.ts
(0 hunks)packages/core/src/clients/twitter-client.ts
(2 hunks)packages/core/src/core/actions.ts
(0 hunks)packages/core/src/core/consciousness.ts
(2 hunks)packages/core/src/core/core.ts
(2 hunks)packages/core/src/core/processor.ts
(8 hunks)packages/core/src/types/index.ts
(1 hunks)
💤 Files with no reviewable changes (2)
- packages/core/src/core/actions.ts
- packages/core/src/clients/base-client.ts
🔇 Additional comments (2)
packages/core/src/core/consciousness.ts (1)
200-211
: High-confidence thought handling is implemented correctly.The logic for handling high-confidence thoughts is well-structured. The returned object includes all necessary properties and appropriately merges
thought.context
intometadata
.packages/core/src/clients/twitter-client.ts (1)
12-16
: EnsureTweetData
interface aligns with Twitter API requirementsVerify that the
TweetData
interface fields correctly represent the data needed to send a tweet via the Twitter API. Missing required fields or incorrect types may cause issues when integrating with the actual API.Please confirm that
inReplyTo
andconversationId
are sufficient for threading tweets and that no additional fields are required.
private async think(): Promise<{ | ||
type: string; | ||
source: string; | ||
content: string; | ||
timestamp: Date; | ||
metadata: Record<string, 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.
💡 Codebase verification
Update error handling in setInterval
callback
The new return type of think()
reveals a critical issue in consciousness.ts
:
- The
setInterval
callback ignores the Promise returned bythink()
, which could lead to unhandled rejections and memory leaks - Update line 189 in
consciousness.ts
to properly handle the Promise, for example:
this.thoughtInterval = setInterval(async () => {
try {
await this.think();
} catch (error) {
this.logger.error("Error in think interval", error);
}
}, intervalMs);
Note: The usage in example-basic.ts
already correctly handles the return value, and the think()
method in chain-of-thought.ts
appears to be a different implementation.
🔗 Analysis chain
Verify all usages of think()
handle the new return type.
The think()
method now returns a Promise
of an object with detailed properties instead of Promise<void>
. Ensure that all calls to think()
throughout the codebase are updated to handle the new return value appropriately to prevent any runtime errors.
You can run the following script to identify all usages of think()
:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Find all occurrences where the `think` method is called.
# Search for calls to the `think` method in TypeScript files.
rg --type ts '\.think\('
Length of output: 310
Script:
#!/bin/bash
# Get context for each usage
echo "=== examples/example-basic.ts ==="
rg -B 5 -A 5 "\.think\(" examples/example-basic.ts
echo -e "\n=== packages/core/src/core/consciousness.ts ==="
rg -B 5 -A 5 "\.think\(" packages/core/src/core/consciousness.ts
echo -e "\n=== packages/core/src/core/chain-of-thought.ts ==="
rg -B 5 -A 5 "\.think\(" packages/core/src/core/chain-of-thought.ts
Length of output: 1474
Script:
#!/bin/bash
# Check the implementation of think() in chain-of-thought.ts
ast-grep --pattern 'think($$$) {
$$$
}' packages/core/src/core/chain-of-thought.ts
Length of output: 86
Script:
#!/bin/bash
# Check the content of chain-of-thought.ts for class definition and imports
echo "=== Class definition and imports in chain-of-thought.ts ==="
rg -B 5 "class" packages/core/src/core/chain-of-thought.ts
echo -e "\n=== Imports in chain-of-thought.ts ==="
rg "^import|^from" packages/core/src/core/chain-of-thought.ts
Length of output: 1159
packages/core/src/core/core.ts
Outdated
const validate = require("ajv")().compile(output.schema); | ||
if (!validate(data)) { | ||
throw new Error(`Invalid data for output ${name}: ${validate.errors}`); |
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
Import Ajv
properly and reuse the validator instance
Using require
in TypeScript is discouraged. Instead, import Ajv
at the top of the file and instantiate it once, preferably in the constructor, to avoid creating a new instance on each call to executeOutput
.
Apply this diff to update the import and validator usage:
+import Ajv, { JSONSchemaType } from "ajv";
export class Core {
+ private ajv: Ajv;
// ...
constructor(
roomManager: RoomManager,
vectorDb: VectorDB,
processor: Processor,
config?: CoreConfig
) {
// ...
+ this.ajv = new Ajv();
}
public async executeOutput(name: string, data: any): Promise<any> {
const output = this.outputs.get(name);
if (!output) {
throw new Error(`No output registered with name: ${name}`);
}
- const validate = require("ajv")().compile(output.schema);
+ const validate = this.ajv.compile(output.schema);
if (!validate(data)) {
throw new Error(`Invalid data for output ${name}: ${validate.errors}`);
}
// ...
}
}
Committable suggestion skipped: line range outside the PR's diff.
public async close() { | ||
if (this.scraper) { | ||
try { | ||
// await this.scraper.close(); | ||
this.isInitialized = false; | ||
} catch (error) { | ||
this.log("Error closing Twitter scraper", error); | ||
} | ||
} | ||
|
||
this.isInitialized = false; | ||
await super.stop(); | ||
} | ||
|
||
private async checkMentions() { | ||
try { | ||
this.log("Checking for new mentions", { | ||
username: this.credentials.username, | ||
}); | ||
|
||
const mentions = ( | ||
await this.scraper.fetchSearchTweets( | ||
`@${this.credentials.username}`, | ||
20, | ||
SearchMode.Latest | ||
) | ||
).tweets; | ||
|
||
// Convert AsyncGenerator to array and de-duplicate | ||
const mentionsArray: Tweet[] = []; | ||
for await (const tweet of mentions) { | ||
mentionsArray.push(tweet); | ||
} | ||
|
||
// De-duplicate and sort mentions | ||
const uniqueMentions = [...new Set(mentionsArray)] | ||
.sort((a: Tweet, b: Tweet) => a.id?.localeCompare(b.id ?? "") ?? 0) | ||
.filter((tweet: Tweet) => tweet.userId !== this.credentials.username); | ||
|
||
for (const tweet of uniqueMentions) { | ||
if ( | ||
!this.lastCheckedTweetId || | ||
BigInt(tweet.id ?? "") > this.lastCheckedTweetId | ||
) { | ||
await this.handleMention(tweet); | ||
this.lastCheckedTweetId = BigInt(tweet.id ?? ""); | ||
await this.saveLastCheckedId(); // Save checkpoint after processing | ||
} | ||
this.logger.error("TwitterClient.close", "Error closing scraper", { | ||
error, | ||
}); |
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
Uncomment and implement scraper closure in close
method
The close
method currently does not close the scraper instance, which may lead to resource leaks. Uncomment and implement the await this.scraper.close();
line to properly dispose of resources.
Apply this diff to update the method:
public async close() {
if (this.scraper) {
try {
- // await this.scraper.close();
+ await this.scraper.close();
this.isInitialized = false;
} catch (error) {
this.logger.error("TwitterClient.close", "Error closing scraper", {
error,
});
}
}
}
📝 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.
public async close() { | |
if (this.scraper) { | |
try { | |
// await this.scraper.close(); | |
this.isInitialized = false; | |
} catch (error) { | |
this.log("Error closing Twitter scraper", error); | |
} | |
} | |
this.isInitialized = false; | |
await super.stop(); | |
} | |
private async checkMentions() { | |
try { | |
this.log("Checking for new mentions", { | |
username: this.credentials.username, | |
}); | |
const mentions = ( | |
await this.scraper.fetchSearchTweets( | |
`@${this.credentials.username}`, | |
20, | |
SearchMode.Latest | |
) | |
).tweets; | |
// Convert AsyncGenerator to array and de-duplicate | |
const mentionsArray: Tweet[] = []; | |
for await (const tweet of mentions) { | |
mentionsArray.push(tweet); | |
} | |
// De-duplicate and sort mentions | |
const uniqueMentions = [...new Set(mentionsArray)] | |
.sort((a: Tweet, b: Tweet) => a.id?.localeCompare(b.id ?? "") ?? 0) | |
.filter((tweet: Tweet) => tweet.userId !== this.credentials.username); | |
for (const tweet of uniqueMentions) { | |
if ( | |
!this.lastCheckedTweetId || | |
BigInt(tweet.id ?? "") > this.lastCheckedTweetId | |
) { | |
await this.handleMention(tweet); | |
this.lastCheckedTweetId = BigInt(tweet.id ?? ""); | |
await this.saveLastCheckedId(); // Save checkpoint after processing | |
} | |
this.logger.error("TwitterClient.close", "Error closing scraper", { | |
error, | |
}); | |
public async close() { | |
if (this.scraper) { | |
try { | |
await this.scraper.close(); | |
this.isInitialized = false; | |
} catch (error) { | |
this.logger.error("TwitterClient.close", "Error closing scraper", { | |
error, | |
}); |
private async sendTweet(data: TweetData) { | ||
try { | ||
const tweetEvent: TweetReceived = { | ||
type: "tweet_received", | ||
source: this.id, | ||
content: this.formatTweetContent(tweet), | ||
tweetId: tweet.id ?? "", | ||
userId: tweet.userId ?? "", | ||
username: tweet.username ?? "", | ||
timestamp: new Date(tweet.timestamp ?? ""), | ||
metadata: { | ||
metrics: { | ||
likes: tweet.likes, | ||
retweets: tweet.retweets, | ||
replies: tweet.replies, | ||
}, | ||
isRetweet: tweet.isRetweet, | ||
isReply: tweet.isReply, | ||
hasMedia: tweet.photos.length > 0 || tweet.videos.length > 0, | ||
url: tweet.permanentUrl, | ||
conversationId: tweet.conversationId, | ||
inReplyToId: tweet.inReplyToStatusId, | ||
}, | ||
}; | ||
// TODO: Implement actual tweet sending using scraper | ||
this.logger.info("TwitterClient.sendTweet", "Would send tweet", { data }); | ||
|
||
await this.core.emit(tweetEvent); | ||
return { | ||
success: true, | ||
tweetId: "mock-tweet-id", // Replace with actual tweet ID | ||
}; |
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.
Implement actual tweet sending logic in sendTweet
method
The sendTweet
method currently contains a placeholder and does not send tweets. Implement the actual logic to send tweets using the scraper or Twitter API to ensure the output functions correctly.
Apply this diff to start implementing the tweet sending logic:
private async sendTweet(data: TweetData) {
try {
- // TODO: Implement actual tweet sending using scraper
- this.logger.info("TwitterClient.sendTweet", "Would send tweet", { data });
+ const tweetId = await this.scraper.postTweet(data.content, {
+ inReplyToTweetId: data.inReplyTo,
+ });
+ this.logger.info("TwitterClient.sendTweet", "Tweet sent successfully", { tweetId });
return {
success: true,
- tweetId: "mock-tweet-id", // Replace with actual tweet ID
+ tweetId: tweetId,
};
} catch (error) {
this.logger.error("TwitterClient.sendTweet", "Error sending tweet", {
error,
});
throw error;
}
}
Ensure proper error handling and response mapping as per the scraper's implementation.
Committable suggestion skipped: line range outside the PR's diff.
tweet.userId !== this.credentials.username && | ||
(!this.lastCheckedTweetId || | ||
BigInt(tweet.id ?? "") > this.lastCheckedTweetId) | ||
) |
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.
Handle potential issues with tweet ID conversion to BigInt
Converting tweet.id
to BigInt
without ensuring it's a valid number may cause runtime errors. Add validations to ensure tweet.id
is defined and can be converted to BigInt
.
Apply this diff to improve error handling:
.filter(
(tweet) =>
tweet.userId !== this.credentials.username &&
(!this.lastCheckedTweetId ||
- BigInt(tweet.id ?? "") > this.lastCheckedTweetId)
+ (tweet.id && BigInt(tweet.id) > this.lastCheckedTweetId))
)
.map((tweet) => {
- this.lastCheckedTweetId = BigInt(tweet.id ?? "");
+ if (tweet.id) {
+ this.lastCheckedTweetId = BigInt(tweet.id);
+ }
return this.formatTweetData(tweet);
});
📝 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.
tweet.userId !== this.credentials.username && | |
(!this.lastCheckedTweetId || | |
BigInt(tweet.id ?? "") > this.lastCheckedTweetId) | |
) | |
tweet.userId !== this.credentials.username && | |
(!this.lastCheckedTweetId || | |
(tweet.id && BigInt(tweet.id) > this.lastCheckedTweetId)) | |
) | |
.map((tweet) => { | |
if (tweet.id) { | |
this.lastCheckedTweetId = BigInt(tweet.id); | |
} | |
return this.formatTweetData(tweet); | |
}); |
export interface ProcessedResult { | ||
content: any; | ||
metadata: Record<string, any>; | ||
enrichedContext: EnrichedContext; |
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 any
types with specific interfaces to improve type safety
The use of the any
type for content
and metadata
in ProcessedResult
reduces type safety and can lead to runtime errors. Defining specific interfaces for content
and metadata
will enhance type checking, maintainability, and self-documentation of the code.
this.logger.debug("EventProcessor.process", "Processing event", { | ||
type: event.type, | ||
source: event.source, | ||
async process(content: any, room: Room): Promise<ProcessedResult> { |
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
Update method signature to use specific content type instead of any
The process
method accepts content: any
, which weakens type safety. Defining a specific type or interface for content
will help catch errors at compile time and improve code readability.
const contentStr = | ||
typeof content === "string" ? content : JSON.stringify(content); | ||
|
||
const prompt = `Classify the following content and provide context: | ||
|
||
Content: "${event.content}" | ||
Source: ${event.source} | ||
Type: ${event.type} | ||
Metadata: ${JSON.stringify(event.metadata)} | ||
Content: "${contentStr}" | ||
|
||
Determine: | ||
1. What type of content this is (tweet, question, statement, etc.) | ||
2. What kind of response or action it requires | ||
1. What type of content this is (data, message, event, etc.) | ||
2. What kind of processing it requires | ||
3. Any relevant context | ||
|
||
Return JSON only: | ||
{ | ||
"contentType": "tweet|question|statement|etc", | ||
"requiresResponse": boolean, | ||
"contentType": "data|message|event|etc", | ||
"requiresProcessing": boolean, | ||
"context": { |
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.
Mitigate potential prompt injection vulnerabilities in classifyContent
Including user-provided contentStr
directly in LLM prompts without sanitization may expose the system to prompt injection attacks. Malicious input could manipulate the prompt to affect the LLM's behavior. Consider sanitizing or encoding contentStr
before including it in the prompt to prevent such vulnerabilities.
Apply this diff to sanitize the content using a helper method:
const prompt = `Classify the following content and provide context:
- Content: "${contentStr}"
+ Content: "${this.escapePromptContent(contentStr)}"
Determine:
1. What type of content this is (data, message, event, etc.)
2. What kind of processing it requires
3. Any relevant context
Return JSON only:
{
"contentType": "data|message|event|etc",
"requiresProcessing": boolean,
"context": {
"topic": "string",
"urgency": "high|medium|low",
"additionalContext": "string"
}
}`;
+ private escapePromptContent(content: string): string {
+ return content.replace(/[{}[\]<>\|`"]/g, '');
+ }
Committable suggestion skipped: line range outside the PR's diff.
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: 10
♻️ Duplicate comments (1)
packages/core/src/core/core.ts (1)
149-151
:⚠️ Potential issueImport
Ajv
properly and reuse the validator instance.Using
require
in TypeScript is discouraged. Instead, importAjv
at the top of the file and instantiate it once.import type { JSONSchemaType } from "ajv"; +import Ajv from "ajv"; export class Core { + private ajv: Ajv; private inputs: Map<string, Input & { lastRun?: number }> = new Map(); private outputs: Map<string, Output> = new Map(); constructor( roomManager: RoomManager, vectorDb: VectorDB, processor: Processor, config?: CoreConfig ) { // ... + this.ajv = new Ajv(); } public async executeOutput(name: string, data: any): Promise<any> { // ... - const validate = require("ajv")().compile(output.schema); + const validate = this.ajv.compile(output.schema); if (!validate(data)) { - throw new Error(`Invalid data for output ${name}: ${validate.errors}`); + throw new Error(`Invalid data for output ${name}: ${JSON.stringify(validate.errors, null, 2)}`); } // ... } }
🧹 Nitpick comments (4)
packages/core/src/core/core.ts (3)
12-14
: Consider strengthening type safety for the Input interface.The
response
type and handler arguments are currently usingany
. Consider using generics to make the interface type-safe:-export interface Input { +export interface Input<T = any, R = any> { name: string; - handler: (...args: any[]) => Promise<any>; - response: any; // Type of response expected + handler: (...args: T[]) => Promise<R>; + response: R; // Type of response expected interval?: number; // Time in milliseconds between runs, if not provided runs once }
19-22
: Consider strengthening type safety for the Output interface.Similar to the Input interface, the Output interface could benefit from generic types:
-export interface Output { +export interface Output<T = any, R = any> { name: string; - handler: (data: any) => Promise<any>; - response: any; // Type of response expected + handler: (data: T) => Promise<R>; + response: R; // Type of response expected - schema: JSONSchemaType<any>; // Schema to validate input data + schema: JSONSchemaType<T>; // Schema to validate input data }
197-199
: Enhance input/output removal with logging and cleanup.The current implementation lacks logging and proper cleanup. Consider adding these safety measures:
public removeInput(name: string): void { + this.logger.info("Core.removeInput", "Removing input", { name }); + const input = this.inputs.get(name); + if (!input) { + this.logger.warn("Core.removeInput", "Input not found", { name }); + return; + } this.inputs.delete(name); } public removeOutput(name: string): void { + this.logger.info("Core.removeOutput", "Removing output", { name }); + const output = this.outputs.get(name); + if (!output) { + this.logger.warn("Core.removeOutput", "Output not found", { name }); + return; + } this.outputs.delete(name); }Also applies to: 204-206
packages/core/src/clients/twitter.ts (1)
181-181
: Implement the actual tweet sending functionalityThe
sendTweet
method currently contains aTODO
and logs the intended action without sending the tweet. Implement the tweet sending logic using the scraper to complete this method.Do you want me to help implement the tweet sending functionality or open a new GitHub issue to track this task?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/core/src/clients/twitter.ts
(1 hunks)packages/core/src/core/core.ts
(2 hunks)
// Start input processing loop | ||
this.processInputs(); |
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
Consider implementing lifecycle management for input processing.
Starting the input processing loop directly in the constructor could cause issues:
- No way to stop the processing loop
- Potential problems during testing
- No error handling for the initial call
Consider implementing start/stop methods:
export class Core {
+ private isProcessing: boolean = false;
+
constructor(
roomManager: RoomManager,
vectorDb: VectorDB,
processor: Processor,
config?: CoreConfig
) {
// ... initialization ...
- this.processInputs();
}
+
+ public start(): void {
+ if (!this.isProcessing) {
+ this.isProcessing = true;
+ this.processInputs().catch(error => {
+ this.logger.error("Core.start", "Error in input processing loop", { error });
+ this.isProcessing = false;
+ });
+ }
+ }
+
+ public stop(): void {
+ this.isProcessing = false;
+ }
Committable suggestion skipped: line range outside the PR's diff.
packages/core/src/core/core.ts
Outdated
private async processInputs(): Promise<void> { | ||
while (true) { | ||
for (const [name, input] of this.inputs.entries()) { | ||
const now = Date.now(); | ||
|
||
if (!room) { | ||
room = await this.roomManager.createRoom(platformId, platform, { | ||
name: this.getRoomName(event), | ||
description: this.getRoomDescription(event), | ||
participants: this.getParticipants(event), | ||
metadata: event.metadata, | ||
}); | ||
} | ||
// Skip if not ready to run again | ||
if ( | ||
input.interval && | ||
input.lastRun && | ||
now - input.lastRun < input.interval | ||
) { | ||
continue; | ||
} | ||
|
||
return room; | ||
} | ||
try { | ||
this.logger.debug("Core.processInputs", "Processing input", { name }); | ||
|
||
private getPlatformId(event: ClientEvent): string { | ||
if (event.type === "tweet_received") { | ||
return ( | ||
(event as TweetReceived).metadata?.conversationId ?? | ||
(event as TweetReceived).tweetId | ||
); | ||
} | ||
return event.source; | ||
} | ||
const result = await input.handler(); | ||
|
||
private getPlatform(event: ClientEvent): string { | ||
return event.source; | ||
} | ||
// Update last run time | ||
this.inputs.set(name, { | ||
...input, | ||
lastRun: now, | ||
}); | ||
|
||
private getRoomName(event: ClientEvent): string { | ||
if (event.type === "tweet_received") { | ||
return `Twitter Thread ${(event as TweetReceived).tweetId}`; | ||
} | ||
return `${event.source} Room`; | ||
} | ||
// Store result in room memory | ||
if (result) { | ||
const room = await this.ensureRoom(name); | ||
|
||
const processed = await this.processor.process(result, room); | ||
|
||
await this.roomManager.addMemory(room.id, processed.content, { | ||
source: name, | ||
type: "input", | ||
...processed.metadata, | ||
...processed.enrichedContext, | ||
}); | ||
} | ||
} catch (error) { | ||
this.logger.error("Core.processInputs", "Error processing input", { | ||
name, | ||
error, | ||
}); | ||
} | ||
} | ||
|
||
private getRoomDescription(event: ClientEvent): string { | ||
if (event.type === "tweet_received") { | ||
return `Twitter conversation thread starting with tweet ${ | ||
(event as TweetReceived).tweetId | ||
}`; | ||
// Small delay between iterations | ||
await new Promise((resolve) => setTimeout(resolve, 1000)); | ||
} |
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
Improve input processing loop implementation.
The current implementation has several potential issues:
- Fixed 1-second delay might not be optimal for all use cases
- No backoff strategy for failed inputs
- Memory usage might grow unbounded
Consider implementing these improvements:
private async processInputs(): Promise<void> {
- while (true) {
+ while (this.isProcessing) {
+ const startTime = Date.now();
+ let processedCount = 0;
+
for (const [name, input] of this.inputs.entries()) {
const now = Date.now();
// Skip if not ready to run again
if (
input.interval &&
input.lastRun &&
now - input.lastRun < input.interval
) {
continue;
}
+ let backoffTime = 1000; // Start with 1 second
try {
this.logger.debug("Core.processInputs", "Processing input", { name });
const result = await input.handler();
+ processedCount++;
// Update last run time
this.inputs.set(name, {
...input,
lastRun: now,
+ consecutiveFailures: 0, // Reset failure count on success
});
// Store result in room memory
if (result) {
const room = await this.ensureRoom(name);
const processed = await this.processor.process(result, room);
await this.roomManager.addMemory(room.id, processed.content, {
source: name,
type: "input",
...processed.metadata,
...processed.enrichedContext,
});
}
} catch (error) {
+ // Implement exponential backoff
+ const currentInput = this.inputs.get(name);
+ const consecutiveFailures = (currentInput?.consecutiveFailures || 0) + 1;
+ backoffTime *= Math.min(Math.pow(2, consecutiveFailures), 60); // Cap at 60 seconds
+
+ this.inputs.set(name, {
+ ...input,
+ lastRun: now,
+ consecutiveFailures,
+ });
+
this.logger.error("Core.processInputs", "Error processing input", {
name,
error,
+ backoffTime,
+ consecutiveFailures,
});
}
}
- // Small delay between iterations
- await new Promise((resolve) => setTimeout(resolve, 1000));
+ // Dynamic delay based on processing time
+ const processingTime = Date.now() - startTime;
+ const minDelay = processedCount > 0 ? 1000 : 5000; // Longer delay if no inputs were processed
+ const delayTime = Math.max(minDelay - processingTime, 0);
+ await new Promise((resolve) => setTimeout(resolve, delayTime));
}
}
Committable suggestion skipped: line range outside the PR's diff.
packages/core/src/clients/twitter.ts
Outdated
this.logger.error( | ||
"TwitterClient.checkMentions", | ||
"Error checking mentions", | ||
{ error } |
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.
Avoid logging sensitive information in error messages
Similar to the previous comment, ensure that logging in checkMentions
does not expose sensitive data.
Update the error logging:
this.logger.error(
"TwitterClient.checkMentions",
"Error checking mentions",
- { error }
+ { error: error.message }
);
📝 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.
this.logger.error( | |
"TwitterClient.checkMentions", | |
"Error checking mentions", | |
{ error } | |
this.logger.error( | |
"TwitterClient.checkMentions", | |
"Error checking mentions", | |
{ error: error.message } |
packages/core/src/clients/twitter.ts
Outdated
this.isInitialized = true; | ||
this.logger.info("TwitterClient", "Initialized successfully"); | ||
} catch (error) { | ||
this.logger.error("TwitterClient", "Failed to initialize", { error }); |
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.
Avoid logging sensitive information in error messages
When logging errors, ensure that sensitive data is not exposed. Logging the entire error
object may include sensitive details. Log only the necessary information.
Modify the error logging to exclude sensitive data:
- this.logger.error("TwitterClient", "Failed to initialize", { error });
+ this.logger.error("TwitterClient", "Failed to initialize", { error: error.message });
📝 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.
this.logger.error("TwitterClient", "Failed to initialize", { error }); | |
this.logger.error("TwitterClient", "Failed to initialize", { error: error.message }); |
packages/core/src/clients/twitter.ts
Outdated
tweet.userId !== this.credentials.username && | ||
(!this.lastCheckedTweetId || | ||
BigInt(tweet.id ?? "") > this.lastCheckedTweetId) |
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.
Handle potential undefined tweet.id
to prevent runtime errors
Using BigInt(tweet.id ?? "")
can throw an error if tweet.id
is undefined
or empty. Converting an empty string to BigInt
results in a SyntaxError
. Ensure that tweet.id
is defined before conversion.
Apply this diff to fix the issue:
- (!this.lastCheckedTweetId ||
- BigInt(tweet.id ?? "") > this.lastCheckedTweetId)
+ tweet.id &&
+ (!this.lastCheckedTweetId ||
+ BigInt(tweet.id) > this.lastCheckedTweetId)
📝 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.
tweet.userId !== this.credentials.username && | |
(!this.lastCheckedTweetId || | |
BigInt(tweet.id ?? "") > this.lastCheckedTweetId) | |
tweet.userId !== this.credentials.username && | |
tweet.id && | |
(!this.lastCheckedTweetId || | |
BigInt(tweet.id) > this.lastCheckedTweetId) |
packages/core/src/clients/twitter.ts
Outdated
BigInt(tweet.id ?? "") > this.lastCheckedTweetId) | ||
) | ||
.map((tweet) => { | ||
this.lastCheckedTweetId = BigInt(tweet.id ?? ""); |
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.
Ensure tweet.id
is valid before assigning to lastCheckedTweetId
Assigning this.lastCheckedTweetId = BigInt(tweet.id ?? "")
without checking if tweet.id
is defined can cause a runtime error. Verify that tweet.id
is valid before the assignment.
Apply this diff to fix the issue:
- this.lastCheckedTweetId = BigInt(tweet.id ?? "");
+ if (tweet.id) {
+ this.lastCheckedTweetId = BigInt(tweet.id);
+ }
📝 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.
this.lastCheckedTweetId = BigInt(tweet.id ?? ""); | |
if (tweet.id) { | |
this.lastCheckedTweetId = BigInt(tweet.id); | |
} |
packages/core/src/clients/twitter.ts
Outdated
tweetId: tweet.id, | ||
userId: tweet.userId, | ||
username: tweet.username, | ||
timestamp: new Date(tweet.timestamp ?? ""), |
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.
Handle potential invalid tweet.timestamp
values
Creating a new Date
with tweet.timestamp ?? ""
may result in Invalid Date
if tweet.timestamp
is undefined
or empty. This could cause issues when using the timestamp later.
Consider validating the timestamp:
- timestamp: new Date(tweet.timestamp ?? ""),
+ timestamp: tweet.timestamp ? new Date(tweet.timestamp) : null,
📝 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.
timestamp: new Date(tweet.timestamp ?? ""), | |
timestamp: tweet.timestamp ? new Date(tweet.timestamp) : null, |
packages/core/src/clients/twitter.ts
Outdated
this.logger.error( | ||
"TwitterClient.fetchUserTweets", | ||
"Error fetching tweets", | ||
{ error } |
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.
Avoid logging sensitive information in error messages
In fetchUserTweets
, ensure error logging does not include sensitive details.
Modify the logging statement:
this.logger.error(
"TwitterClient.fetchUserTweets",
"Error fetching tweets",
- { error }
+ { error: error.message }
);
📝 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.
this.logger.error( | |
"TwitterClient.fetchUserTweets", | |
"Error fetching tweets", | |
{ error } | |
this.logger.error( | |
"TwitterClient.fetchUserTweets", | |
"Error fetching tweets", | |
{ error: error.message } |
packages/core/src/clients/twitter.ts
Outdated
this.logger.error("TwitterClient.sendTweet", "Error sending tweet", { | ||
error, | ||
}); |
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.
Avoid logging sensitive information in error messages
In sendTweet
, prevent exposing sensitive data in logs.
Adjust the error logging:
this.logger.error("TwitterClient.sendTweet", "Error sending tweet", {
- error,
+ error: error.message,
});
📝 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.
this.logger.error("TwitterClient.sendTweet", "Error sending tweet", { | |
error, | |
}); | |
this.logger.error("TwitterClient.sendTweet", "Error sending tweet", { | |
error: error.message, | |
}); |
packages/core/src/clients/twitter.ts
Outdated
private async initialize() { | ||
if (!this.isInitialized) { | ||
try { | ||
await this.scraper.login( | ||
this.credentials.username, | ||
this.credentials.password, | ||
this.credentials.email | ||
); | ||
this.isInitialized = true; | ||
this.logger.info("TwitterClient", "Initialized successfully"); | ||
} catch (error) { | ||
this.logger.error("TwitterClient", "Failed to initialize", { error }); | ||
throw error; | ||
} | ||
} | ||
} |
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
Prevent multiple concurrent initializations
The initialize
method may be called concurrently from different parts of the code, leading to multiple login attempts if this.isInitialized
is false
. This can cause redundant operations or potential conflicts. Implement a mechanism to ensure initialization happens only once.
You can use a promise to synchronize the initialization:
+ private initializingPromise: Promise<void> | null = null;
private async initialize() {
if (!this.isInitialized) {
+ if (this.initializingPromise) {
+ await this.initializingPromise;
+ return;
+ }
+ this.initializingPromise = (async () => {
try {
await this.scraper.login(
this.credentials.username,
this.credentials.password,
this.credentials.email
);
this.isInitialized = true;
this.logger.info("TwitterClient", "Initialized successfully");
} catch (error) {
this.logger.error("TwitterClient", "Failed to initialize", { error: error.message });
throw error;
} finally {
+ this.initializingPromise = null;
}
+ })();
+ await this.initializingPromise;
}
}
📝 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.
private async initialize() { | |
if (!this.isInitialized) { | |
try { | |
await this.scraper.login( | |
this.credentials.username, | |
this.credentials.password, | |
this.credentials.email | |
); | |
this.isInitialized = true; | |
this.logger.info("TwitterClient", "Initialized successfully"); | |
} catch (error) { | |
this.logger.error("TwitterClient", "Failed to initialize", { error }); | |
throw error; | |
} | |
} | |
} | |
private initializingPromise: Promise<void> | null = null; | |
private async initialize() { | |
if (!this.isInitialized) { | |
if (this.initializingPromise) { | |
await this.initializingPromise; | |
return; | |
} | |
this.initializingPromise = (async () => { | |
try { | |
await this.scraper.login( | |
this.credentials.username, | |
this.credentials.password, | |
this.credentials.email | |
); | |
this.isInitialized = true; | |
this.logger.info("TwitterClient", "Initialized successfully"); | |
} catch (error) { | |
this.logger.error("TwitterClient", "Failed to initialize", { error: error.message }); | |
throw error; | |
} finally { | |
this.initializingPromise = null; | |
} | |
})(); | |
await this.initializingPromise; | |
} | |
} |
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: 0
♻️ Duplicate comments (5)
packages/core/src/core/core.ts (3)
59-60
: 🛠️ Refactor suggestionImplement proper lifecycle management.
Starting the input processing loop in the constructor could cause issues with testing and error handling. Consider implementing explicit start/stop methods.
88-170
: 🛠️ Refactor suggestionEnhance robustness of input processing loop.
Several improvements could make the processing loop more resilient:
- The confidence threshold (0.7) should be configurable
- The loop delay is fixed at 1 second
- No exponential backoff for failed inputs
Additionally, there's a TODO comment that needs attention:
JSON.stringify(processed.content), // TODO: Fix this
185-187
: 🛠️ Refactor suggestionOptimize Ajv usage for better performance.
Creating a new Ajv instance on each validation is inefficient. Consider:
- Importing Ajv properly at the top of the file
- Creating a single instance as a class property
packages/core/src/core/processor.ts (2)
126-127
:⚠️ Potential issueSanitize content before including in LLM prompt.
Including raw content in the prompt could expose the system to prompt injection attacks.
193-195
: 🛠️ Refactor suggestionOptimize Ajv usage across validation calls.
Creating new Ajv instances for each validation is inefficient. Consider moving the instance to a class property.
🧹 Nitpick comments (6)
packages/core/src/core/core.ts (1)
10-22
: Consider strengthening type safety for Input/Output interfaces.The interfaces use
any
types which could lead to runtime errors. Consider:
- Creating specific types for the response property
- Using generics to enforce type consistency between handler and response
-export interface Input { +export interface Input<T = unknown> { name: string; - handler: (...args: any[]) => Promise<any>; - response: any; + handler: (...args: unknown[]) => Promise<T>; + response: T; interval?: number; } -export interface Output { +export interface Output<T = unknown, R = unknown> { name: string; - handler: (data: any) => Promise<any>; - response: any; + handler: (data: T) => Promise<R>; + response: R; schema: JSONSchemaType<any>; }packages/core/src/core/processor.ts (1)
174-174
: Remove debug console.log statement.Production code should not contain console.log statements.
- console.log("response", response);
packages/core/src/clients/twitter.ts (2)
75-79
: Improve type safety with proper TypeScript types.Replace string literals with proper TypeScript types for better type safety and IDE support.
- response: { - type: "string", - content: "string", - metadata: "object", - }, + response: { + type: string; + content: string; + metadata: Record<string, unknown>; + },
179-194
: Implement actual tweet sending functionality.The method currently only logs and returns mock data. Would you like me to help implement the actual tweet sending functionality using the scraper?
examples/example-twitter.ts (2)
72-73
: Consider making intervals configurable via environment variables.Hardcoded intervals for checking mentions and timelines could be more flexible if configurable.
- interval: 60000, + interval: parseInt(process.env.MENTIONS_CHECK_INTERVAL ?? "60000"),Also applies to: 90-91
76-76
: Move account list to configuration.Consider moving the hardcoded account list to a configuration file or environment variable for easier maintenance.
-const accountsToMonitor = ["elonmusk", "sama", "naval"]; +const accountsToMonitor = (process.env.MONITORED_ACCOUNTS ?? "").split(",").filter(Boolean);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
examples/example-twitter.ts
(1 hunks)package.json
(1 hunks)packages/core/src/clients/twitter.ts
(1 hunks)packages/core/src/core/core.ts
(2 hunks)packages/core/src/core/processor.ts
(9 hunks)
🔇 Additional comments (8)
packages/core/src/core/processor.ts (1)
10-15
: Improve type safety in ProcessedResult interface.Using
any
types reduces type safety. Consider creating specific interfaces for content and metadata.packages/core/src/clients/twitter.ts (6)
19-28
: LGTM! Well-structured schema definition.The schema properly validates the TweetData interface and ensures type safety.
48-63
: Prevent multiple concurrent initializations.The initialize method may be called concurrently from different parts of the code.
59-59
: Avoid logging sensitive information in error messages.When logging errors, ensure that sensitive data is not exposed.
144-146
: Handle potential undefinedtweet.id
to prevent runtime errors.Using
BigInt(tweet.id ?? "")
can throw an error iftweet.id
isundefined
or empty.
204-204
: Handle potential invalidtweet.timestamp
values.Creating a new
Date
withtweet.timestamp ?? ""
may result inInvalid Date
.
6-16
: Consider using a more secure credential management approach.Storing sensitive credentials in memory could pose security risks. Consider using environment variables or a secure credential manager.
package.json (1)
6-7
: LGTM! Script addition is appropriate.The new Twitter script is well-integrated with existing scripts.
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: 1
♻️ Duplicate comments (3)
packages/core/src/core/core.ts (3)
59-60
:⚠️ Potential issueImplement proper lifecycle management.
Starting the input processing loop in the constructor could cause issues with testing and resource cleanup.
Consider implementing start/stop methods as suggested in the previous review.
93-169
: 🛠️ Refactor suggestionImprove input processing robustness.
The current implementation has several potential issues:
- No graceful shutdown mechanism
- Fixed delay regardless of processing time
- Hard-coded confidence threshold (0.7)
Consider implementing the backoff strategy and dynamic delay as suggested in the previous review.
Additionally, make the confidence threshold configurable:
+ private readonly DEFAULT_CONFIDENCE_THRESHOLD = 0.7; + constructor( roomManager: RoomManager, vectorDb: VectorDB, processor: Processor, config?: CoreConfig ) { this.roomManager = roomManager; this.vectorDb = vectorDb; + this.confidenceThreshold = config?.confidenceThreshold ?? this.DEFAULT_CONFIDENCE_THRESHOLD;
184-186
: 🛠️ Refactor suggestionOptimize schema validation.
Creating a new Ajv instance for each validation is inefficient.
Consider implementing the suggested Ajv optimization from the previous review.
🧹 Nitpick comments (3)
packages/core/src/core/core.ts (3)
10-14
: Consider adding type parameters to the Input interface.The interface could benefit from stronger typing:
-export interface Input { +export interface Input<T extends any[] = any[], R = any> { name: string; - handler: (...args: any[]) => Promise<any>; - response: any; // Type of response expected + handler: (...args: T) => Promise<R>; + response: R; // Type of response expected interval?: number; // Time in milliseconds between runs, if not provided runs once }
18-22
: Consider adding type parameters to the Output interface.Similar to Input, the Output interface could be more type-safe:
-export interface Output { +export interface Output<T = any, R = any> { name: string; - handler: (data: any) => Promise<any>; - response: any; // Type of response expected - schema: JSONSchemaType<any>; // Schema to validate input data + handler: (data: T) => Promise<R>; + response: R; // Type of response expected + schema: JSONSchemaType<T>; // Schema to validate input data }
247-249
: Enhance resource cleanup in removal methods.The current implementation might leave dangling resources.
public removeInput(name: string): void { + const input = this.inputs.get(name); + if (!input) { + this.logger.warn("Core.removeInput", "Input not found", { name }); + return; + } this.inputs.delete(name); + this.logger.info("Core.removeInput", "Input removed", { name }); } public removeOutput(name: string): void { + const output = this.outputs.get(name); + if (!output) { + this.logger.warn("Core.removeOutput", "Output not found", { name }); + return; + } this.outputs.delete(name); + this.processor.unregisterAvailableOutput(output); + this.logger.info("Core.removeOutput", "Output removed", { name }); }Also applies to: 254-255
|
||
await this.roomManager.addMemory( | ||
room.id, | ||
JSON.stringify(processed.content), // TODO: Fix this everything being dumped into memory |
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.
Remove TODO comment and implement proper serialization.
The TODO comment indicates a known issue with memory storage.
- JSON.stringify(processed.content), // TODO: Fix this everything being dumped into memory
+ typeof processed.content === 'string'
+ ? processed.content
+ : JSON.stringify(processed.content),
📝 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.
JSON.stringify(processed.content), // TODO: Fix this everything being dumped into memory | |
typeof processed.content === 'string' | |
? processed.content | |
: JSON.stringify(processed.content), |
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: 4
🧹 Nitpick comments (2)
packages/core/src/io/twitter.ts (1)
76-79
: Refineresponse
type definitions for better clarityThe
response
objects increateMentionsInput
andcreateTimelineInput
specify types as strings, which may not accurately represent the actual data structure. Consider defining explicit TypeScript interfaces or using more precise types to improve type safety and code readability.For example, you could define a response type:
interface TweetResponse { type: string; content: string; metadata: Record<string, any>; }And update the
response
property:return { name: "twitter_mentions", handler: async () => { /*...*/ }, - response: { - type: "string", - content: "string", - metadata: "object", - }, + response: TweetResponse, interval, };Also applies to: 96-99
examples/example-twitter.ts (1)
102-109
: Avoid casting toany
in JSON schema definitionsCasting the schema to
JSONSchemaType<any>
defeats the purpose of type checking and can lead to runtime errors. Define a proper type for your schema to ensure type safety.Define a specific interface for your output data:
interface TwitterReplyData { content: string; inReplyTo: string; }Then, define the schema using
JSONSchemaType<TwitterReplyData>
:const twitterReplySchema: JSONSchemaType<TwitterReplyData> = { type: "object", properties: { content: { type: "string" }, inReplyTo: { type: "string" }, }, required: ["content", "inReplyTo"], additionalProperties: false, };Update the output registration to use this schema:
core.registerOutput({ name: "twitter_reply", handler: async (data: TwitterReplyData) => { return twitter.createTweetOutput().handler(data); }, response: { success: "boolean", tweetId: "string", }, - schema: { /*...*/ } as unknown as JSONSchemaType<any>, + schema: twitterReplySchema, });
private async sendTweet(data: TweetData) { | ||
try { | ||
// TODO: Implement actual tweet sending using scraper | ||
this.logger.info("TwitterClient.sendTweet", "Would send tweet", { data }); | ||
|
||
return { | ||
success: true, | ||
tweetId: "mock-tweet-id", // Replace with actual tweet ID | ||
}; | ||
} catch (error) { | ||
this.logger.error("TwitterClient.sendTweet", "Error sending tweet", { | ||
error, | ||
}); | ||
throw error; | ||
} | ||
} |
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.
Implement sendTweet
or adjust the success response
The sendTweet
method currently does not send a tweet but returns success: true
and a mock tweet ID. This could mislead calling code into believing the tweet was successfully sent.
Consider implementing the actual tweet sending functionality or adjusting the response to accurately reflect the operation's outcome. If you need assistance with the implementation, I can help develop the code to send tweets using the Scraper
library. Would you like me to open a new GitHub issue for this task?
(!this.lastCheckedTweetId || | ||
BigInt(tweet.id ?? "") > this.lastCheckedTweetId) | ||
) | ||
.map((tweet) => { | ||
this.lastCheckedTweetId = BigInt(tweet.id ?? ""); |
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.
Ensure tweet.id
is defined before converting to BigInt
Using BigInt(tweet.id ?? "")
may throw a runtime error if tweet.id
is null
, undefined
, or an empty string. To prevent this, check if tweet.id
is defined before converting it to BigInt
.
Apply this diff to add a check:
.filter(
(tweet) =>
tweet.userId !== this.credentials.username &&
+ tweet.id &&
(!this.lastCheckedTweetId ||
- BigInt(tweet.id ?? "") > this.lastCheckedTweetId)
+ BigInt(tweet.id) > this.lastCheckedTweetId)
)
And update the assignment:
- this.lastCheckedTweetId = BigInt(tweet.id ?? "");
+ if (tweet.id) {
+ this.lastCheckedTweetId = BigInt(tweet.id);
+ }
📝 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.
(!this.lastCheckedTweetId || | |
BigInt(tweet.id ?? "") > this.lastCheckedTweetId) | |
) | |
.map((tweet) => { | |
this.lastCheckedTweetId = BigInt(tweet.id ?? ""); | |
tweet.id && | |
(!this.lastCheckedTweetId || | |
BigInt(tweet.id) > this.lastCheckedTweetId) | |
) | |
.map((tweet) => { | |
if (tweet.id) { | |
this.lastCheckedTweetId = BigInt(tweet.id); | |
} |
const llmClient = new LLMClient({ | ||
provider: "anthropic", | ||
apiKey: env.ANTHROPIC_API_KEY, | ||
}); |
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
Validate environment variables before use
Environment variables like ANTHROPIC_API_KEY
, TWITTER_USERNAME
, TWITTER_PASSWORD
, and TWITTER_EMAIL
are used without prior validation. If any of these variables are undefined, it could lead to runtime errors.
Add validation to ensure that all required environment variables are defined:
const { ANTHROPIC_API_KEY, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_EMAIL } = env;
if (!ANTHROPIC_API_KEY) {
throw new Error("Environment variable ANTHROPIC_API_KEY is not defined");
}
if (!TWITTER_USERNAME || !TWITTER_PASSWORD || !TWITTER_EMAIL) {
throw new Error("Twitter credentials are not fully defined in environment variables");
}
Then, use these validated variables in your clients:
const llmClient = new LLMClient({
provider: "anthropic",
apiKey: ANTHROPIC_API_KEY,
});
const twitter = new TwitterClient(
{
username: TWITTER_USERNAME,
password: TWITTER_PASSWORD,
email: TWITTER_EMAIL,
},
LogLevel.DEBUG
);
Also applies to: 47-50
return twitter.createTimelineInput(account, 300000).handler(); | ||
}, |
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.
Ensure correct usage of handler
function in input registration
In the handler
function for monitoring timelines, you're calling twitter.createTimelineInput(account, 300000).handler()
. This might not behave as expected since createTimelineInput
is intended to return an input configuration, not to be called directly.
Adjust the code to correctly use the handler
provided by the createTimelineInput
method:
return twitter.createTimelineInput(account, 300000).handler();
Should be:
const timelineInput = twitter.createTimelineInput(account, 300000);
return timelineInput.handler();
Or directly register the input returned by createTimelineInput
:
- core.registerInput({
- name: `twitter_timeline_${account}`,
- handler: async () => {
- console.log(chalk.blue(`📱 Checking ${account}'s timeline...`));
- return twitter.createTimelineInput(account, 300000).handler();
- },
- response: { /*...*/ },
- interval: 300000,
- });
+ const timelineInput = twitter.createTimelineInput(account, 300000);
+ core.registerInput(timelineInput);
This ensures that the input is registered with the correct configuration and interval.
Committable suggestion skipped: line range outside the PR's diff.
Simplifies the core to purely inputs and outputs. Modifies the client classes to be utility classes. Drastic simplification.
Summary by CodeRabbit
Release Notes
Architecture Changes
New Features
Improvements
Refactoring