Skip to content
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

feat: add action versioning support #1241

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

himanshu-dixit
Copy link
Collaborator

@himanshu-dixit himanshu-dixit commented Feb 2, 2025

  • Write test

Important

Adds action versioning support with lock file management in ComposioToolSet, updating schema and execution processes.

  • Behavior:
    • Adds action versioning support in ComposioToolSet by introducing actionVersionMap and lockFile configuration.
    • Updates getToolsSchema() and executeAction() in base.toolset.ts to handle action versions.
    • Modifies execute() in Entity.ts to include version parameter.
    • Updates list() and execute() in actions.ts to support action versions.
  • Utilities:
    • Adds loadLockFile and saveLockFile in load.ts for reading and writing lock files.
    • Implements updateLockFileWithActionVersion and getVersionsFromLockFileAsJson in lockFile.ts for managing action versions.
    • Introduces actionLockProcessor in action_lock.ts to update lock files during schema processing.
  • Types:
    • Adds version and availableVersions to ZRawActionSchema in base_toolset.ts.
    • Updates ZExecuteActionParams in entity.ts and action.ts to include version.

This description was created by Ellipsis for f9bc94a. It will automatically update as commits are pushed.

Copy link

vercel bot commented Feb 2, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
composio ❌ Failed (Inspect) Feb 2, 2025 9:44pm

Comment on lines +89 to +91
* @param {Object} config.lockFile - Lock file configuration
* @param {string} config.lockFile.path - Path to the lock file, Default: .composio.lock
* @param {boolean} config.lockFile.lock - Whether to lock the file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lockFile.lock parameter in JSDoc doesn't match the actual parameter name lockFile.save in the code, causing incorrect documentation

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
* @param {Object} config.lockFile - Lock file configuration
* @param {string} config.lockFile.path - Path to the lock file, Default: .composio.lock
* @param {boolean} config.lockFile.lock - Whether to lock the file
* @param {Object} config.lockFile - Lock file configuration
* @param {string} config.lockFile.path - Path to the lock file, Default: .composio.lock
* @param {boolean} config.lockFile.save - Whether to lock the file

Comment on lines +138 to +143
if (!this.lockFile.path) {
CEG.getCustomError(COMPOSIO_SDK_ERROR_CODES.SDK.INVALID_PARAMETER, {
message: "Lock file path is required when save is true",
description: "Lock file path is required when save is true",
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error throw after generating error with CEG.getCustomError(). The error is created but never thrown, allowing execution to continue with invalid state

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if (!this.lockFile.path) {
CEG.getCustomError(COMPOSIO_SDK_ERROR_CODES.SDK.INVALID_PARAMETER, {
message: "Lock file path is required when save is true",
description: "Lock file path is required when save is true",
});
}
if (!this.lockFile.path) {
throw CEG.getCustomError(COMPOSIO_SDK_ERROR_CODES.SDK.INVALID_PARAMETER, {
message: "Lock file path is required when save is true",
description: "Lock file path is required when save is true",
});
}

Comment on lines +3 to +14
export const loadLockFile = (filePath: string) => {
const fileContent = fs.readFileSync(filePath, "utf8");
return JSON.parse(fileContent);
};

export const saveLockFile = (
filePath: string,
actionName: string,
version: string
) => {
fs.writeFileSync(filePath, JSON.stringify({ actionName, version }, null, 2));
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for file operations. Wrap fs.readFileSync(), fs.writeFileSync(), and JSON.parse() in try-catch blocks to handle errors gracefully

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
export const loadLockFile = (filePath: string) => {
const fileContent = fs.readFileSync(filePath, "utf8");
return JSON.parse(fileContent);
};
export const saveLockFile = (
filePath: string,
actionName: string,
version: string
) => {
fs.writeFileSync(filePath, JSON.stringify({ actionName, version }, null, 2));
};
export const loadLockFile = (filePath: string) => {
try {
const fileContent = fs.readFileSync(filePath, "utf8");
return JSON.parse(fileContent);
} catch (error) {
throw new Error(`Failed to load lock file: ${error.message}`);
}
};
export const saveLockFile = (
filePath: string,
actionName: string,
version: string
) => {
try {
fs.writeFileSync(filePath, JSON.stringify({ actionName, version }, null, 2));
} catch (error) {
throw new Error(`Failed to save lock file: ${error.message}`);
}
};

Comment on lines +15 to +46
const lockFileContent = require("fs").readFileSync(filePath, "utf8");
const actionVersions: Record<string, string> = {};
const lines = lockFileContent.split("\n");
for (const line of lines) {
if (line) {
const [actionName, version] = line.split("=");
actionVersions[actionName] = version;
}
}
return actionVersions;
} catch (e) {
const error = e as NodeJS.ErrnoException;
if (error.code === "ENOENT") {
logger.warn("Lock file does not exist, creating new one");
} else if (error.code === "EACCES" || error.code === "EPERM") {
logger.error("Permission denied accessing lock file", e);
} else {
logger.warn("Error reading lock file", e);
}
return {};
}
};

export const saveLockFile = (
filePath: string,
actionVersions: Record<string, string>
) => {
try {
const lockFileContent = Object.entries(actionVersions)
.map(([actionName, version]) => `${actionName}=${version}`)
.join("\n");
require("fs").writeFileSync(filePath, lockFileContent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock file read/write operations use dynamic require('fs') which is not recommended. Import fs at the top level instead using import * as fs from 'fs' for better performance and static analysis.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const lockFileContent = require("fs").readFileSync(filePath, "utf8");
const actionVersions: Record<string, string> = {};
const lines = lockFileContent.split("\n");
for (const line of lines) {
if (line) {
const [actionName, version] = line.split("=");
actionVersions[actionName] = version;
}
}
return actionVersions;
} catch (e) {
const error = e as NodeJS.ErrnoException;
if (error.code === "ENOENT") {
logger.warn("Lock file does not exist, creating new one");
} else if (error.code === "EACCES" || error.code === "EPERM") {
logger.error("Permission denied accessing lock file", e);
} else {
logger.warn("Error reading lock file", e);
}
return {};
}
};
export const saveLockFile = (
filePath: string,
actionVersions: Record<string, string>
) => {
try {
const lockFileContent = Object.entries(actionVersions)
.map(([actionName, version]) => `${actionName}=${version}`)
.join("\n");
require("fs").writeFileSync(filePath, lockFileContent);
const lockFileContent = fs.readFileSync(filePath, "utf8");
const actionVersions: Record<string, string> = {};
const lines = lockFileContent.split("\n");
for (const line of lines) {
if (line) {
const [actionName, version] = line.split("=");
actionVersions[actionName] = version;
}
}
return actionVersions;
} catch (e) {
const error = e as NodeJS.ErrnoException;
if (error.code === "ENOENT") {
logger.warn("Lock file does not exist, creating new one");
} else if (error.code === "EACCES" || error.code === "EPERM") {
logger.error("Permission denied accessing lock file", e);
} else {
logger.warn("Error reading lock file", e);
}
return {};
}
};
export const saveLockFile = (
filePath: string,
actionVersions: Record<string, string>
) => {
try {
const lockFileContent = Object.entries(actionVersions)
.map(([actionName, version]) => `${actionName}=${version}`)
.join("\n");
fs.writeFileSync(filePath, lockFileContent);

Comment on lines +3 to +11
export const updateLockFileWithActionVersion = (
filePath: string,
actionName: string,
version: string
) => {
const actionVersions = getVersionsFromLockFileAsJson(filePath);
actionVersions[actionName] = version;
saveLockFile(filePath, actionVersions);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing input validation for actionName and version parameters could allow injection of malicious content into the lock file. Add validation to ensure they don't contain = or newline characters.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
export const updateLockFileWithActionVersion = (
filePath: string,
actionName: string,
version: string
) => {
const actionVersions = getVersionsFromLockFileAsJson(filePath);
actionVersions[actionName] = version;
saveLockFile(filePath, actionVersions);
};
export const updateLockFileWithActionVersion = (
filePath: string,
actionName: string,
version: string
) => {
if (actionName.includes('=') || actionName.includes('\n') || version.includes('=') || version.includes('\n')) {
throw new Error('Action name and version must not contain "=" or newline characters');
}
const actionVersions = getVersionsFromLockFileAsJson(filePath);
actionVersions[actionName] = version;
saveLockFile(filePath, actionVersions);
};

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Changes requested. Reviewed everything up to 9e9c773 in 1 minute and 41 seconds

More details
  • Looked at 247 lines of code in 7 files
  • Skipped 0 files when reviewing.
  • Skipped posting 2 drafted comments based on config settings.
1. js/src/sdk/utils/lock/load.ts:13
  • Draft comment:
    The saveLockFile function is saving the lock file content as a JSON object with actionName and version keys, which is inconsistent with the format used in lockFile.ts. Consider updating it to match the format used in lockFile.ts.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable:
    The comment might be correct, but we have no way to verify this without seeing lockFile.ts. Following our rules, we should not keep comments that require additional context to verify. The comment is about a cross-file consistency issue, which our rules say to ignore.
    The inconsistency could be a real issue that causes runtime problems. Maybe we should ask to see lockFile.ts?
    No - our rules explicitly state to ignore cross-file issues and to delete comments if we need more context to verify them. We must stick to these principles.
    Delete this comment because it requires knowledge of another file to verify, and we're instructed to ignore cross-file issues.
2. js/src/sdk/utils/processor/action_lock.ts:13
  • Draft comment:
    The actionLockProcessor function currently returns the toolSchema without any processing. If processing is intended, consider implementing the necessary logic.
  • Reason this comment was not posted:
    Confidence changes required: 50%
    The actionLockProcessor function currently does not perform any processing on the toolSchema. It simply returns the toolSchema as is. This might be intentional, but if processing is expected, this function needs to be implemented.

Workflow ID: wflow_fDh5a2oC8ZlJpFmZ


Want Ellipsis to fix these issues? Tag @ellipsis-dev in a comment. You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

@@ -79,19 +86,27 @@ export class ComposioToolSet {
* @param {string|null} config.runtime - Runtime environment
* @param {string} config.entityId - Entity ID for operations
* @param {Record<string, string>} config.connectedAccountIds - Map of app names to their connected account IDs
* @param {Object} config.lockFile - Lock file configuration
* @param {string} config.lockFile.path - Path to the lock file, Default: .composio.lock
* @param {boolean} config.lockFile.lock - Whether to lock the file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lockFile parameter documentation mentions a lock property, but the code uses a save property. Update the documentation to reflect the correct property name.

Copy link

github-actions bot commented Feb 2, 2025

This comment was generated by github-actions[bot]!

JS SDK Coverage Report

📊 Coverage report for JS SDK can be found at the following URL:
https://pub-92e668239ab84bfd80ee07d61e9d2f40.r2.dev/coverage-13102751726/coverage/index.html

📁 Test report folder can be found at the following URL:
https://pub-92e668239ab84bfd80ee07d61e9d2f40.r2.dev/html-report-13102751726/html-report/report.html

CEG.getCustomError(COMPOSIO_SDK_ERROR_CODES.SDK.INVALID_PARAMETER, {
message: "Lock file path is required when save is true",
description: "Lock file path is required when save is true",
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling here is incorrect. The error is created but not thrown, which means the code will continue executing with an invalid state. You should either throw the error or handle it appropriately:

if (!this.lockFile.path) {
  throw CEG.getCustomError(COMPOSIO_SDK_ERROR_CODES.SDK.INVALID_PARAMETER, {
    message: "Lock file path is required when save is true",
    description: "Lock file path is required when save is true",
  });
}


export const getVersionsFromLockFileAsJson = (filePath: string) => {
try {
const lockFileContent = require("fs").readFileSync(filePath, "utf8");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using require('fs') directly is not recommended. Instead, import the fs module at the top of the file:

import fs from 'fs';

This makes the code more maintainable and follows better module import practices.

toolSchema: RawActionData;
}
): RawActionData => {
return toolSchema;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actionLockProcessor currently returns the toolSchema without any modification. If this is intended to be a placeholder for future version locking logic, it should be documented:

export const actionLockProcessor = (
  filePath: string,
  {
    actionName,
    toolSchema,
  }: {
    actionName: string;
    toolSchema: RawActionData;
  }
): RawActionData => {
  // TODO: Implement version locking logic
  return toolSchema;
};

@@ -79,19 +86,27 @@ export class ComposioToolSet {
* @param {string|null} config.runtime - Runtime environment
* @param {string} config.entityId - Entity ID for operations
* @param {Record<string, string>} config.connectedAccountIds - Map of app names to their connected account IDs
* @param {Object} config.lockFile - Lock file configuration
* @param {string} config.lockFile.path - Path to the lock file, Default: .composio.lock
* @param {boolean} config.lockFile.lock - Whether to lock the file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lock file configuration in the constructor's JSDoc is incorrect. The parameter is documented as lock but the actual property is save:

* @param {Object} config.lockFile - Lock file configuration
* @param {string} config.lockFile.path - Path to the lock file, Default: .composio.lock
* @param {boolean} config.lockFile.save - Whether to save version information to the lock file

@shreysingla11
Copy link
Collaborator

Code Review Summary

Overall Assessment

The PR introduces action versioning support with a lock file mechanism, which is a valuable addition. However, there are several issues that need to be addressed:

Critical Issues

  1. Incorrect error handling in the constructor when validating lock file path
  2. Incomplete implementation of actionLockProcessor
  3. Documentation mismatch in constructor parameters

Code Quality: 6/10

  • Good separation of concerns and file organization
  • Clear type definitions and interfaces
  • Error handling needs improvement
  • Some inconsistent practices in module imports
  • Missing documentation in key areas

Recommendations

  1. Fix error handling in constructor
  2. Complete or document TODO items in actionLockProcessor
  3. Update JSDoc to match implementation
  4. Use consistent module import practices
  5. Add more comprehensive error handling for file operations

Please address these issues before merging to ensure code quality and maintainability.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me! Incremental review on f9bc94a in 29 seconds

More details
  • Looked at 139 lines of code in 6 files
  • Skipped 0 files when reviewing.
  • Skipped posting 3 drafted comments based on config settings.
1. js/src/sdk/base.toolset.ts:129
  • Draft comment:
    The PR description indicates that tests are not yet written. It's important to include tests to ensure the new feature works correctly and does not introduce regressions.
  • Reason this comment was not posted:
    Comment did not seem useful.
2. js/src/sdk/base.toolset.ts:224
  • Draft comment:
    Using optional chaining with schema?.name and schema?.version might be unnecessary if RawActionData guarantees these properties are defined. Consider removing the optional chaining if these properties are always present.
  • Reason this comment was not posted:
    Confidence changes required: 50%
    The code uses optional chaining with schema?.name and schema?.version, but schema is already defined as RawActionData, which should have these properties. This might be unnecessary unless RawActionData allows these properties to be undefined.
3. js/src/sdk/base.toolset.ts:321
  • Draft comment:
    Ensure that actionVersionMap keys are consistently stored and accessed in uppercase to avoid potential mismatches.
  • Reason this comment was not posted:
    Comment did not seem useful.

Workflow ID: wflow_mIK42P7XRkh8P5dM


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

Comment on lines +222 to +224
if (schema?.version) {
// store the version in the map for execution
this.actionVersionMap[schema?.name.toUpperCase()] = schema?.version;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential runtime error if schema?.name is undefined when accessing toUpperCase(). Should add null check or use optional chaining.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if (schema?.version) {
// store the version in the map for execution
this.actionVersionMap[schema?.name.toUpperCase()] = schema?.version;
if (schema?.version && schema?.name) {
// store the version in the map for execution
this.actionVersionMap[schema.name.toUpperCase()] = schema.version;
}

Comment on lines 79 to 83
params,
text,
connectedAccountId,
version,
}: ExecuteActionParams): Promise<ActionExecuteResponse> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version parameter is added to the interface but lacks type definition and validation, which could lead to runtime errors if invalid values are passed.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
params,
text,
connectedAccountId,
version,
}: ExecuteActionParams): Promise<ActionExecuteResponse> {
params,
text,
connectedAccountId,
version?: string | number,
}: ExecuteActionParams): Promise<ActionExecuteResponse> {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants