@vertesia/workflow 1.0.0-dev.20260128.144200 → 1.0.0-dev.20260225.024852Z
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.
- package/lib/cjs/activities/advanced/createOrUpdateDocumentFromInteractionRun.js +1 -1
- package/lib/cjs/activities/advanced/createOrUpdateDocumentFromInteractionRun.js.map +1 -1
- package/lib/cjs/activities/chunkDocument.js +3 -1
- package/lib/cjs/activities/chunkDocument.js.map +1 -1
- package/lib/cjs/activities/extractDocumentText.js +56 -16
- package/lib/cjs/activities/extractDocumentText.js.map +1 -1
- package/lib/cjs/activities/generateDocumentProperties.js +4 -2
- package/lib/cjs/activities/generateDocumentProperties.js.map +1 -1
- package/lib/cjs/activities/generateEmbeddings.js +20 -10
- package/lib/cjs/activities/generateEmbeddings.js.map +1 -1
- package/lib/cjs/activities/generateOrAssignContentType.js +2 -2
- package/lib/cjs/activities/generateOrAssignContentType.js.map +1 -1
- package/lib/cjs/activities/index-dsl.js +7 -7
- package/lib/cjs/activities/index-dsl.js.map +1 -1
- package/lib/cjs/activities/media/saveGladiaTranscription.js +38 -24
- package/lib/cjs/activities/media/saveGladiaTranscription.js.map +1 -1
- package/lib/cjs/activities/media/transcribeMediaWithGladia.js +41 -24
- package/lib/cjs/activities/media/transcribeMediaWithGladia.js.map +1 -1
- package/lib/cjs/activities/notifyWebhook.js +11 -2
- package/lib/cjs/activities/notifyWebhook.js.map +1 -1
- package/lib/cjs/activities/renditions/generateImageRendition.js +2 -2
- package/lib/cjs/activities/renditions/generateImageRendition.js.map +1 -1
- package/lib/cjs/activities/setDocumentStatus.js +13 -2
- package/lib/cjs/activities/setDocumentStatus.js.map +1 -1
- package/lib/cjs/conversion/image.js +10 -10
- package/lib/cjs/conversion/image.js.map +1 -1
- package/lib/cjs/dsl/dsl-workflow.js +44 -7
- package/lib/cjs/dsl/dsl-workflow.js.map +1 -1
- package/lib/cjs/dsl/setup/ActivityContext.js +56 -0
- package/lib/cjs/dsl/setup/ActivityContext.js.map +1 -1
- package/lib/cjs/errors.js +11 -1
- package/lib/cjs/errors.js.map +1 -1
- package/lib/cjs/index.js +6 -5
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/result-types.js.map +1 -1
- package/lib/cjs/utils/renditions.js +9 -5
- package/lib/cjs/utils/renditions.js.map +1 -1
- package/lib/cjs/utils/text-preview-utils.js +43 -0
- package/lib/cjs/utils/text-preview-utils.js.map +1 -0
- package/lib/esm/activities/advanced/createOrUpdateDocumentFromInteractionRun.js +1 -1
- package/lib/esm/activities/advanced/createOrUpdateDocumentFromInteractionRun.js.map +1 -1
- package/lib/esm/activities/chunkDocument.js +3 -1
- package/lib/esm/activities/chunkDocument.js.map +1 -1
- package/lib/esm/activities/extractDocumentText.js +56 -16
- package/lib/esm/activities/extractDocumentText.js.map +1 -1
- package/lib/esm/activities/generateDocumentProperties.js +4 -2
- package/lib/esm/activities/generateDocumentProperties.js.map +1 -1
- package/lib/esm/activities/generateEmbeddings.js +20 -10
- package/lib/esm/activities/generateEmbeddings.js.map +1 -1
- package/lib/esm/activities/generateOrAssignContentType.js +2 -2
- package/lib/esm/activities/generateOrAssignContentType.js.map +1 -1
- package/lib/esm/activities/index-dsl.js +3 -3
- package/lib/esm/activities/index-dsl.js.map +1 -1
- package/lib/esm/activities/media/saveGladiaTranscription.js +38 -24
- package/lib/esm/activities/media/saveGladiaTranscription.js.map +1 -1
- package/lib/esm/activities/media/transcribeMediaWithGladia.js +41 -24
- package/lib/esm/activities/media/transcribeMediaWithGladia.js.map +1 -1
- package/lib/esm/activities/notifyWebhook.js +11 -2
- package/lib/esm/activities/notifyWebhook.js.map +1 -1
- package/lib/esm/activities/renditions/generateImageRendition.js +2 -2
- package/lib/esm/activities/renditions/generateImageRendition.js.map +1 -1
- package/lib/esm/activities/setDocumentStatus.js +13 -2
- package/lib/esm/activities/setDocumentStatus.js.map +1 -1
- package/lib/esm/conversion/image.js +10 -10
- package/lib/esm/conversion/image.js.map +1 -1
- package/lib/esm/dsl/dsl-workflow.js +44 -7
- package/lib/esm/dsl/dsl-workflow.js.map +1 -1
- package/lib/esm/dsl/setup/ActivityContext.js +57 -1
- package/lib/esm/dsl/setup/ActivityContext.js.map +1 -1
- package/lib/esm/errors.js +9 -0
- package/lib/esm/errors.js.map +1 -1
- package/lib/esm/index.js +6 -5
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/result-types.js.map +1 -1
- package/lib/esm/utils/renditions.js +9 -5
- package/lib/esm/utils/renditions.js.map +1 -1
- package/lib/esm/utils/text-preview-utils.js +38 -0
- package/lib/esm/utils/text-preview-utils.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types/activities/chunkDocument.d.ts.map +1 -1
- package/lib/types/activities/extractDocumentText.d.ts +1 -0
- package/lib/types/activities/extractDocumentText.d.ts.map +1 -1
- package/lib/types/activities/generateDocumentProperties.d.ts.map +1 -1
- package/lib/types/activities/generateEmbeddings.d.ts.map +1 -1
- package/lib/types/activities/index-dsl.d.ts +3 -3
- package/lib/types/activities/index-dsl.d.ts.map +1 -1
- package/lib/types/activities/media/saveGladiaTranscription.d.ts +1 -0
- package/lib/types/activities/media/saveGladiaTranscription.d.ts.map +1 -1
- package/lib/types/activities/media/transcribeMediaWithGladia.d.ts +1 -0
- package/lib/types/activities/media/transcribeMediaWithGladia.d.ts.map +1 -1
- package/lib/types/activities/setDocumentStatus.d.ts +1 -1
- package/lib/types/activities/setDocumentStatus.d.ts.map +1 -1
- package/lib/types/dsl/dsl-workflow.d.ts.map +1 -1
- package/lib/types/dsl/setup/ActivityContext.d.ts +32 -2
- package/lib/types/dsl/setup/ActivityContext.d.ts.map +1 -1
- package/lib/types/errors.d.ts +4 -0
- package/lib/types/errors.d.ts.map +1 -1
- package/lib/types/index.d.ts +6 -5
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/result-types.d.ts +5 -1
- package/lib/types/result-types.d.ts.map +1 -1
- package/lib/types/utils/renditions.d.ts +2 -0
- package/lib/types/utils/renditions.d.ts.map +1 -1
- package/lib/types/utils/text-preview-utils.d.ts +15 -0
- package/lib/types/utils/text-preview-utils.d.ts.map +1 -0
- package/lib/workflows-bundle.js +11747 -11141
- package/package.json +6 -7
- package/src/activities/advanced/createOrUpdateDocumentFromInteractionRun.ts +1 -1
- package/src/activities/chunkDocument.ts +3 -1
- package/src/activities/extractDocumentText.ts +85 -26
- package/src/activities/generateDocumentProperties.ts +4 -2
- package/src/activities/generateEmbeddings.ts +22 -14
- package/src/activities/generateOrAssignContentType.ts +2 -2
- package/src/activities/index-dsl.ts +4 -3
- package/src/activities/media/saveGladiaTranscription.test.ts +406 -0
- package/src/activities/media/saveGladiaTranscription.ts +41 -26
- package/src/activities/media/transcribeMediaWithGladia.test.ts +583 -0
- package/src/activities/media/transcribeMediaWithGladia.ts +46 -25
- package/src/activities/notifyWebhook.test.ts +121 -8
- package/src/activities/notifyWebhook.ts +10 -2
- package/src/activities/renditions/generateImageRendition.ts +2 -2
- package/src/activities/setDocumentStatus.ts +12 -4
- package/src/conversion/image.test.ts +1 -0
- package/src/conversion/image.ts +10 -10
- package/src/dsl/dsl-workflow.ts +57 -9
- package/src/dsl/setup/ActivityContext.ts +73 -0
- package/src/dsl.ts +1 -0
- package/src/errors.ts +15 -0
- package/src/index.ts +6 -5
- package/src/result-types.ts +5 -1
- package/src/utils/renditions.ts +11 -5
- package/src/utils/text-preview-utils.ts +62 -0
|
@@ -9,6 +9,7 @@ import { TextExtractionResult, TextExtractionStatus } from "../../index.js";
|
|
|
9
9
|
export interface TranscriptMediaParams {
|
|
10
10
|
environmentId?: string;
|
|
11
11
|
force?: boolean;
|
|
12
|
+
output_storage_path?: string;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface TranscriptMedia extends DSLActivitySpec<TranscriptMediaParams> {
|
|
@@ -28,39 +29,59 @@ const GLADIA_URL = "https://api.gladia.io/v2";
|
|
|
28
29
|
|
|
29
30
|
export async function transcribeMedia(payload: DSLActivityExecutionPayload<TranscriptMediaParams>): Promise<TranscriptMediaResult> {
|
|
30
31
|
|
|
31
|
-
const
|
|
32
|
+
const context = await setupActivity<TranscriptMediaParams>(payload);
|
|
33
|
+
const { params, client, inputType } = context;
|
|
32
34
|
|
|
33
35
|
const gladiaConfig = await client.projects.integrations.retrieve(payload.project_id, SupportedIntegrations.gladia) as GladiaConfiguration | undefined;
|
|
34
36
|
if (!gladiaConfig || !gladiaConfig.enabled) {
|
|
35
37
|
return {
|
|
36
38
|
hasText: false,
|
|
37
|
-
objectId,
|
|
39
|
+
objectId: inputType === 'objectIds' ? context.objectId : undefined,
|
|
38
40
|
status: TextExtractionStatus.error,
|
|
39
41
|
error: "Gladia integration not enabled",
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
const object = await client.objects.retrieve(objectId, "+text");
|
|
44
45
|
const gladiaClient = new FetchClient(gladiaConfig.url ?? GLADIA_URL);
|
|
45
46
|
gladiaClient.withHeaders({ "x-gladia-key": gladiaConfig.api_key });
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
48
|
+
let mediaSource: string;
|
|
49
|
+
let storageId: string;
|
|
50
50
|
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
if (inputType === 'objectIds') {
|
|
52
|
+
// Object mode: fetch from object store
|
|
53
|
+
const objectId = context.objectId;
|
|
54
|
+
const object = await client.objects.retrieve(objectId, "+text");
|
|
55
|
+
|
|
56
|
+
if (object.text && !params.force) {
|
|
57
|
+
return { hasText: true, objectId, status: TextExtractionStatus.skipped, message: "text already present and force not enabled" }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!object.content?.source) {
|
|
61
|
+
throw new DocumentNotFoundError(`No source found for object ${objectId}`);
|
|
62
|
+
}
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
// Check for audio rendition in video metadata (preferred for videos)
|
|
65
|
+
mediaSource = object.content.source;
|
|
66
|
+
if (object.metadata?.type === ContentNature.Video) {
|
|
67
|
+
const videoMetadata = object.metadata as VideoMetadata;
|
|
68
|
+
const audioRendition = videoMetadata.renditions?.find(r => r.name === AUDIO_RENDITION_NAME);
|
|
69
|
+
if (audioRendition?.content?.source) {
|
|
70
|
+
mediaSource = audioRendition.content.source;
|
|
71
|
+
log.info(`Found audio rendition for video object ${objectId}`, { mediaSource });
|
|
72
|
+
}
|
|
63
73
|
}
|
|
74
|
+
|
|
75
|
+
storageId = objectId;
|
|
76
|
+
} else {
|
|
77
|
+
// File mode: use file input
|
|
78
|
+
const file = context.file;
|
|
79
|
+
if (!params.output_storage_path) {
|
|
80
|
+
throw new DocumentNotFoundError("output_storage_path is required when using file input");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
mediaSource = file.url;
|
|
84
|
+
storageId = params.output_storage_path;
|
|
64
85
|
}
|
|
65
86
|
|
|
66
87
|
// Get download URL for the media source
|
|
@@ -70,12 +91,12 @@ export async function transcribeMedia(payload: DSLActivityExecutionPayload<Trans
|
|
|
70
91
|
throw new DocumentNotFoundError(`Error fetching media URL for ${mediaSource}`);
|
|
71
92
|
}
|
|
72
93
|
|
|
73
|
-
log.info(`Using media URL for transcription`, {
|
|
94
|
+
log.info(`Using media URL for transcription`, { storageId, mediaUrl: mediaSource });
|
|
74
95
|
|
|
75
96
|
const taskToken = Buffer.from(activityInfo().taskToken).toString('base64url');
|
|
76
|
-
const callbackUrl = generateCallbackUrlForGladia(client.store.baseUrl, taskToken
|
|
97
|
+
const callbackUrl = generateCallbackUrlForGladia(client.store.baseUrl, taskToken);
|
|
77
98
|
|
|
78
|
-
log.info(`Transcribing media ${mediaUrl} with Gladia`, {
|
|
99
|
+
log.info(`Transcribing media ${mediaUrl} with Gladia`, { storageId, callbackUrl });
|
|
79
100
|
|
|
80
101
|
try {
|
|
81
102
|
const res = await gladiaClient.post("/transcription", {
|
|
@@ -90,25 +111,25 @@ export async function transcribeMedia(payload: DSLActivityExecutionPayload<Trans
|
|
|
90
111
|
}
|
|
91
112
|
}
|
|
92
113
|
}) as GladiaTranscriptRequestResponse;
|
|
93
|
-
log.info(`Transcription request sent to Gladia`, {
|
|
114
|
+
log.info(`Transcription request sent to Gladia`, { storageId, res });
|
|
94
115
|
} catch (error: any) {
|
|
95
116
|
if (error instanceof RequestError && error.status === 422) {
|
|
96
117
|
return {
|
|
97
118
|
hasText: false,
|
|
98
|
-
objectId,
|
|
119
|
+
objectId: storageId,
|
|
99
120
|
status: TextExtractionStatus.error,
|
|
100
121
|
error: `Gladia transcription error: ${error.message}`,
|
|
101
122
|
}
|
|
102
123
|
}
|
|
103
|
-
log.error(`Error sending transcription request to Gladia for
|
|
124
|
+
log.error(`Error sending transcription request to Gladia for storage ${storageId}`, { error });
|
|
104
125
|
throw error;
|
|
105
126
|
}
|
|
106
127
|
|
|
107
128
|
throw new CompleteAsyncError();
|
|
108
129
|
}
|
|
109
130
|
|
|
110
|
-
function generateCallbackUrlForGladia(baseUrl: string, taskToken: string
|
|
111
|
-
return `${baseUrl}/webhooks/gladia
|
|
131
|
+
function generateCallbackUrlForGladia(baseUrl: string, taskToken: string) {
|
|
132
|
+
return `${baseUrl}/webhooks/gladia?task_token=${taskToken}`;
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
interface GladiaTranscriptRequestResponse {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MockActivityEnvironment,
|
|
3
3
|
} from "@temporalio/testing";
|
|
4
|
-
import { ContentEventName, DSLActivityExecutionPayload } from "@vertesia/common";
|
|
4
|
+
import { ApiVersions, ContentEventName, DSLActivityExecutionPayload, WebHookSpec } from "@vertesia/common";
|
|
5
5
|
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
-
import { notifyWebhook, NotifyWebhookParams } from "./notifyWebhook.js";
|
|
6
|
+
import { notifyWebhook, NotifyWebhookParams, WebhookNotificationPayload } from "./notifyWebhook.js";
|
|
7
7
|
|
|
8
8
|
// Mock fetch globally
|
|
9
9
|
vi.stubGlobal('fetch', vi.fn());
|
|
@@ -63,10 +63,15 @@ describe("Webhook should be notified", () => {
|
|
|
63
63
|
const payload = createTestPayload();
|
|
64
64
|
const res = await testEnv.run(notifyWebhook, payload);
|
|
65
65
|
|
|
66
|
-
// Verify fetch was called with correct parameters
|
|
66
|
+
// Verify fetch was called with correct parameters (old format wraps detail in result)
|
|
67
67
|
expect(mockFetch).toHaveBeenCalledWith(defaultParams.webhook, {
|
|
68
68
|
method: 'POST',
|
|
69
|
-
body: JSON.stringify({
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
workflowId: 'wf_id',
|
|
71
|
+
runId: 'wf_run_id',
|
|
72
|
+
status: 'completed',
|
|
73
|
+
result: { message: 'Hello World' }
|
|
74
|
+
}),
|
|
70
75
|
headers: {
|
|
71
76
|
'Content-Type': 'application/json',
|
|
72
77
|
},
|
|
@@ -98,10 +103,15 @@ describe("Webhook should be notified", () => {
|
|
|
98
103
|
`Webhook Notification to ${defaultParams.webhook} failed with status: 500 Internal Server Error - Response: {"error": "Database connection failed", "code": "DB_ERROR"}`
|
|
99
104
|
);
|
|
100
105
|
|
|
101
|
-
// Verify fetch was called with correct parameters
|
|
106
|
+
// Verify fetch was called with correct parameters (old format wraps detail in result)
|
|
102
107
|
expect(mockFetch).toHaveBeenCalledWith(defaultParams.webhook, {
|
|
103
108
|
method: 'POST',
|
|
104
|
-
body: JSON.stringify({
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
workflowId: 'wf_id',
|
|
111
|
+
runId: 'wf_run_id',
|
|
112
|
+
status: 'completed',
|
|
113
|
+
result: { message: 'Hello World' }
|
|
114
|
+
}),
|
|
105
115
|
headers: {
|
|
106
116
|
'Content-Type': 'application/json',
|
|
107
117
|
},
|
|
@@ -121,14 +131,117 @@ describe("Webhook should be notified", () => {
|
|
|
121
131
|
// Expect the function to throw the network error
|
|
122
132
|
await expect(testEnv.run(notifyWebhook, payload)).rejects.toThrow('Network request failed');
|
|
123
133
|
|
|
124
|
-
// Verify fetch was called with correct parameters
|
|
134
|
+
// Verify fetch was called with correct parameters (old format wraps detail in result)
|
|
125
135
|
expect(mockFetch).toHaveBeenCalledWith(defaultParams.webhook, {
|
|
126
136
|
method: 'POST',
|
|
127
|
-
body: JSON.stringify({
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
workflowId: 'wf_id',
|
|
139
|
+
runId: 'wf_run_id',
|
|
140
|
+
status: 'completed',
|
|
141
|
+
result: { message: 'Hello World' }
|
|
142
|
+
}),
|
|
128
143
|
headers: {
|
|
129
144
|
'Content-Type': 'application/json',
|
|
130
145
|
},
|
|
131
146
|
});
|
|
132
147
|
});
|
|
133
148
|
|
|
149
|
+
it("test POST with undefined detail still sends body with workflow info (new format)", async () => {
|
|
150
|
+
// Mock successful response
|
|
151
|
+
const mockResponse = {
|
|
152
|
+
ok: true,
|
|
153
|
+
status: 200,
|
|
154
|
+
statusText: 'OK',
|
|
155
|
+
url: 'https://vertesia.test'
|
|
156
|
+
};
|
|
157
|
+
mockFetch.mockResolvedValueOnce(mockResponse as Response);
|
|
158
|
+
|
|
159
|
+
// Create webhook spec with version to use new format
|
|
160
|
+
const webhookSpec: WebHookSpec = {
|
|
161
|
+
url: 'https://vertesia.test',
|
|
162
|
+
version: ApiVersions.COMPLETION_RESULT_V1
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Create payload with undefined detail (like workflow_failed events)
|
|
166
|
+
const payload = createTestPayload({
|
|
167
|
+
webhook: webhookSpec,
|
|
168
|
+
detail: undefined,
|
|
169
|
+
event_name: 'workflow_failed'
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const res = await testEnv.run(notifyWebhook, payload);
|
|
173
|
+
|
|
174
|
+
// Verify fetch was called
|
|
175
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
176
|
+
const [_url, options] = mockFetch.mock.calls[0];
|
|
177
|
+
|
|
178
|
+
// Verify the body parameter is NOT undefined
|
|
179
|
+
expect(options?.body).toBeDefined();
|
|
180
|
+
|
|
181
|
+
// Verify the body contains workflow info
|
|
182
|
+
const bodyData = JSON.parse(options?.body as string) as WebhookNotificationPayload;
|
|
183
|
+
expect(bodyData.workflow_id).toBe('wf_id');
|
|
184
|
+
expect(bodyData.workflow_name).toBe('wfFuncName');
|
|
185
|
+
expect(bodyData.workflow_run_id).toBe('wf_run_id');
|
|
186
|
+
expect(bodyData.event_name).toBe('workflow_failed');
|
|
187
|
+
// detail should not be present when undefined (JSON.stringify omits it)
|
|
188
|
+
expect(bodyData.detail).toBeUndefined();
|
|
189
|
+
|
|
190
|
+
// Verify Content-Type header is set
|
|
191
|
+
const headers = options?.headers as Record<string, string>;
|
|
192
|
+
expect(headers['Content-Type']).toBe('application/json');
|
|
193
|
+
|
|
194
|
+
// Verify response
|
|
195
|
+
expect(res).toEqual({
|
|
196
|
+
status: 200,
|
|
197
|
+
message: 'OK',
|
|
198
|
+
url: webhookSpec.url
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("test POST with undefined detail still sends body with workflow info (old format)", async () => {
|
|
203
|
+
// Mock successful response
|
|
204
|
+
const mockResponse = {
|
|
205
|
+
ok: true,
|
|
206
|
+
status: 200,
|
|
207
|
+
statusText: 'OK',
|
|
208
|
+
url: 'https://vertesia.test'
|
|
209
|
+
};
|
|
210
|
+
mockFetch.mockResolvedValueOnce(mockResponse as Response);
|
|
211
|
+
|
|
212
|
+
// Create payload with string webhook (old format) and undefined detail
|
|
213
|
+
const payload = createTestPayload({
|
|
214
|
+
webhook: 'https://vertesia.test',
|
|
215
|
+
detail: undefined,
|
|
216
|
+
event_name: 'workflow_completed'
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const res = await testEnv.run(notifyWebhook, payload);
|
|
220
|
+
|
|
221
|
+
// Verify fetch was called
|
|
222
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
223
|
+
const [_url, options] = mockFetch.mock.calls[0];
|
|
224
|
+
|
|
225
|
+
// Verify the body parameter is NOT undefined
|
|
226
|
+
expect(options?.body).toBeDefined();
|
|
227
|
+
|
|
228
|
+
// Verify the body contains workflow info in old format
|
|
229
|
+
const bodyData = JSON.parse(options?.body as string);
|
|
230
|
+
expect(bodyData.workflowId).toBe('wf_id');
|
|
231
|
+
expect(bodyData.runId).toBe('wf_run_id');
|
|
232
|
+
expect(bodyData.status).toBe('completed');
|
|
233
|
+
expect(bodyData.result).toBeNull(); // null when detail is undefined
|
|
234
|
+
|
|
235
|
+
// Verify Content-Type header is set
|
|
236
|
+
const headers = options?.headers as Record<string, string>;
|
|
237
|
+
expect(headers['Content-Type']).toBe('application/json');
|
|
238
|
+
|
|
239
|
+
// Verify response
|
|
240
|
+
expect(res).toEqual({
|
|
241
|
+
status: 200,
|
|
242
|
+
message: 'OK',
|
|
243
|
+
url: 'https://vertesia.test'
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
134
247
|
});
|
|
@@ -45,7 +45,7 @@ export async function notifyWebhook(payload: DSLActivityExecutionPayload<NotifyW
|
|
|
45
45
|
|
|
46
46
|
if (!target_url) throw new WorkflowParamNotFoundError('target_url');
|
|
47
47
|
|
|
48
|
-
const hasBody =
|
|
48
|
+
const hasBody = method === 'POST'; //body is sent only for POST, always includes workflow info
|
|
49
49
|
|
|
50
50
|
const headers = {
|
|
51
51
|
...defaultHeaders,
|
|
@@ -194,6 +194,14 @@ async function createOldRequestBody(payload: WorkflowExecutionBaseParams, params
|
|
|
194
194
|
result: result || null
|
|
195
195
|
}
|
|
196
196
|
};
|
|
197
|
+
} else {
|
|
198
|
+
// Always include workflow metadata in old format, even when detail is undefined
|
|
199
|
+
data = {
|
|
200
|
+
workflowId: params.workflow_id,
|
|
201
|
+
runId: params.workflow_run_id,
|
|
202
|
+
status: params.event_name === 'workflow_completed' ? 'completed' : params.event_name,
|
|
203
|
+
result: data || null
|
|
204
|
+
};
|
|
197
205
|
}
|
|
198
|
-
return JSON.stringify(data
|
|
206
|
+
return JSON.stringify(data);
|
|
199
207
|
}
|
|
@@ -31,7 +31,7 @@ export async function generateImageRendition(
|
|
|
31
31
|
format: originParams.format || (originParams as any).format_output || "png", // Default to png if format is missing
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
log.
|
|
34
|
+
log.debug(`Generating image rendition for ${objectId}`, {
|
|
35
35
|
originParams,
|
|
36
36
|
params,
|
|
37
37
|
});
|
|
@@ -79,7 +79,7 @@ export async function generateImageRendition(
|
|
|
79
79
|
client,
|
|
80
80
|
inputObject.content.source,
|
|
81
81
|
);
|
|
82
|
-
log.
|
|
82
|
+
log.debug(`Image ${objectId} copied to ${imageFile}`);
|
|
83
83
|
renditionPages.push(imageFile);
|
|
84
84
|
|
|
85
85
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from "@temporalio/activity";
|
|
1
2
|
import { ContentObjectStatus, DSLActivityExecutionPayload, DSLActivitySpec } from "@vertesia/common";
|
|
2
3
|
import { setupActivity } from "../dsl/setup/ActivityContext.js";
|
|
3
4
|
|
|
@@ -18,8 +19,15 @@ export interface SetDocumentStatus extends DSLActivitySpec<SetDocumentStatusPara
|
|
|
18
19
|
export async function setDocumentStatus(payload: DSLActivityExecutionPayload<SetDocumentStatusParams>) {
|
|
19
20
|
const { client, params, objectId } = await setupActivity<SetDocumentStatusParams>(payload);
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
try {
|
|
23
|
+
const res = await client.objects.update(objectId, { status: params.status });
|
|
24
|
+
return res.status;
|
|
25
|
+
} catch (err: any) {
|
|
26
|
+
// If document was deleted, nothing to update - log warning and continue
|
|
27
|
+
if (err.status === 404 || err.name === 'ZenoClientNotFoundError') {
|
|
28
|
+
log.warn(`Document ${objectId} not found - may have been deleted. Skipping status update to '${params.status}'`);
|
|
29
|
+
return undefined; // Signal that document wasn't found
|
|
30
|
+
}
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
25
33
|
}
|
package/src/conversion/image.ts
CHANGED
|
@@ -24,7 +24,7 @@ export async function imageResizer(
|
|
|
24
24
|
colorspaceCorrection: boolean = true,
|
|
25
25
|
colorspace: 'RGB' | 'LAB' | 'LUV' | 'sigmoidal' = 'RGB'
|
|
26
26
|
): Promise<string> {
|
|
27
|
-
log.
|
|
27
|
+
log.debug(`[image-resizer] Resizing image: ${inputPath} to max_hw: ${max_hw}, format: ${format}, progressive: ${progressive}, colorspaceCorrection: ${colorspaceCorrection ? colorspace : 'disabled'}`);
|
|
28
28
|
|
|
29
29
|
const allowedFormats = ["jpg", "jpeg", "png", "webp"];
|
|
30
30
|
|
|
@@ -63,17 +63,17 @@ export async function imageResizer(
|
|
|
63
63
|
const lowerFormat = format.toLowerCase();
|
|
64
64
|
if (lowerFormat === "jpg" || lowerFormat === "jpeg") {
|
|
65
65
|
conversionOption = "-interlace JPEG";
|
|
66
|
-
log.
|
|
66
|
+
log.debug(`Enabling interlaced ${lowerFormat.toUpperCase()} format`);
|
|
67
67
|
} else if (lowerFormat === "png") {
|
|
68
68
|
conversionOption = "-interlace PNG";
|
|
69
|
-
log.
|
|
69
|
+
log.debug(`Enabling interlaced ${lowerFormat.toUpperCase()} format`);
|
|
70
70
|
} else if (lowerFormat === "gif") {
|
|
71
71
|
conversionOption = "-interlace GIF";
|
|
72
|
-
log.
|
|
72
|
+
log.debug(`Enabling interlaced ${lowerFormat.toUpperCase()} format`);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
log.
|
|
76
|
+
log.debug(`Resizing image using ImageMagick: ${inputPath} -> ${outputPath}`);
|
|
77
77
|
|
|
78
78
|
const command = `convert`
|
|
79
79
|
let args = [inputPath];
|
|
@@ -92,26 +92,26 @@ export async function imageResizer(
|
|
|
92
92
|
// Linear light, recommended default
|
|
93
93
|
// Convert from sRGB to linear RGB for processing
|
|
94
94
|
args.push("-colorspace", "RGB");
|
|
95
|
-
log.
|
|
95
|
+
log.debug("Using linear RGB colorspace for resize processing");
|
|
96
96
|
break;
|
|
97
97
|
case 'LAB':
|
|
98
98
|
// Perceptual linear light
|
|
99
99
|
// Use LAB colorspace which separates intensity from color
|
|
100
100
|
// Better for avoiding color clipping and distortion
|
|
101
101
|
args.push("-colorspace", "LAB");
|
|
102
|
-
log.
|
|
102
|
+
log.debug("Using LAB colorspace for resize processing");
|
|
103
103
|
break;
|
|
104
104
|
case 'LUV':
|
|
105
105
|
// Perceptual linear light
|
|
106
106
|
// Alternative to LAB with perceptually uniform color deltas
|
|
107
107
|
args.push("-colorspace", "LUV");
|
|
108
|
-
log.
|
|
108
|
+
log.debug("Using LUV colorspace for resize processing");
|
|
109
109
|
break;
|
|
110
110
|
case 'sigmoidal':
|
|
111
111
|
// Sigmoidal colorspace modification to reduce ringing artifacts
|
|
112
112
|
args.push("-colorspace", "RGB");
|
|
113
113
|
args.push("+sigmoidal-contrast", "6.5,50%");
|
|
114
|
-
log.
|
|
114
|
+
log.debug("Using sigmoidal contrast modification for resize processing");
|
|
115
115
|
break;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -144,7 +144,7 @@ export async function imageResizer(
|
|
|
144
144
|
// Output path
|
|
145
145
|
args.push(outputPath);
|
|
146
146
|
|
|
147
|
-
log.
|
|
147
|
+
log.debug(`ImageMagick command: ${command} ${args.join(" ")}`);
|
|
148
148
|
|
|
149
149
|
const { stderr } = await execFile(command, args);
|
|
150
150
|
|
package/src/dsl/dsl-workflow.ts
CHANGED
|
@@ -20,14 +20,15 @@ import {
|
|
|
20
20
|
DSLWorkflowSpec,
|
|
21
21
|
getDocumentIds,
|
|
22
22
|
getTenantId,
|
|
23
|
-
WorkflowExecutionPayload
|
|
23
|
+
WorkflowExecutionPayload,
|
|
24
|
+
WorkflowInputFile
|
|
24
25
|
} from "@vertesia/common";
|
|
25
26
|
import ms, { StringValue } from 'ms';
|
|
26
27
|
import { HandleDslErrorParams } from "../activities/handleError.js";
|
|
27
28
|
import * as activities from "../activities/index.js";
|
|
29
|
+
import { RateLimitParams } from "../activities/rateLimiter.js";
|
|
28
30
|
import { WF_NON_RETRYABLE_ERRORS, WorkflowParamNotFoundError } from "../errors.js";
|
|
29
31
|
import { Vars } from "./vars.js";
|
|
30
|
-
import { RateLimitParams } from "../activities/rateLimiter.js";
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
interface BaseActivityPayload extends WorkflowExecutionPayload {
|
|
@@ -49,6 +50,23 @@ export async function dslWorkflow(payload: DSLWorkflowExecutionPayload) {
|
|
|
49
50
|
if (!definition) {
|
|
50
51
|
throw new WorkflowParamNotFoundError("workflow");
|
|
51
52
|
}
|
|
53
|
+
|
|
54
|
+
// Normalize input: convert legacy objectIds format to new input format
|
|
55
|
+
if (!payload.input && payload.objectIds) {
|
|
56
|
+
payload.input = {
|
|
57
|
+
inputType: 'objectIds',
|
|
58
|
+
objectIds: payload.objectIds
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate that workflow has input
|
|
63
|
+
if (!payload.input && !payload.objectIds) {
|
|
64
|
+
throw new WorkflowParamNotFoundError(
|
|
65
|
+
"input",
|
|
66
|
+
definition
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
// the base payload will be used to create the activities payload
|
|
53
71
|
const basePayload: BaseActivityPayload = {
|
|
54
72
|
...payload,
|
|
@@ -73,12 +91,33 @@ export async function dslWorkflow(payload: DSLWorkflowExecutionPayload) {
|
|
|
73
91
|
});
|
|
74
92
|
const defaultProxy = proxyActivities(defaultOptions);
|
|
75
93
|
log.debug("Default activity proxy is ready");
|
|
76
|
-
|
|
94
|
+
|
|
95
|
+
// Merge default vars with the payload vars and add input variables
|
|
96
|
+
const inputType = payload.input?.inputType || 'objectIds';
|
|
97
|
+
|
|
98
|
+
// Extract objectIds and files based on input type
|
|
99
|
+
let objectIds: string[] = [];
|
|
100
|
+
let files: WorkflowInputFile[] = [];
|
|
101
|
+
|
|
102
|
+
if (payload.input) {
|
|
103
|
+
if (payload.input.inputType === 'objectIds') {
|
|
104
|
+
objectIds = payload.input.objectIds;
|
|
105
|
+
} else if (payload.input.inputType === 'files') {
|
|
106
|
+
files = payload.input.files;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
77
110
|
const vars = new Vars({
|
|
78
111
|
...definition.vars,
|
|
79
112
|
...payload.vars,
|
|
80
|
-
|
|
81
|
-
|
|
113
|
+
// Add input type variables
|
|
114
|
+
inputType,
|
|
115
|
+
// Add objectIds variables (for objectIds input or backward compatibility)
|
|
116
|
+
objectIds,
|
|
117
|
+
objectId: objectIds[0],
|
|
118
|
+
// Add files variables (for files input)
|
|
119
|
+
files,
|
|
120
|
+
file: files[0],
|
|
82
121
|
});
|
|
83
122
|
|
|
84
123
|
log.info("Executing workflow", { payload });
|
|
@@ -153,6 +192,10 @@ async function handleError(originalError: any, basePayload: BaseActivityPayload,
|
|
|
153
192
|
}
|
|
154
193
|
|
|
155
194
|
async function startChildWorkflow(step: DSLChildWorkflowStep, payload: DSLWorkflowExecutionPayload, vars: Vars, debug_mode?: boolean) {
|
|
195
|
+
if (step.condition && !vars.match(step.condition)) {
|
|
196
|
+
log.info("Child workflow skipped: condition not satisfied", { workflow: step.name, condition: step.condition });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
156
199
|
const resolvedVars = vars.resolve();
|
|
157
200
|
if (step.vars) {
|
|
158
201
|
// copy user vars (from step definition) to the resolved vars, resolving any expressions
|
|
@@ -187,6 +230,10 @@ async function startChildWorkflow(step: DSLChildWorkflowStep, payload: DSLWorkfl
|
|
|
187
230
|
}
|
|
188
231
|
|
|
189
232
|
async function executeChildWorkflow(step: DSLChildWorkflowStep, payload: DSLWorkflowExecutionPayload, vars: Vars, debug_mode?: boolean) {
|
|
233
|
+
if (step.condition && !vars.match(step.condition)) {
|
|
234
|
+
log.info("Child workflow skipped: condition not satisfied", { workflow: step.name, condition: step.condition });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
190
237
|
const resolvedVars = vars.resolve();
|
|
191
238
|
if (step.vars) {
|
|
192
239
|
// copy user vars (from step definition) to the resolved vars, resolving any expressions
|
|
@@ -282,9 +329,10 @@ async function runActivity(activity: DSLActivitySpec, basePayload: BaseActivityP
|
|
|
282
329
|
log.info("Activity skipped: condition not satisfied", activity.condition);
|
|
283
330
|
return;
|
|
284
331
|
}
|
|
332
|
+
|
|
285
333
|
const importParams = vars.createImportVars(activity.import);
|
|
286
334
|
const executionPayload = dslActivityPayload(basePayload, activity, importParams);
|
|
287
|
-
log.
|
|
335
|
+
log.debug("Executing activity: " + activity.name, { payload: executionPayload });
|
|
288
336
|
|
|
289
337
|
let proxy = defaultProxy;
|
|
290
338
|
if (activity.options) {
|
|
@@ -316,7 +364,7 @@ async function runActivity(activity: DSLActivitySpec, basePayload: BaseActivityP
|
|
|
316
364
|
];
|
|
317
365
|
|
|
318
366
|
if (activity.name && rateLimitedActivities.includes(activity.name)) {
|
|
319
|
-
log.
|
|
367
|
+
log.debug(`Applying rate limit for activity ${activity.name}`);
|
|
320
368
|
// Apply rate limiting logic here
|
|
321
369
|
// Check rate limit first - loop until no delay
|
|
322
370
|
const rateLimitParams = buildRateLimitParams(activity, executionPayload);
|
|
@@ -325,7 +373,7 @@ async function runActivity(activity: DSLActivitySpec, basePayload: BaseActivityP
|
|
|
325
373
|
let rateLimitResult = await proxy.checkRateLimit(rateLimitPayload);
|
|
326
374
|
|
|
327
375
|
while (rateLimitResult.delayMs > 0) {
|
|
328
|
-
log.
|
|
376
|
+
log.debug(`Rate limit delay applied: ${rateLimitResult.delayMs}ms`);
|
|
329
377
|
await sleep(rateLimitResult.delayMs);
|
|
330
378
|
|
|
331
379
|
// Check again after sleeping
|
|
@@ -338,7 +386,7 @@ async function runActivity(activity: DSLActivitySpec, basePayload: BaseActivityP
|
|
|
338
386
|
//TODO execute in parallel
|
|
339
387
|
log.info("Parallel execution not yet implemented");
|
|
340
388
|
} else {
|
|
341
|
-
log.
|
|
389
|
+
log.debug("Executing activity: " + activity.name, { importParams });
|
|
342
390
|
const result = await fn(executionPayload);
|
|
343
391
|
if (activity.output) {
|
|
344
392
|
vars.setValue(activity.output, result);
|