@vertesia/workflow 1.1.0-dev.20260327.125707Z → 1.1.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 (98) hide show
  1. package/lib/cjs/activities/executeRemoteActivity.js +21 -2
  2. package/lib/cjs/activities/executeRemoteActivity.js.map +1 -1
  3. package/lib/cjs/activities/generateEmbeddings.js +5 -3
  4. package/lib/cjs/activities/generateEmbeddings.js.map +1 -1
  5. package/lib/cjs/activities/handleError.js +8 -1
  6. package/lib/cjs/activities/handleError.js.map +1 -1
  7. package/lib/cjs/activities/index-dsl.js +5 -1
  8. package/lib/cjs/activities/index-dsl.js.map +1 -1
  9. package/lib/cjs/activities/loadChildWorkflowSpec.js +15 -0
  10. package/lib/cjs/activities/loadChildWorkflowSpec.js.map +1 -0
  11. package/lib/cjs/activities/media/prepareAudio.js +3 -2
  12. package/lib/cjs/activities/media/prepareAudio.js.map +1 -1
  13. package/lib/cjs/activities/media/prepareVideo.js +4 -1
  14. package/lib/cjs/activities/media/prepareVideo.js.map +1 -1
  15. package/lib/cjs/activities/media/probeMediaStreams.js +49 -0
  16. package/lib/cjs/activities/media/probeMediaStreams.js.map +1 -0
  17. package/lib/cjs/activities/media/saveGladiaTranscription.js +8 -0
  18. package/lib/cjs/activities/media/saveGladiaTranscription.js.map +1 -1
  19. package/lib/cjs/activities/media/transcribeMediaWithGladia.js +8 -0
  20. package/lib/cjs/activities/media/transcribeMediaWithGladia.js.map +1 -1
  21. package/lib/cjs/activities/resolveRemoteActivities.js +29 -16
  22. package/lib/cjs/activities/resolveRemoteActivities.js.map +1 -1
  23. package/lib/cjs/dsl/dsl-workflow.js +22 -4
  24. package/lib/cjs/dsl/dsl-workflow.js.map +1 -1
  25. package/lib/cjs/dsl/setup/ActivityContext.js +12 -2
  26. package/lib/cjs/dsl/setup/ActivityContext.js.map +1 -1
  27. package/lib/cjs/security/ssrf.js +34 -0
  28. package/lib/cjs/security/ssrf.js.map +1 -0
  29. package/lib/esm/activities/executeRemoteActivity.js +21 -2
  30. package/lib/esm/activities/executeRemoteActivity.js.map +1 -1
  31. package/lib/esm/activities/generateEmbeddings.js +5 -3
  32. package/lib/esm/activities/generateEmbeddings.js.map +1 -1
  33. package/lib/esm/activities/handleError.js +8 -1
  34. package/lib/esm/activities/handleError.js.map +1 -1
  35. package/lib/esm/activities/index-dsl.js +2 -0
  36. package/lib/esm/activities/index-dsl.js.map +1 -1
  37. package/lib/esm/activities/loadChildWorkflowSpec.js +12 -0
  38. package/lib/esm/activities/loadChildWorkflowSpec.js.map +1 -0
  39. package/lib/esm/activities/media/prepareAudio.js +3 -2
  40. package/lib/esm/activities/media/prepareAudio.js.map +1 -1
  41. package/lib/esm/activities/media/prepareVideo.js +5 -2
  42. package/lib/esm/activities/media/prepareVideo.js.map +1 -1
  43. package/lib/esm/activities/media/probeMediaStreams.js +46 -0
  44. package/lib/esm/activities/media/probeMediaStreams.js.map +1 -0
  45. package/lib/esm/activities/media/saveGladiaTranscription.js +8 -0
  46. package/lib/esm/activities/media/saveGladiaTranscription.js.map +1 -1
  47. package/lib/esm/activities/media/transcribeMediaWithGladia.js +8 -0
  48. package/lib/esm/activities/media/transcribeMediaWithGladia.js.map +1 -1
  49. package/lib/esm/activities/resolveRemoteActivities.js +29 -16
  50. package/lib/esm/activities/resolveRemoteActivities.js.map +1 -1
  51. package/lib/esm/dsl/dsl-workflow.js +22 -4
  52. package/lib/esm/dsl/dsl-workflow.js.map +1 -1
  53. package/lib/esm/dsl/setup/ActivityContext.js +12 -2
  54. package/lib/esm/dsl/setup/ActivityContext.js.map +1 -1
  55. package/lib/esm/security/ssrf.js +29 -0
  56. package/lib/esm/security/ssrf.js.map +1 -0
  57. package/lib/tsconfig.tsbuildinfo +1 -1
  58. package/lib/types/activities/executeRemoteActivity.d.ts.map +1 -1
  59. package/lib/types/activities/generateEmbeddings.d.ts +6 -4
  60. package/lib/types/activities/generateEmbeddings.d.ts.map +1 -1
  61. package/lib/types/activities/handleError.d.ts.map +1 -1
  62. package/lib/types/activities/index-dsl.d.ts +3 -0
  63. package/lib/types/activities/index-dsl.d.ts.map +1 -1
  64. package/lib/types/activities/loadChildWorkflowSpec.d.ts +6 -0
  65. package/lib/types/activities/loadChildWorkflowSpec.d.ts.map +1 -0
  66. package/lib/types/activities/media/prepareAudio.d.ts.map +1 -1
  67. package/lib/types/activities/media/prepareVideo.d.ts.map +1 -1
  68. package/lib/types/activities/media/probeMediaStreams.d.ts +12 -0
  69. package/lib/types/activities/media/probeMediaStreams.d.ts.map +1 -0
  70. package/lib/types/activities/media/saveGladiaTranscription.d.ts.map +1 -1
  71. package/lib/types/activities/media/transcribeMediaWithGladia.d.ts.map +1 -1
  72. package/lib/types/activities/resolveRemoteActivities.d.ts.map +1 -1
  73. package/lib/types/dsl/dsl-workflow.d.ts.map +1 -1
  74. package/lib/types/dsl/setup/ActivityContext.d.ts.map +1 -1
  75. package/lib/types/security/ssrf.d.ts +18 -0
  76. package/lib/types/security/ssrf.d.ts.map +1 -0
  77. package/lib/types/system/recalculateEmbeddingsWorkflow.d.ts +6 -4
  78. package/lib/types/system/recalculateEmbeddingsWorkflow.d.ts.map +1 -1
  79. package/lib/workflows-bundle.js +352 -158
  80. package/package.json +7 -7
  81. package/src/activities/executeRemoteActivity.test.ts +8 -0
  82. package/src/activities/executeRemoteActivity.ts +21 -2
  83. package/src/activities/generateEmbeddings.ts +6 -3
  84. package/src/activities/handleError.ts +9 -1
  85. package/src/activities/index-dsl.ts +3 -0
  86. package/src/activities/loadChildWorkflowSpec.ts +21 -0
  87. package/src/activities/media/prepareAudio.ts +3 -2
  88. package/src/activities/media/prepareVideo.ts +5 -2
  89. package/src/activities/media/probeMediaStreams.test.ts +126 -0
  90. package/src/activities/media/probeMediaStreams.ts +81 -0
  91. package/src/activities/media/saveGladiaTranscription.ts +8 -0
  92. package/src/activities/media/transcribeMediaWithGladia.ts +8 -0
  93. package/src/activities/resolveRemoteActivities.test.ts +11 -10
  94. package/src/activities/resolveRemoteActivities.ts +31 -16
  95. package/src/dsl/dsl-workflow.ts +22 -4
  96. package/src/dsl/setup/ActivityContext.test.ts +57 -0
  97. package/src/dsl/setup/ActivityContext.ts +16 -2
  98. package/src/security/ssrf.ts +32 -0
@@ -183,9 +183,9 @@ async function executeSteps(definition: DSLWorkflowSpec, payload: DSLWorkflowExe
183
183
  if (stepType === 'workflow') {
184
184
  const childWorkflowStep = step as DSLChildWorkflowStep;
185
185
  if (childWorkflowStep.async) {
186
- await startChildWorkflow(childWorkflowStep, payload, vars, basePayload.debug_mode);
186
+ await startChildWorkflow(childWorkflowStep, payload, vars, basePayload.debug_mode, defaultProxy, basePayload);
187
187
  } else {
188
- await executeChildWorkflow(childWorkflowStep, payload, vars, basePayload.debug_mode);
188
+ await executeChildWorkflow(childWorkflowStep, payload, vars, basePayload.debug_mode, defaultProxy, basePayload);
189
189
  }
190
190
  } else { // activity
191
191
  await runActivity(step as DSLActivitySpec, basePayload, vars, defaultProxy, defaultOptions, remoteActivities);
@@ -236,11 +236,20 @@ async function handleError(originalError: any, basePayload: BaseActivityPayload,
236
236
  throw originalError;
237
237
  }
238
238
 
239
- async function startChildWorkflow(step: DSLChildWorkflowStep, payload: DSLWorkflowExecutionPayload, vars: Vars, debug_mode?: boolean) {
239
+ async function startChildWorkflow(step: DSLChildWorkflowStep, payload: DSLWorkflowExecutionPayload, vars: Vars, debug_mode?: boolean, proxy?: ActivityInterfaceFor<UntypedActivities>, basePayload?: BaseActivityPayload) {
240
240
  if (step.condition && !vars.match(step.condition)) {
241
241
  log.info("Child workflow skipped: condition not satisfied", { workflow: step.name, condition: step.condition });
242
242
  return;
243
243
  }
244
+ if (step.name.startsWith('dsl:') && !step.spec && proxy && basePayload) {
245
+ const workflowName = step.name.slice(4);
246
+ const specPayload = dslActivityPayload(basePayload, { name: 'loadChildWorkflowSpec' } as DSLActivitySpec, { workflowName });
247
+ const spec = await proxy.loadChildWorkflowSpec(specPayload) as DSLWorkflowSpec;
248
+ const humanName = spec.name.replace(/([A-Z])/g, ' $1').trim();
249
+ const objectIds = getDocumentIds(payload);
250
+ const workflowId = objectIds.length > 0 ? `${humanName}:${objectIds[0]}` : humanName;
251
+ step = { ...step, name: 'dslWorkflow', spec, options: { ...step.options, workflowId } };
252
+ }
244
253
  const resolvedVars = vars.resolve();
245
254
  if (step.vars) {
246
255
  // copy user vars (from step definition) to the resolved vars, resolving any expressions
@@ -274,11 +283,20 @@ async function startChildWorkflow(step: DSLChildWorkflowStep, payload: DSLWorkfl
274
283
  }
275
284
  }
276
285
 
277
- async function executeChildWorkflow(step: DSLChildWorkflowStep, payload: DSLWorkflowExecutionPayload, vars: Vars, debug_mode?: boolean) {
286
+ async function executeChildWorkflow(step: DSLChildWorkflowStep, payload: DSLWorkflowExecutionPayload, vars: Vars, debug_mode?: boolean, proxy?: ActivityInterfaceFor<UntypedActivities>, basePayload?: BaseActivityPayload) {
278
287
  if (step.condition && !vars.match(step.condition)) {
279
288
  log.info("Child workflow skipped: condition not satisfied", { workflow: step.name, condition: step.condition });
280
289
  return;
281
290
  }
291
+ if (step.name.startsWith('dsl:') && !step.spec && proxy && basePayload) {
292
+ const workflowName = step.name.slice(4);
293
+ const specPayload = dslActivityPayload(basePayload, { name: 'loadChildWorkflowSpec' } as DSLActivitySpec, { workflowName });
294
+ const spec = await proxy.loadChildWorkflowSpec(specPayload) as DSLWorkflowSpec;
295
+ const humanName = spec.name.replace(/([A-Z])/g, ' $1').trim();
296
+ const objectIds = getDocumentIds(payload);
297
+ const workflowId = objectIds.length > 0 ? `${humanName}:${objectIds[0]}` : humanName;
298
+ step = { ...step, name: 'dslWorkflow', spec, options: { ...step.options, workflowId } };
299
+ }
282
300
  const resolvedVars = vars.resolve();
283
301
  if (step.vars) {
284
302
  // copy user vars (from step definition) to the resolved vars, resolving any expressions
@@ -0,0 +1,57 @@
1
+ import { ContentEventName, DSLActivityExecutionPayload } from "@vertesia/common";
2
+ import { describe, expect, it } from "vitest";
3
+ import { setupActivity } from "./ActivityContext.js";
4
+
5
+ const MOCK_AUTH_TOKEN =
6
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbW9jay10b2tlbi1zZXJ2ZXIiLCJzdWIiOiJ0ZXN0In0.signature";
7
+
8
+ function basePayload<T extends Record<string, any>>(
9
+ activity: DSLActivityExecutionPayload<T>["activity"],
10
+ params: T,
11
+ ): DSLActivityExecutionPayload<T> {
12
+ return {
13
+ event: ContentEventName.create,
14
+ vars: {},
15
+ account_id: "acc",
16
+ project_id: "prj",
17
+ auth_token: MOCK_AUTH_TOKEN,
18
+ config: { studio_url: "http://localhost", store_url: "http://localhost" },
19
+ activity,
20
+ params,
21
+ workflow_name: "test",
22
+ };
23
+ }
24
+
25
+ describe("setupActivity", () => {
26
+ // Regression guard: user-supplied strings containing "${...}" must be preserved
27
+ // verbatim when the activity is dispatched from TypeScript code (no activity.params,
28
+ // no activity.fetch). Previously Vars.parse/resolve would treat any "${...}" as a
29
+ // template ref and silently drop the key when it failed to resolve.
30
+ it("preserves user data containing literal ${} in code-dispatched activities", async () => {
31
+ const data = {
32
+ task: 'preserve "${}" literal',
33
+ other: "also has ${undefined_ref}",
34
+ };
35
+ const payload = basePayload({ name: "testActivity" }, { data });
36
+ const ctx = await setupActivity(payload);
37
+ expect(ctx.params.data.task).toBe('preserve "${}" literal');
38
+ expect(ctx.params.data.other).toBe("also has ${undefined_ref}");
39
+ });
40
+
41
+ it("returns empty params when code-dispatched activity has no params", async () => {
42
+ const payload = basePayload({ name: "testActivity" }, undefined as any);
43
+ const ctx = await setupActivity(payload);
44
+ expect(ctx.params).toEqual({});
45
+ });
46
+
47
+ // Counter-test: DSL-defined activities (those carrying activity.params) must
48
+ // still have their ${refs} resolved against payload.params (imported vars).
49
+ it("still resolves ${refs} in activity.params for DSL-dispatched activities", async () => {
50
+ const payload = basePayload(
51
+ { name: "dslActivity", params: { message: "Hello ${name}" } },
52
+ { name: "World" } as any,
53
+ );
54
+ const ctx = await setupActivity(payload);
55
+ expect((ctx.params as any).message).toBe("Hello World");
56
+ });
57
+ });
@@ -183,6 +183,22 @@ export class ActivityContext<ParamsT extends Record<string, any>> {
183
183
  export async function setupActivity<ParamsT extends Record<string, any>>(
184
184
  payload: DSLActivityExecutionPayload<ParamsT>,
185
185
  ) {
186
+ const client = await getVertesiaClient(payload);
187
+
188
+ // Activities dispatched from TypeScript code (via dslProxyActivities) set
189
+ // only `activity.name`; their params are a plain TS object that must not
190
+ // be run through Vars template expansion — doing so would interpret any
191
+ // `${...}` in user-supplied strings as a variable reference and silently
192
+ // drop the key when it fails to resolve. Only DSL-defined activities
193
+ // (which carry `activity.params` and/or `activity.fetch`) need Vars.
194
+ const isDslDispatched =
195
+ payload.activity.params !== undefined || payload.activity.fetch !== undefined;
196
+
197
+ if (!isDslDispatched) {
198
+ const params = (payload.params ?? {}) as ParamsT;
199
+ return new ActivityContext<ParamsT>(payload, client, params);
200
+ }
201
+
186
202
  const isDebugMode = !!payload.debug_mode;
187
203
 
188
204
  const vars = new Vars({
@@ -190,7 +206,6 @@ export async function setupActivity<ParamsT extends Record<string, any>>(
190
206
  ...payload.activity.params, // activity params (may contain expressions)
191
207
  });
192
208
 
193
- //}
194
209
  if (isDebugMode) {
195
210
  log.info(`Setting up activity ${payload.activity.name}`, {
196
211
  config: payload.config,
@@ -200,7 +215,6 @@ export async function setupActivity<ParamsT extends Record<string, any>>(
200
215
  });
201
216
  }
202
217
 
203
- const client = await getVertesiaClient(payload);
204
218
  const fetchSpecs = payload.activity.fetch;
205
219
  if (fetchSpecs) {
206
220
  const keys = Object.keys(fetchSpecs);
@@ -0,0 +1,32 @@
1
+ /**
2
+ * SSRF protection shim for @vertesia/workflow.
3
+ *
4
+ * URL validation is delegated via client.apps.validateUrl() so that security
5
+ * policy details (blocked IP ranges, ports, hostnames) are not exposed in this
6
+ * public package.
7
+ *
8
+ * This file only provides redirect-safe fetch and the error class.
9
+ */
10
+
11
+ export class URLValidationError extends Error {
12
+ constructor(message: string) {
13
+ super(message);
14
+ this.name = 'URLValidationError';
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Redirect-safe fetch wrapper. Does not validate the URL — call client.apps.validateUrl() first.
20
+ * @throws {URLValidationError} if the server redirects (potential redirect-based SSRF)
21
+ */
22
+ export async function safeFetch(url: string, init: RequestInit = {}): Promise<Response> {
23
+ const response = await fetch(url, { ...init, redirect: 'manual' });
24
+ // Only block actual redirects (those with a Location header). 304 Not Modified is a 3xx
25
+ // but carries no Location and must not be treated as a redirect.
26
+ if (response.headers.has('location')) {
27
+ throw new URLValidationError(
28
+ `Request to ${url} returned a redirect (${response.status}), which is not allowed`,
29
+ );
30
+ }
31
+ return response;
32
+ }