Skip to content

Commit

Permalink
feat: add action versioning support
Browse files Browse the repository at this point in the history
  • Loading branch information
himanshu-dixit committed Feb 2, 2025
1 parent de3e119 commit 9e9c773
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 2 deletions.
39 changes: 37 additions & 2 deletions js/src/sdk/base.toolset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { Triggers } from "./models/triggers";
import { getUserDataJson } from "./utils/config";
import { CEG } from "./utils/error";
import { COMPOSIO_SDK_ERROR_CODES } from "./utils/errors/src/constants";
import { getVersionsFromLockFileAsJson } from "./utils/lockFile";
import { actionLockProcessor } from "./utils/processor/action_lock";
import {
fileInputProcessor,
fileResponseProcessor,
Expand Down Expand Up @@ -55,6 +57,11 @@ export class ComposioToolSet {

userActionRegistry: ActionRegistry;

lockFile: {
path: string | null;
save: boolean;
};

private internalProcessors: {
pre: TPreProcessor[];
post: TPostProcessor[];
Expand All @@ -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
*/
constructor({
apiKey,
baseUrl,
runtime,
entityId,
connectedAccountIds,
lockFile,
}: {
apiKey?: string | null;
baseUrl?: string | null;
runtime?: string | null;
entityId?: string;
connectedAccountIds?: Record<string, string>;
lockFile?: {
path: string;
save: boolean;
};
} = {}) {
const clientApiKey: string | undefined =
apiKey ||
Expand All @@ -114,6 +129,22 @@ export class ComposioToolSet {
this.activeTriggers = this.client.activeTriggers;
this.connectedAccountIds = connectedAccountIds || {};

this.lockFile = lockFile || {
path: ".composio.lock",
save: false,
};

if (this.lockFile.save) {
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",
});
}
const actionLock = actionLockProcessor.bind(this, this.lockFile.path);
this.internalProcessors.schema.push(actionLock);
}

this.userActionRegistry = new ActionRegistry(this.client);

if (entityId && connectedAccountIds) {
Expand Down Expand Up @@ -149,13 +180,17 @@ export class ComposioToolSet {
): Promise<RawActionData[]> {
const parsedFilters = ZToolSchemaFilter.parse(filters);

const apps = await this.client.actions.list({
const actionVersions = this.lockFile.path
? getVersionsFromLockFileAsJson(this.lockFile.path)
: {};
const actions = await this.client.actions.list({
apps: parsedFilters.apps?.join(","),
tags: parsedFilters.tags?.join(","),
useCase: parsedFilters.useCase,
actions: parsedFilters.actions?.join(","),
usecaseLimit: parsedFilters.useCaseLimit,
filterByAvailableApps: parsedFilters.filterByAvailableApps,
actionVersions: actionVersions,
});

const customActions = await this.userActionRegistry.getAllActions();
Expand All @@ -171,7 +206,7 @@ export class ComposioToolSet {
);
});

const toolsActions = [...(apps?.items || []), ...toolsWithCustomActions];
const toolsActions = [...(actions?.items || []), ...toolsWithCustomActions];

const allSchemaProcessor = [
...this.internalProcessors.schema,
Expand Down
1 change: 1 addition & 0 deletions js/src/sdk/models/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export class Actions {
showEnabledOnly: data.showEnabledOnly,
usecaseLimit: data.usecaseLimit || undefined,
useCase: data.useCase as string,
actionVersions: data.actionVersions,
},
body: {
useCase: data.useCase as string,
Expand Down
1 change: 1 addition & 0 deletions js/src/sdk/types/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const ZGetListActionsParams = z.object({
.boolean()
.optional()
.describe("Filter actions by available apps"),
actionVersions: z.record(z.string()).optional(),
});

export const ZParameter = z.object({
Expand Down
14 changes: 14 additions & 0 deletions js/src/sdk/utils/lock/load.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import fs from "fs";

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));
};
60 changes: 60 additions & 0 deletions js/src/sdk/utils/lockFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import logger from "../../utils/logger";

export const updateLockFileWithActionVersion = (
filePath: string,
actionName: string,
version: string
) => {
const actionVersions = getVersionsFromLockFileAsJson(filePath);
actionVersions[actionName] = version;
saveLockFile(filePath, actionVersions);
};

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

Check warning on line 15 in js/src/sdk/utils/lockFile.ts

View workflow job for this annotation

GitHub Actions / lint-and-prettify

A `require()` style import is forbidden

Check warning on line 15 in js/src/sdk/utils/lockFile.ts

View workflow job for this annotation

GitHub Actions / lint-and-prettify

A `require()` style import is forbidden
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);

Check warning on line 46 in js/src/sdk/utils/lockFile.ts

View workflow job for this annotation

GitHub Actions / lint-and-prettify

A `require()` style import is forbidden

Check warning on line 46 in js/src/sdk/utils/lockFile.ts

View workflow job for this annotation

GitHub Actions / lint-and-prettify

A `require()` style import is forbidden
} catch (e) {
const error = e as NodeJS.ErrnoException;
if (error.code === "EACCES" || error.code === "EPERM") {
logger.error("Permission denied writing to lock file", e);
throw new Error("Permission denied writing to lock file");
} else if (error.code === "ENOENT") {
logger.error("Directory does not exist for lock file", e);
throw new Error("Directory does not exist for lock file");
} else {
logger.error("Error writing to lock file", e);
throw new Error("Error writing to lock file");
}
}
};
14 changes: 14 additions & 0 deletions js/src/sdk/utils/processor/action_lock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { RawActionData } from "../../../types/base_toolset";

export const actionLockProcessor = (
filePath: string,
{
actionName,

Check warning on line 6 in js/src/sdk/utils/processor/action_lock.ts

View workflow job for this annotation

GitHub Actions / lint-and-prettify

'actionName' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 6 in js/src/sdk/utils/processor/action_lock.ts

View workflow job for this annotation

GitHub Actions / lint-and-prettify

'actionName' is defined but never used. Allowed unused args must match /^_/u
toolSchema,
}: {
actionName: string;
toolSchema: RawActionData;
}
): RawActionData => {
return toolSchema;
};
2 changes: 2 additions & 0 deletions js/src/types/base_toolset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const ZRawActionSchema = z.object({
name: z.string(),
toolName: z.string().optional(),
}),
version: z.string().optional(),
availableVersions: z.array(z.string()).optional(),
});

export type RawActionData = z.infer<typeof ZRawActionSchema>;
Expand Down

0 comments on commit 9e9c773

Please sign in to comment.