@composio/core
Version:

173 lines (170 loc) • 6.04 kB
JavaScript
import "../../chunk-CWQeghP5.mjs";
import { n as logger_default, t as ComposioError } from "../../ComposioError-ChkSdxqU.mjs";
import { n as getFileDataAfterUploadingToS3, t as downloadFileFromS3 } from "../../fileUtils.node-BOZnseMe.mjs";
import { i as transformProperties, n as schemaHasFileDownloadable, r as schemaHasFileUploadable, t as isPlainObject } from "../../FileToolModifier.utils.neutral-BcIR-cVE.mjs";
//#region src/errors/FileModifierErrors.ts
const FileModifierErrorCodes = { FILE_UPLOAD_FAILED: "FILE_UPLOAD_FAILED" };
var ComposioFileUploadError = class extends ComposioError {
constructor(message = "Failed to upload file", options = {}) {
super(message, {
...options,
code: FileModifierErrorCodes.FILE_UPLOAD_FAILED,
possibleFixes: options.possibleFixes || ["Check if the file exists in the location provided"]
});
this.name = "ComposioFileUploadError";
}
};
//#endregion
//#region src/utils/modifiers/FileToolModifier.node.ts
/**
* Recursively walks a runtime value and its matching JSON-Schema node,
* uploading any string path whose schema node has `file_uploadable: true`.
* The function returns a **new** value with all substitutions applied;
* nothing is mutated in-place.
*/
const hydrateFiles = async (value, schema, ctx) => {
if (schema?.file_uploadable) {
if (typeof value !== "string" && !(value instanceof File)) return value;
logger_default.debug(`Uploading file "${value}"`);
return getFileDataAfterUploadingToS3(value, {
toolSlug: ctx.toolSlug,
toolkitSlug: ctx.toolkitSlug,
client: ctx.client
});
}
const schemaVariants = [
...schema?.anyOf ?? [],
...schema?.oneOf ?? [],
...schema?.allOf ?? []
];
if (schemaVariants.length > 0) {
const uploadableVariants = schemaVariants.filter(schemaHasFileUploadable);
if (uploadableVariants.length > 0) {
let result = value;
for (const variant of uploadableVariants) result = await hydrateFiles(result, variant, ctx);
return result;
}
}
if (schema?.type === "object" && schema.properties && isPlainObject(value)) {
const transformed = {};
for (const [k, v] of Object.entries(value)) transformed[k] = await hydrateFiles(v, schema.properties[k], ctx);
return transformed;
}
if (schema?.type === "array" && schema.items && Array.isArray(value)) {
const itemSchema = Array.isArray(schema.items) ? schema.items[0] : schema.items;
return Promise.all(value.map((item) => hydrateFiles(item, itemSchema, ctx)));
}
return value;
};
/**
* Downloads a file from S3 and returns a replacement object.
*/
const downloadS3File = async (value, ctx) => {
const { s3url, mimetype } = value;
try {
logger_default.debug(`Downloading from S3: ${s3url}`);
const dl = await downloadFileFromS3({
toolSlug: ctx.toolSlug,
s3Url: s3url,
mimeType: mimetype ?? "application/octet-stream"
});
logger_default.debug(`Downloaded → ${dl.filePath}`);
return {
uri: dl.filePath,
file_downloaded: dl.filePath ? true : false,
s3url,
mimeType: dl.mimeType
};
} catch (err) {
logger_default.error(`Download failed: ${s3url}`, { cause: err });
return {
uri: "",
file_downloaded: false,
s3url,
mimeType: mimetype ?? "application/octet-stream"
};
}
};
/**
* Recursively walks an arbitrary value and its matching JSON-Schema node.
* Whenever it encounters an object that represents a file reference
* (i.e. has an `s3url`), it downloads the file and returns a replacement:
* {
* uri: "<local-path>",
* file_downloaded: true | false,
* s3url: "<original S3 URL>",
* mimeType: "<detected-or-fallback-mime-type>"
* }
*
* The function is side-effect-free: it never mutates the input value.
*/
const hydrateDownloads = async (value, schema, ctx) => {
if (isPlainObject(value) && typeof value.s3url === "string") return downloadS3File(value, ctx);
if (schema?.file_downloadable && isPlainObject(value) && typeof value.s3url === "string") return downloadS3File(value, ctx);
const schemaVariants = [
...schema?.anyOf ?? [],
...schema?.oneOf ?? [],
...schema?.allOf ?? []
];
if (schemaVariants.length > 0) {
const downloadableVariants = schemaVariants.filter(schemaHasFileDownloadable);
let result = value;
for (const variant of downloadableVariants) result = await hydrateDownloads(result, variant, ctx);
if (downloadableVariants.length === 0) return hydrateDownloads(value, void 0, ctx);
return result;
}
if (isPlainObject(value)) {
const pairs = await Promise.all(Object.entries(value).map(async ([k, v]) => [k, await hydrateDownloads(v, schema?.properties?.[k], ctx)]));
return Object.fromEntries(pairs);
}
if (Array.isArray(value)) {
const itemSchema = schema?.items ? Array.isArray(schema.items) ? schema.items[0] : schema.items : void 0;
return Promise.all(value.map((item) => hydrateDownloads(item, itemSchema, ctx)));
}
return value;
};
var FileToolModifier = class {
client;
constructor(client) {
this.client = client;
}
async modifyToolSchema(toolSlug, toolkitSlug, schema) {
if (!schema.inputParameters?.properties) return schema;
const properties = transformProperties(schema.inputParameters.properties);
return {
...schema,
inputParameters: {
...schema.inputParameters,
properties
}
};
}
async fileUploadModifier(tool, options) {
const { params, toolSlug, toolkitSlug = "unknown" } = options;
const { arguments: args } = params;
if (!args || typeof args !== "object") return params;
try {
const newArgs = await hydrateFiles(args, tool.inputParameters, {
toolSlug,
toolkitSlug,
client: this.client
});
return {
...params,
arguments: newArgs
};
} catch (error) {
throw new ComposioFileUploadError("Failed to upload file", { cause: error });
}
}
async fileDownloadModifier(tool, options) {
const { result, toolSlug } = options;
const dataWithDownloads = await hydrateDownloads(result.data, tool.outputParameters, { toolSlug });
return {
...result,
data: dataWithDownloads
};
}
};
//#endregion
export { FileToolModifier };