@vertesia/workflow 0.65.0 → 0.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/lib/cjs/activities/generateOrAssignContentType.js +1 -0
  2. package/lib/cjs/activities/generateOrAssignContentType.js.map +1 -1
  3. package/lib/cjs/activities/renditions/generateImageRendition.js +14 -1
  4. package/lib/cjs/activities/renditions/generateImageRendition.js.map +1 -1
  5. package/lib/cjs/activities/renditions/generateVideoRendition.js +5 -1
  6. package/lib/cjs/activities/renditions/generateVideoRendition.js.map +1 -1
  7. package/lib/cjs/errors.js +1 -60
  8. package/lib/cjs/errors.js.map +1 -1
  9. package/lib/cjs/index.js +1 -0
  10. package/lib/cjs/index.js.map +1 -1
  11. package/lib/cjs/utils/renditions.js +20 -20
  12. package/lib/cjs/utils/renditions.js.map +1 -1
  13. package/lib/cjs/utils/storage.js +55 -0
  14. package/lib/cjs/utils/storage.js.map +1 -0
  15. package/lib/esm/activities/generateOrAssignContentType.js +1 -0
  16. package/lib/esm/activities/generateOrAssignContentType.js.map +1 -1
  17. package/lib/esm/activities/renditions/generateImageRendition.js +14 -1
  18. package/lib/esm/activities/renditions/generateImageRendition.js.map +1 -1
  19. package/lib/esm/activities/renditions/generateVideoRendition.js +5 -1
  20. package/lib/esm/activities/renditions/generateVideoRendition.js.map +1 -1
  21. package/lib/esm/errors.js +0 -55
  22. package/lib/esm/errors.js.map +1 -1
  23. package/lib/esm/index.js +1 -0
  24. package/lib/esm/index.js.map +1 -1
  25. package/lib/esm/utils/renditions.js +20 -20
  26. package/lib/esm/utils/renditions.js.map +1 -1
  27. package/lib/esm/utils/storage.js +46 -0
  28. package/lib/esm/utils/storage.js.map +1 -0
  29. package/lib/types/activities/renditions/generateImageRendition.d.ts.map +1 -1
  30. package/lib/types/activities/renditions/generateVideoRendition.d.ts.map +1 -1
  31. package/lib/types/errors.d.ts +0 -31
  32. package/lib/types/errors.d.ts.map +1 -1
  33. package/lib/types/index.d.ts +1 -0
  34. package/lib/types/index.d.ts.map +1 -1
  35. package/lib/types/utils/renditions.d.ts +3 -3
  36. package/lib/types/utils/renditions.d.ts.map +1 -1
  37. package/lib/types/utils/storage.d.ts +16 -0
  38. package/lib/types/utils/storage.d.ts.map +1 -0
  39. package/lib/workflows-bundle.js +427 -358
  40. package/package.json +7 -6
  41. package/src/activities/generateOrAssignContentType.ts +1 -0
  42. package/src/activities/renditions/generateImageRendition.ts +97 -80
  43. package/src/activities/renditions/generateVideoRendition.ts +6 -1
  44. package/src/errors.ts +0 -58
  45. package/src/index.ts +1 -0
  46. package/src/utils/renditions.ts +24 -21
  47. package/src/utils/storage.ts +61 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/workflow",
3
- "version": "0.65.0",
3
+ "version": "0.67.0",
4
4
  "type": "module",
5
5
  "description": "Composable prompts workflow dsl",
6
6
  "main": "./lib/esm/index.js",
@@ -39,8 +39,10 @@
39
39
  "@vertesia/memory": "^0.43.0",
40
40
  "fast-deep-equal": "^3.1.3",
41
41
  "jsonwebtoken": "^9.0.2",
42
+ "mime": "^4.0.0",
42
43
  "ms": "3.0.0-canary.1",
43
44
  "node-web-stream-adapters": "^0.2.1",
45
+ "p-limit": "^6.2.0",
44
46
  "papaparse": "^5.4.1",
45
47
  "seedrandom": "^3.0.5",
46
48
  "sharp": "^0.33.4",
@@ -48,11 +50,10 @@
48
50
  "tmp": "^0.2.3",
49
51
  "tmp-promise": "^3.0.3",
50
52
  "yaml": "^2.6.0",
51
- "mime": "^4.0.0",
52
- "@llumiverse/common": "0.18.0",
53
- "@vertesia/common": "0.65.0",
54
- "@vertesia/client": "0.65.0",
55
- "@vertesia/api-fetch-client": "0.65.0"
53
+ "@vertesia/client": "0.67.0",
54
+ "@vertesia/api-fetch-client": "0.67.0",
55
+ "@llumiverse/common": "0.20.0",
56
+ "@vertesia/common": "0.67.0"
56
57
  },
57
58
  "ts_dual_module": {
58
59
  "outDir": "lib",
@@ -202,6 +202,7 @@ async function generateNewType(
202
202
  log.info("Generated schema for type", genTypeRes.result.metadata_schema);
203
203
  const typeData: CreateContentObjectTypePayload = {
204
204
  name: genTypeRes.result.document_type,
205
+ description: genTypeRes.result.document_type_description,
205
206
  object_schema: genTypeRes.result.metadata_schema,
206
207
  is_chunkable: genTypeRes.result.is_chunkable,
207
208
  table_layout: genTypeRes.result.table_layout,
@@ -4,96 +4,113 @@ import { setupActivity } from "../../dsl/setup/ActivityContext.js";
4
4
  import { DocumentNotFoundError, WorkflowParamNotFoundError } from "../../errors.js";
5
5
  import { saveBlobToTempFile } from "../../utils/blobs.js";
6
6
  import {
7
- ImageRenditionParams,
8
- uploadRenditionPages,
7
+ ImageRenditionParams,
8
+ uploadRenditionPages,
9
9
  } from "../../utils/renditions.js";
10
10
 
11
- interface GenerateImageRenditionParams extends ImageRenditionParams {}
11
+ interface GenerateImageRenditionParams extends ImageRenditionParams { }
12
12
 
13
13
  export interface GenerateImageRendition
14
- extends DSLActivitySpec<GenerateImageRenditionParams> {
15
- name: "generateImageRendition";
14
+ extends DSLActivitySpec<GenerateImageRenditionParams> {
15
+ name: "generateImageRendition";
16
16
  }
17
17
 
18
18
  export async function generateImageRendition(
19
- payload: DSLActivityExecutionPayload<GenerateImageRenditionParams>,
19
+ payload: DSLActivityExecutionPayload<GenerateImageRenditionParams>,
20
20
  ) {
21
- const {
22
- client,
23
- objectId,
24
- params: originParams,
25
- } = await setupActivity<GenerateImageRenditionParams>(payload);
26
-
27
- // Fix: Use maxHeightWidth if max_hw is not provided
28
- const params = {
29
- ...originParams,
30
- max_hw: originParams.max_hw || (originParams as any).maxHeightWidth || 1024, // Default to 1024 if both are missing
31
- format: originParams.format || (originParams as any).format_output || "png", // Default to png if format is missing
32
- };
33
-
34
- log.info(`Generating image rendition for ${objectId}`, {
35
- originParams,
36
- params,
37
- });
38
-
39
- const inputObject = await client.objects.retrieve(objectId).catch((err) => {
40
- log.error(`Failed to retrieve document ${objectId}`, { err });
41
- if (err.message.includes("not found")) {
42
- throw new DocumentNotFoundError(`Document ${objectId} not found`, [objectId]);
21
+ const {
22
+ client,
23
+ objectId,
24
+ params: originParams,
25
+ } = await setupActivity<GenerateImageRenditionParams>(payload);
26
+
27
+ // Fix: Use maxHeightWidth if max_hw is not provided
28
+ const params = {
29
+ ...originParams,
30
+ max_hw: originParams.max_hw || (originParams as any).maxHeightWidth || 1024, // Default to 1024 if both are missing
31
+ format: originParams.format || (originParams as any).format_output || "png", // Default to png if format is missing
32
+ };
33
+
34
+ log.info(`Generating image rendition for ${objectId}`, {
35
+ originParams,
36
+ params,
37
+ });
38
+
39
+ const inputObject = await client.objects.retrieve(objectId).catch((err) => {
40
+ log.error(`Failed to retrieve document ${objectId}`, { err });
41
+ if (err.message.includes("not found")) {
42
+ throw new DocumentNotFoundError(`Document ${objectId} not found`, [objectId]);
43
+ }
44
+ throw err;
45
+ });
46
+
47
+ if (!inputObject) {
48
+ log.error(`Document ${objectId} not found`);
49
+ throw new DocumentNotFoundError(`Document ${objectId} not found`, [objectId]);
43
50
  }
44
- throw err;
45
- });
46
-
47
- if (!params.format) {
48
- log.error(`Format not found`);
49
- throw new WorkflowParamNotFoundError(`format`);
50
- }
51
-
52
- if (!inputObject.content?.source) {
53
- log.error(`Document ${objectId} has no source`);
54
- throw new DocumentNotFoundError(`Document ${objectId} has no source`, [objectId]);
55
- }
56
-
57
- if (
58
- !inputObject.content.type ||
59
- !inputObject.content.type?.startsWith("image/")
60
- ) {
61
- log.error(
62
- `Document ${objectId} is not an image or a video: ${inputObject.content.type}`,
63
- );
64
- throw new DocumentNotFoundError(
65
- `Document ${objectId} is not an image or a video: ${inputObject.content.type}`,
66
- [objectId],
51
+
52
+ if (!inputObject.content?.source) {
53
+ log.error(`Document ${objectId} has no etag or source`);
54
+ throw new DocumentNotFoundError(`Document ${objectId} has no etag or source`, [objectId]);
55
+ }
56
+
57
+ if (!params.format) {
58
+ log.error(`Format not found`);
59
+ throw new WorkflowParamNotFoundError(`format`);
60
+ }
61
+
62
+ if (!inputObject.content?.source) {
63
+ log.error(`Document ${objectId} has no source`);
64
+ throw new DocumentNotFoundError(`Document ${objectId} has no source`, [objectId]);
65
+ }
66
+
67
+ if (
68
+ !inputObject.content.type ||
69
+ !inputObject.content.type?.startsWith("image/")
70
+ ) {
71
+ log.error(
72
+ `Document ${objectId} is not an image or a video: ${inputObject.content.type}`,
73
+ );
74
+ throw new DocumentNotFoundError(
75
+ `Document ${objectId} is not an image or a video: ${inputObject.content.type}`,
76
+ [objectId],
77
+ );
78
+ }
79
+
80
+ //array of rendition files to upload
81
+ let renditionPages: string[] = [];
82
+
83
+ const imageFile = await saveBlobToTempFile(
84
+ client,
85
+ inputObject.content.source,
67
86
  );
68
- }
69
-
70
- //array of rendition files to upload
71
- let renditionPages: string[] = [];
72
-
73
- const imageFile = await saveBlobToTempFile(
74
- client,
75
- inputObject.content.source,
76
- );
77
- log.info(`Image ${objectId} copied to ${imageFile}`);
78
- renditionPages.push(imageFile);
79
-
80
- const uploaded = await uploadRenditionPages(
81
- client,
82
- objectId,
83
- [imageFile],
84
- params,
85
- );
86
-
87
- if (!uploaded || !uploaded.length || !uploaded[0]) {
88
- log.error(`Failed to upload rendition for ${objectId}`, { uploaded });
89
- throw new Error(
90
- `Failed to upload rendition for ${objectId} - upload object is empty`,
87
+ log.info(`Image ${objectId} copied to ${imageFile}`);
88
+ renditionPages.push(imageFile);
89
+
90
+
91
+ //IF no etag, log and use use object id as etag
92
+ if (!inputObject.content.etag) {
93
+ log.warn(`Document ${objectId} has no etag, using object id as etag`);
94
+ }
95
+ const contentEtag = inputObject.content.etag ?? inputObject.id;
96
+
97
+ const uploaded = await uploadRenditionPages(
98
+ client,
99
+ contentEtag,
100
+ [imageFile],
101
+ params,
91
102
  );
92
- }
93
103
 
94
- return {
95
- uploads: uploaded.map((u) => u),
96
- format: params.format,
97
- status: "success",
98
- };
104
+ if (!uploaded || !uploaded.length || !uploaded[0]) {
105
+ log.error(`Failed to upload rendition for ${objectId}`, { uploaded });
106
+ throw new Error(
107
+ `Failed to upload rendition for ${objectId} - upload object is empty`,
108
+ );
109
+ }
110
+
111
+ return {
112
+ uploads: uploaded.map((u) => u),
113
+ format: params.format,
114
+ status: "success",
115
+ };
99
116
  }
@@ -271,10 +271,15 @@ export async function generateVideoRendition(
271
271
  }
272
272
  }
273
273
 
274
+ if (!inputObject.content?.etag) {
275
+ log.warn(`Document ${objectId} has no etag, using object id as etag`);
276
+ }
277
+ const etag = inputObject.content.etag ?? inputObject.id;
278
+
274
279
  // Update the final upload call to handle multiple thumbnails
275
280
  const uploaded = await uploadRenditionPages(
276
281
  client,
277
- objectId,
282
+ etag,
278
283
  renditionPages,
279
284
  params,
280
285
  );
package/src/errors.ts CHANGED
@@ -1,20 +1,6 @@
1
1
  import { ApplicationFailure } from "@temporalio/workflow";
2
2
  import { DSLActivitySpec, DSLWorkflowSpec } from "@vertesia/common";
3
3
 
4
- /**
5
- * @deprecated Use {@link DocumentNotFoundError} instead.
6
- */
7
- export class NoDocumentFound extends Error {
8
- constructor(
9
- message: string,
10
- public ids?: string[],
11
- ) {
12
- super(message);
13
- this.name = "NoDocumentFound";
14
- this.ids = ids;
15
- }
16
- }
17
-
18
4
  export class DocumentNotFoundError extends ApplicationFailure {
19
5
  constructor(message: string, public ids?: string[]) {
20
6
  super(
@@ -25,19 +11,6 @@ export class DocumentNotFoundError extends ApplicationFailure {
25
11
  }
26
12
  }
27
13
 
28
- /**
29
- * @deprecated Use {@link ActivityParamNotFoundError} instead.
30
- */
31
- export class ActivityParamNotFound extends Error {
32
- constructor(
33
- public paramName: string,
34
- public activity: DSLActivitySpec,
35
- ) {
36
- super(`Required parameter ${paramName} not found in activity ${activity.name}`);
37
- this.name = "ActivityParamNotFound";
38
- }
39
- }
40
-
41
14
  export class ActivityParamNotFoundError extends ApplicationFailure {
42
15
  constructor(
43
16
  public paramName: string,
@@ -51,20 +24,6 @@ export class ActivityParamNotFoundError extends ApplicationFailure {
51
24
  }
52
25
  }
53
26
 
54
- /**
55
- * @deprecated Use {@link ActivityParamInvalidError} instead.
56
- */
57
- export class ActivityParamInvalid extends Error {
58
- constructor(
59
- public paramName: string,
60
- public activity: DSLActivitySpec,
61
- reason?: string,
62
- ) {
63
- super(`${paramName} in activity ${activity.name} is invalid${reason ? ` ${reason}` : ""}`);
64
- this.name = "ActivityParamInvalid";
65
- }
66
- }
67
-
68
27
  export class ActivityParamInvalidError extends ApplicationFailure {
69
28
  constructor(
70
29
  public paramName: string,
@@ -79,19 +38,6 @@ export class ActivityParamInvalidError extends ApplicationFailure {
79
38
  }
80
39
  }
81
40
 
82
- /**
83
- * @deprecated Use {@link WorkflowParamNotFoundError} instead.
84
- */
85
- export class WorkflowParamNotFound extends Error {
86
- constructor(
87
- public paramName: string,
88
- public workflow?: DSLWorkflowSpec,
89
- ) {
90
- super(`Required parameter ${paramName} not found in workflow ${workflow?.name}`);
91
- this.name = "WorkflowParamNotFound";
92
- }
93
- }
94
-
95
41
  export class WorkflowParamNotFoundError extends ApplicationFailure {
96
42
  constructor(
97
43
  public paramName: string,
@@ -106,12 +52,8 @@ export class WorkflowParamNotFoundError extends ApplicationFailure {
106
52
  }
107
53
 
108
54
  export const WF_NON_RETRYABLE_ERRORS = [
109
- "NoDocumentFound",
110
55
  "DocumentNotFoundError",
111
- "ActivityParamInvalid",
112
56
  "ActivityParamInvalidError",
113
- "ActivityParamNotFound",
114
57
  "ActivityParamNotFoundError",
115
- "WorkflowParamNotFound",
116
58
  "WorkflowParamNotFoundError",
117
59
  ];
package/src/index.ts CHANGED
@@ -33,5 +33,6 @@ export * from "./utils/client.js";
33
33
  export * from "./utils/memory.js";
34
34
  export * from "./utils/tokens.js";
35
35
  export * from "./utils/renditions.js";
36
+ export * from "./utils/storage.js";
36
37
 
37
38
  export * from "./conversion/image.js";
@@ -1,10 +1,10 @@
1
1
  import { log } from "@temporalio/activity";
2
- import { imageResizer } from "../conversion/image.js";
3
- import fs from "fs";
4
2
  import { VertesiaClient } from "@vertesia/client";
5
3
  import { NodeStreamSource } from "@vertesia/client/node";
6
4
  import { ImageRenditionFormat } from "@vertesia/common";
7
- import path from "path";
5
+ import fs from "fs";
6
+ import pLimit from 'p-limit';
7
+ import { imageResizer } from "../conversion/image.js";
8
8
 
9
9
  export interface ImageRenditionParams {
10
10
  max_hw: number; //maximum size of the longest side of the image
@@ -19,10 +19,10 @@ export interface ImageRenditionParams {
19
19
  * @returns
20
20
  */
21
21
  export function getRenditionsPath(
22
- objectId: string,
22
+ contentEtag: string,
23
23
  params: ImageRenditionParams,
24
24
  ) {
25
- const path = `renditions/${objectId}/${params.max_hw}`;
25
+ const path = `renditions/${contentEtag}/${params.max_hw}`;
26
26
  return path;
27
27
  }
28
28
 
@@ -30,15 +30,15 @@ export function getRenditionsPath(
30
30
  * Get a specific page path for a rendition
31
31
  */
32
32
  export function getRenditionPagePath(
33
- objectId: string,
33
+ contentEtag: string,
34
34
  params: ImageRenditionParams,
35
- pageNumber: number | string = 1,
35
+ pageNumber: number | string = 0,
36
36
  ) {
37
- //if number, pad to 5 char
37
+ //if number, pad to 4 char
38
38
  if (typeof pageNumber === "number") {
39
- pageNumber = String(pageNumber).padStart(5, "0");
39
+ pageNumber = String(pageNumber).padStart(4, "0");
40
40
  }
41
- const path = getRenditionsPath(objectId, params);
41
+ const path = getRenditionsPath(contentEtag, params);
42
42
  const pagePath = `${path}/${pageNumber}.${params.format}`;
43
43
  return pagePath;
44
44
  }
@@ -48,21 +48,24 @@ export function getRenditionPagePath(
48
48
  */
49
49
  export async function uploadRenditionPages(
50
50
  client: VertesiaClient,
51
- objectId: string,
51
+ contentEtag: string,
52
52
  files: string[],
53
53
  params: ImageRenditionParams,
54
+ concurrency?: number,
54
55
  ) {
55
56
  log.info(
56
- `Uploading rendition for ${objectId} with ${files.length} pages (max_hw: ${params.max_hw}, format: ${params.format})`,
57
+ `Uploading rendition for etag ${contentEtag} with ${files.length} pages (max_hw: ${params.max_hw}, format: ${params.format})`,
57
58
  { files },
58
59
  );
59
- const uploads = files.map(async (file, i) => {
60
- const filename = path.basename(file).split(".")[0];
61
- const pageId = getRenditionPagePath(objectId, params, filename);
60
+
61
+ const limit = pLimit(concurrency ?? 20);
62
+
63
+ const uploads = files.map((file, i) => limit(async () => {
64
+ const pageId = getRenditionPagePath(contentEtag, params, i);
62
65
  let resizedImagePath = null;
63
66
 
64
67
  try {
65
- log.info(`Resizing image for ${objectId} page ${i}`, {
68
+ log.info(`Resizing image for ${contentEtag} page ${i}`, {
66
69
  file,
67
70
  params,
68
71
  });
@@ -85,7 +88,7 @@ export async function uploadRenditionPages(
85
88
  );
86
89
 
87
90
  log.info(
88
- `Uploading rendition for ${objectId} page ${i} with max_hw: ${params.max_hw} and format: ${params.format}`,
91
+ `Uploading rendition for ${contentEtag} page ${i} with max_hw: ${params.max_hw} and format: ${params.format}`,
89
92
  {
90
93
  resizedImagePath,
91
94
  fileId,
@@ -98,7 +101,7 @@ export async function uploadRenditionPages(
98
101
  .uploadFile(source)
99
102
  .catch((err) => {
100
103
  log.error(
101
- `Failed to upload rendition for ${objectId} page ${i}`,
104
+ `Failed to upload rendition for ${contentEtag} page ${i}`,
102
105
  {
103
106
  error: err,
104
107
  errorMessage: err.message,
@@ -107,18 +110,18 @@ export async function uploadRenditionPages(
107
110
  );
108
111
  return Promise.reject(`Upload failed: ${err.message}`);
109
112
  });
110
- log.info(`Rendition uploaded for ${objectId} page ${i}`, {
113
+ log.info(`Rendition uploaded for ${contentEtag} page ${i}`, {
111
114
  result,
112
115
  });
113
116
 
114
117
  return result;
115
118
  } catch (err: any) {
116
- log.error(`Failed to upload rendition for ${objectId} page ${i}`, {
119
+ log.error(`Failed to upload rendition for ${contentEtag} page ${i}`, {
117
120
  error: err,
118
121
  });
119
122
  return Promise.reject(`Upload failed: ${err.message}`);
120
123
  }
121
- });
124
+ }));
122
125
 
123
126
  return Promise.all(uploads);
124
127
  }
@@ -0,0 +1,61 @@
1
+ import { activityInfo, log } from "@temporalio/activity";
2
+ import { ApplicationFailure } from "@temporalio/workflow";
3
+ import { VertesiaClient } from "@vertesia/client";
4
+ import { NodeStreamSource } from "@vertesia/client/node";
5
+ import { basename } from "path";
6
+ import { Readable } from "stream";
7
+ import mime from "mime";
8
+ import { fetchBlobAsBuffer } from "../utils/blobs.js";
9
+
10
+
11
+ export const agentStoragePath = (runId: string) => `agents/${runId}`;
12
+
13
+ /**
14
+ *
15
+ * Save an artifact generated by an agent to cloud storage
16
+ *
17
+ * @param client
18
+ * @param name
19
+ * @param fileContent
20
+ * @param mimeType
21
+ * @returns
22
+ */
23
+ export async function saveAgentArtifact(
24
+ client: VertesiaClient,
25
+ name: string,
26
+ fileContent: Readable,
27
+ mimeType: string = "application/json",
28
+ ) {
29
+ const { runId } = activityInfo().workflowExecution;
30
+ const ext = mime.getExtension(mimeType);
31
+ if (!name) {
32
+ throw ApplicationFailure.nonRetryable(`Name is required`);
33
+ }
34
+
35
+ //create the file path and append extension if needed
36
+ const filePath = agentStoragePath(runId) + "/" + name + (ext && !name.endsWith(ext) ? "." + ext : "");
37
+ log.info(`Storing agent artifact ${filePath} for run ${runId}`);
38
+
39
+ try {
40
+ const source = new NodeStreamSource(fileContent, `${runId}-${basename(filePath)}`, mimeType, filePath);
41
+ return await client.files.uploadFile(source);
42
+ } catch (err: any) {
43
+ log.error(`Failed to save agent artifact for run ${runId}`, {
44
+ err,
45
+ file: filePath,
46
+ });
47
+ throw ApplicationFailure.nonRetryable(
48
+ `Failed to save agent artifact for run ${runId}`,
49
+ "SaveAgentArtifactError",
50
+ {
51
+ error: err,
52
+ },
53
+ );
54
+ }
55
+ }
56
+
57
+ export async function fetchAgentArtifact(client: VertesiaClient, name: string) {
58
+ const { runId } = activityInfo().workflowExecution;
59
+ const filePath = agentStoragePath(runId) + "/" + name;
60
+ return fetchBlobAsBuffer(client, filePath);
61
+ }