@vertesia/workflow 1.5.0-dev.20260714.072725Z → 1.5.0-dev.20260717.131047Z

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/workflow",
3
- "version": "1.5.0-dev.20260714.072725Z",
3
+ "version": "1.5.0-dev.20260717.131047Z",
4
4
  "type": "module",
5
5
  "description": "Vertesia workflow DSL",
6
6
  "main": "./lib/index.js",
@@ -14,10 +14,10 @@
14
14
  },
15
15
  "license": "Apache-2.0",
16
16
  "devDependencies": {
17
- "@smithy/types": "^4.15.0",
18
- "@temporalio/proto": "^1.19.0",
19
- "@temporalio/testing": "^1.19.0",
20
- "@temporalio/worker": "^1.19.0",
17
+ "@smithy/types": "^4.15.1",
18
+ "@temporalio/proto": "^1.20.2",
19
+ "@temporalio/testing": "^1.20.2",
20
+ "@temporalio/worker": "^1.20.2",
21
21
  "@types/jsonwebtoken": "^9.0.10",
22
22
  "@types/node": "^24.13.2",
23
23
  "@types/papaparse": "^5.5.2",
@@ -27,11 +27,11 @@
27
27
  "@vertesia/tsconfig": "0.1.0"
28
28
  },
29
29
  "dependencies": {
30
- "@aws-sdk/client-s3": "^3.1078.0",
31
- "@aws-sdk/client-textract": "^3.1078.0",
32
- "@aws-sdk/credential-providers": "^3.1078.0",
33
- "@temporalio/activity": "^1.19.0",
34
- "@temporalio/workflow": "^1.19.0",
30
+ "@aws-sdk/client-s3": "^3.1083.0",
31
+ "@aws-sdk/client-textract": "^3.1083.0",
32
+ "@aws-sdk/credential-providers": "^3.1081.0",
33
+ "@temporalio/activity": "^1.20.2",
34
+ "@temporalio/workflow": "^1.20.2",
35
35
  "fast-deep-equal": "^3.1.3",
36
36
  "jsonwebtoken": "^9.0.3",
37
37
  "mime": "^4.1.0",
@@ -47,9 +47,9 @@
47
47
  "undici": "^7.28.0",
48
48
  "yaml": "^2.9.0",
49
49
  "@llumiverse/common": "1.5.0-dev.20260713.114357Z",
50
- "@vertesia/api-fetch-client": "1.5.0-dev.20260714.072725Z",
51
- "@vertesia/common": "1.5.0-dev.20260714.072725Z",
52
- "@vertesia/client": "1.5.0-dev.20260714.072725Z"
50
+ "@vertesia/client": "1.5.0-dev.20260717.131047Z",
51
+ "@vertesia/api-fetch-client": "1.5.0-dev.20260717.131047Z",
52
+ "@vertesia/common": "1.5.0-dev.20260717.131047Z"
53
53
  },
54
54
  "exports": {
55
55
  ".": {
@@ -103,6 +103,7 @@
103
103
  "agents",
104
104
  "typescript"
105
105
  ],
106
+ "gitHead": "8aaad228827b2927db8e6aa75b27bfac90fec36f",
106
107
  "scripts": {
107
108
  "lint": "biome lint src",
108
109
  "test": "vitest run",
@@ -0,0 +1,114 @@
1
+ import { MockActivityEnvironment } from '@temporalio/testing';
2
+ import type { VertesiaClient } from '@vertesia/client';
3
+ import {
4
+ ContentEventName,
5
+ type ContentObject,
6
+ type DSLActivityExecutionPayload,
7
+ SupportedEmbeddingTypes,
8
+ } from '@vertesia/common';
9
+ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
10
+ import type { ActivityContext } from '../dsl/setup/ActivityContext.js';
11
+ import { type GenerateEmbeddingsParams, generateEmbeddings } from './generateEmbeddings.js';
12
+
13
+ vi.mock('../dsl/setup/ActivityContext.js', async (importOriginal) => {
14
+ const actual = await importOriginal<typeof import('../dsl/setup/ActivityContext.js')>();
15
+ return { ...actual, setupActivity: vi.fn() };
16
+ });
17
+
18
+ let testEnv: MockActivityEnvironment;
19
+
20
+ beforeAll(() => {
21
+ testEnv = new MockActivityEnvironment();
22
+ });
23
+
24
+ beforeEach(() => {
25
+ vi.clearAllMocks();
26
+ });
27
+
28
+ const createPayload = (params: GenerateEmbeddingsParams): DSLActivityExecutionPayload<GenerateEmbeddingsParams> => {
29
+ const activityParams: GenerateEmbeddingsParams & Record<string, unknown> = { ...params };
30
+ return {
31
+ auth_token: 'mock-token',
32
+ account_id: 'test-account',
33
+ project_id: 'test-project',
34
+ params,
35
+ config: { studio_url: 'http://mock-studio', store_url: 'http://mock-store' },
36
+ workflow_name: 'StandardDocumentIntake',
37
+ event: ContentEventName.create,
38
+ objectIds: ['properties-only-object'],
39
+ input: { inputType: 'objectIds', objectIds: ['properties-only-object'] },
40
+ vars: {},
41
+ activity: { name: 'generateEmbeddings', params: activityParams },
42
+ };
43
+ };
44
+
45
+ describe('generateEmbeddings', () => {
46
+ it('should generate property embeddings for an object without content', async () => {
47
+ const { setupActivity } = await import('../dsl/setup/ActivityContext.js');
48
+ const document = {
49
+ id: 'properties-only-object',
50
+ properties: {
51
+ title: 'Properties-only object',
52
+ category: 'metadata',
53
+ },
54
+ } satisfies Partial<ContentObject>;
55
+ const embeddingResponse = {
56
+ model: 'embedding-model',
57
+ results: [{ outputs: [{ values: [0.1, 0.2, 0.3] }] }],
58
+ };
59
+ const client = {
60
+ objects: {
61
+ retrieve: vi.fn().mockResolvedValue(document),
62
+ setEmbedding: vi.fn().mockResolvedValue(undefined),
63
+ },
64
+ environments: {
65
+ embeddings: vi.fn().mockResolvedValue(embeddingResponse),
66
+ },
67
+ } as unknown as VertesiaClient;
68
+ const params = {
69
+ type: SupportedEmbeddingTypes.properties,
70
+ force: false,
71
+ } satisfies GenerateEmbeddingsParams;
72
+
73
+ vi.mocked(setupActivity).mockResolvedValue({
74
+ client,
75
+ objectId: document.id,
76
+ params,
77
+ fetchProject: vi.fn().mockResolvedValue({
78
+ name: 'Test Project',
79
+ namespace: 'test-project',
80
+ configuration: {
81
+ embeddings: {
82
+ [SupportedEmbeddingTypes.properties]: {
83
+ enabled: true,
84
+ environment: 'test-environment',
85
+ max_tokens: 8000,
86
+ },
87
+ },
88
+ },
89
+ }),
90
+ } as unknown as ActivityContext<GenerateEmbeddingsParams>);
91
+
92
+ const result = await testEnv.run(generateEmbeddings, createPayload(params));
93
+
94
+ expect(result).toEqual({
95
+ id: document.id,
96
+ type: SupportedEmbeddingTypes.properties,
97
+ status: 'completed',
98
+ len: 3,
99
+ });
100
+ expect(client.objects.retrieve).toHaveBeenCalledWith(
101
+ document.id,
102
+ '+text +parts +embeddings +tokens +properties',
103
+ );
104
+ expect(client.environments.embeddings).toHaveBeenCalledWith('test-environment', {
105
+ inputs: [{ type: 'text', text: JSON.stringify(document.properties) }],
106
+ model: undefined,
107
+ });
108
+ expect(client.objects.setEmbedding).toHaveBeenCalledWith(document.id, SupportedEmbeddingTypes.properties, {
109
+ values: embeddingResponse.results[0].outputs[0].values,
110
+ model: embeddingResponse.model,
111
+ etag: expect.any(String),
112
+ });
113
+ });
114
+ });
@@ -113,10 +113,6 @@ export async function generateEmbeddings(payload: DSLActivityExecutionPayload<Ge
113
113
  throw new DocumentNotFoundError('Document not found', [objectId]);
114
114
  }
115
115
 
116
- if (!document.content) {
117
- throw new DocumentNotFoundError('Document content not found', [objectId]);
118
- }
119
-
120
116
  let res:
121
117
  | Awaited<ReturnType<typeof generateTextEmbeddings>>
122
118
  | Awaited<ReturnType<typeof generateImageEmbeddings>>
@@ -174,14 +170,19 @@ async function generateTextEmbeddings({ document, client, type, config, force }:
174
170
  };
175
171
  }
176
172
 
177
- if (type === SupportedEmbeddingTypes.text && !document.text) {
178
- return { id: document.id, status: 'failed', message: 'no text found' };
179
- }
180
- if (type === SupportedEmbeddingTypes.properties && !document?.properties) {
173
+ const sourceText =
174
+ type === SupportedEmbeddingTypes.text
175
+ ? document.text
176
+ : document.properties
177
+ ? JSON.stringify(document.properties)
178
+ : undefined;
179
+
180
+ if (!sourceText) {
181
181
  return {
182
182
  id: document.id,
183
- status: 'failed',
184
- message: 'no properties found',
183
+ type,
184
+ status: 'skipped',
185
+ message: type === SupportedEmbeddingTypes.text ? 'no text found' : 'no properties found',
185
186
  };
186
187
  }
187
188
 
@@ -192,12 +193,12 @@ async function generateTextEmbeddings({ document, client, type, config, force }:
192
193
  );
193
194
  }
194
195
 
195
- // Compute text etag for comparison
196
- const textEtag = document.text_etag ?? (document.text ? md5(document.text) : undefined);
196
+ const sourceEtag =
197
+ type === SupportedEmbeddingTypes.text ? (document.text_etag ?? md5(sourceText)) : md5(sourceText);
197
198
 
198
199
  // Skip if embeddings already exist with matching etag (unless force=true)
199
200
  const existingEmbedding = document.embeddings?.[type];
200
- if (!force && existingEmbedding?.etag && textEtag && existingEmbedding.etag === textEtag) {
201
+ if (!force && existingEmbedding?.etag && existingEmbedding.etag === sourceEtag) {
201
202
  log.debug(`Skipping ${type} embeddings for document ${document.id} - etag unchanged`);
202
203
  return {
203
204
  id: document.id,
@@ -208,15 +209,7 @@ async function generateTextEmbeddings({ document, client, type, config, force }:
208
209
  }
209
210
 
210
211
  // Count tokens if needed, do not rely on existing token count
211
- let tokenCount: number | undefined;
212
- if (type === SupportedEmbeddingTypes.text && document.text) {
213
- tokenCount = countTokens(document.text).count;
214
- }
215
-
216
- if (type === SupportedEmbeddingTypes.properties && document.properties) {
217
- const propertiesText = JSON.stringify(document.properties);
218
- tokenCount = countTokens(propertiesText).count;
219
- }
212
+ const tokenCount = countTokens(sourceText).count;
220
213
 
221
214
  const maxTokens = config.max_tokens ?? 8000;
222
215
 
@@ -233,7 +226,7 @@ async function generateTextEmbeddings({ document, client, type, config, force }:
233
226
  } else {
234
227
  log.debug(`Generating ${type} embeddings for document`);
235
228
 
236
- const res = await generateEmbeddingsFromStudio(JSON.stringify(document[type]), environment, client);
229
+ const res = await generateEmbeddingsFromStudio(sourceText, environment, client);
237
230
  const values = res?.results?.[0]?.outputs?.[0]?.values;
238
231
  if (!values) {
239
232
  return {
@@ -249,7 +242,7 @@ async function generateTextEmbeddings({ document, client, type, config, force }:
249
242
  await client.objects.setEmbedding(document.id, type, {
250
243
  values,
251
244
  model: res.model,
252
- etag: textEtag,
245
+ etag: sourceEtag,
253
246
  });
254
247
 
255
248
  return {
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { stripWorkflowContinuationFromVars } from './dslProxyActivities.js';
3
+
4
+ describe('stripWorkflowContinuationFromVars', () => {
5
+ it('removes continue-as-new state from activity vars', () => {
6
+ const vars = {
7
+ interaction: 'sys:AppDeveloper',
8
+ _continuation: {
9
+ conversationState: { large: true },
10
+ },
11
+ };
12
+
13
+ expect(stripWorkflowContinuationFromVars(vars)).toEqual({
14
+ interaction: 'sys:AppDeveloper',
15
+ });
16
+ expect(vars).toHaveProperty('_continuation');
17
+ });
18
+
19
+ it('returns primitive and array vars unchanged', () => {
20
+ expect(stripWorkflowContinuationFromVars(undefined)).toBeUndefined();
21
+ expect(stripWorkflowContinuationFromVars(['a'])).toEqual(['a']);
22
+ });
23
+ });
@@ -14,6 +14,16 @@ export type DslSimplifiedActivityFunction<ParamsT extends object = Record<string
14
14
  params: ParamsT,
15
15
  ) => Promise<ReturnT>;
16
16
 
17
+ export function stripWorkflowContinuationFromVars<T>(vars: T): T {
18
+ if (!vars || typeof vars !== 'object' || Array.isArray(vars) || !('_continuation' in vars)) {
19
+ return vars;
20
+ }
21
+
22
+ const rest = { ...(vars as Record<string, unknown>) };
23
+ delete rest._continuation;
24
+ return rest as T;
25
+ }
26
+
17
27
  export function dslProxyActivities<ActivitiesT extends object>(workflowName: string, options: ActivityOptions = {}) {
18
28
  type DslActivities = {
19
29
  [K in keyof ActivitiesT]: ActivitiesT[K] extends DslActivityFunction<infer ParamsT, infer ReturnT>
@@ -34,6 +44,7 @@ export function dslProxyActivities<ActivitiesT extends object>(workflowName: str
34
44
  return (payload: WorkflowExecutionPayload, params: Record<string, unknown>) => {
35
45
  return activityFn({
36
46
  ...payload,
47
+ vars: stripWorkflowContinuationFromVars(payload.vars),
37
48
  activity: {
38
49
  name: prop as string,
39
50
  },
package/src/errors.ts CHANGED
@@ -116,4 +116,9 @@ export const WF_NON_RETRYABLE_ERRORS = [
116
116
  'TokenExpiredError',
117
117
  'ZenoClientNotFoundError',
118
118
  'WorkflowExecutionError',
119
+ // Temporal raises NotFoundError when an activity isn't registered on the worker
120
+ // (e.g. a new tool whose activity barrel export was missed). Retrying can never
121
+ // make it appear within a worker version, and the retried failure carries the
122
+ // full activity list which blows the payload size limit — so fail fast.
123
+ 'NotFoundError',
119
124
  ];