@camunda/e2e-test-suite 0.0.1455 → 0.0.1457

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.
@@ -17,6 +17,10 @@ declare class OperateProcessInstancePage {
17
17
  readonly messageVariable: Locator;
18
18
  readonly whatsNewPopUp: Locator;
19
19
  readonly gotItButton: Locator;
20
+ readonly detailsTab: Locator;
21
+ readonly agentInstancePanel: Locator;
22
+ readonly conversationHistorySection: Locator;
23
+ readonly conversationHistoryTrigger: Locator;
20
24
  constructor(page: Page);
21
25
  connectorResultVariableValue(variableName: string): Promise<Locator>;
22
26
  rememberInstanceUrl(timeout?: number, watchedUrl?: InstanceUrlWatcher): Promise<void>;
@@ -36,6 +40,10 @@ declare class OperateProcessInstancePage {
36
40
  assertProcessVariableContainsText(variableName: string, text: string): Promise<void>;
37
41
  assertProcessVariableHasDocumentCount(variableName: string, count: number, softFail?: boolean): Promise<void>;
38
42
  clickDiagramTask(taskName: string): Promise<void>;
43
+ openAgentConversation(elementId: string): Promise<void>;
44
+ assertReadableReasoning(reasoningHistoryItemKey: string, answerHistoryItemKey: string): Promise<void>;
45
+ assertEncryptedReasoningIsHidden(reasoningHistoryItemKey: string, answerHistoryItemKey: string): Promise<void>;
46
+ private assertFinalAnswerVisible;
39
47
  assertActiveTokenIsPresent(): Promise<void>;
40
48
  assertDetailsPopoverIsVisible(): Promise<void>;
41
49
  assertBusinessIdVisible(businessId: string): Promise<void>;
@@ -158,6 +158,10 @@ class OperateProcessInstancePage {
158
158
  messageVariable;
159
159
  whatsNewPopUp;
160
160
  gotItButton;
161
+ detailsTab;
162
+ agentInstancePanel;
163
+ conversationHistorySection;
164
+ conversationHistoryTrigger;
161
165
  constructor(page) {
162
166
  this.page = page;
163
167
  this.diagram = page.getByTestId('diagram');
@@ -185,6 +189,16 @@ class OperateProcessInstancePage {
185
189
  name: "Here's what moved in Operate",
186
190
  });
187
191
  this.gotItButton = page.getByRole('button', { name: 'Got it' });
192
+ this.detailsTab = page
193
+ .getByLabel('Process Instance Bottom Panel Tabs')
194
+ .getByRole('link', { name: 'Details' });
195
+ this.agentInstancePanel = page.getByRole('article', {
196
+ name: 'Agent Instance',
197
+ });
198
+ this.conversationHistorySection = this.agentInstancePanel.getByTestId('agent-conversation-history-section');
199
+ this.conversationHistoryTrigger = this.conversationHistorySection.getByRole('button', {
200
+ name: 'Conversation history',
201
+ });
188
202
  }
189
203
  async connectorResultVariableValue(variableName) {
190
204
  return await this.page.getByTestId(variableName).locator('td').last();
@@ -874,6 +888,48 @@ class OperateProcessInstancePage {
874
888
  await (0, test_1.expect)(diagramTask).toBeVisible();
875
889
  await diagramTask.click();
876
890
  }
891
+ async openAgentConversation(elementId) {
892
+ const agentTask = this.diagram.locator(`.djs-element[data-element-id="${elementId}"]`);
893
+ // BpmnJS ignores element clicks until element statistics load and the
894
+ // element receives the op-selectable marker.
895
+ await (0, test_1.expect)(agentTask).toHaveClass(/op-selectable/, { timeout: 30000 });
896
+ await agentTask.click();
897
+ await (0, test_1.expect)(this.detailsTab).toBeVisible();
898
+ await this.detailsTab.click();
899
+ await (0, test_1.expect)(this.agentInstancePanel).toBeVisible({ timeout: 30000 });
900
+ await (0, test_1.expect)(this.conversationHistoryTrigger).toHaveAttribute('aria-expanded', 'false');
901
+ await this.conversationHistoryTrigger.click();
902
+ await (0, test_1.expect)(this.conversationHistoryTrigger).toHaveAttribute('aria-expanded', 'true');
903
+ }
904
+ async assertReadableReasoning(reasoningHistoryItemKey, answerHistoryItemKey) {
905
+ const reasoningMessage = this.conversationHistorySection.getByTestId(`conversation-message-${reasoningHistoryItemKey}`);
906
+ const thinkingRegion = reasoningMessage.getByRole('region', {
907
+ name: 'Thinking',
908
+ });
909
+ const answerMessage = this.conversationHistorySection.getByTestId(`conversation-message-${answerHistoryItemKey}`);
910
+ await (0, test_1.expect)(reasoningMessage).toHaveAttribute('aria-label', 'Assistant message', { timeout: 60000 });
911
+ await (0, test_1.expect)(thinkingRegion).toBeVisible();
912
+ await (0, test_1.expect)(thinkingRegion.locator('p')).toHaveText(/\S/);
913
+ await (0, test_1.expect)(thinkingRegion.getByRole('button')).toHaveCount(0);
914
+ await this.assertFinalAnswerVisible(answerMessage);
915
+ await (0, test_1.expect)(this.conversationHistorySection).not.toContainText(/camunda\.agenticai\.content\.type|encrypted_content|signature|"payload"/);
916
+ }
917
+ async assertEncryptedReasoningIsHidden(reasoningHistoryItemKey, answerHistoryItemKey) {
918
+ const reasoningMessage = this.conversationHistorySection.getByTestId(`conversation-message-${reasoningHistoryItemKey}`);
919
+ const answerMessage = this.conversationHistorySection.getByTestId(`conversation-message-${answerHistoryItemKey}`);
920
+ await (0, test_1.expect)(reasoningMessage).toHaveAttribute('aria-label', 'Assistant message', { timeout: 60000 });
921
+ await (0, test_1.expect)(reasoningMessage).toBeVisible({ timeout: 60000 });
922
+ await (0, test_1.expect)(reasoningMessage.getByRole('region', { name: 'Thinking' })).toHaveCount(0);
923
+ await (0, test_1.expect)(this.conversationHistorySection).not.toContainText(/encrypted_content|camunda\.agenticai\.content\.type|"payload"|signature/);
924
+ await this.assertFinalAnswerVisible(answerMessage);
925
+ }
926
+ async assertFinalAnswerVisible(answerMessage) {
927
+ await (0, test_1.expect)(answerMessage).toHaveAttribute('aria-label', 'Assistant message', { timeout: 60000 });
928
+ // The Thinking note is a <section> named via aria-labelledby, so match
929
+ // only answer text that is not nested inside it.
930
+ const answerOutsideThinking = answerMessage.locator('xpath=.//*[text()[contains(., "Answer:")]][not(ancestor::section[@aria-labelledby = //*[normalize-space(text())="Thinking"]/@id])]');
931
+ await (0, test_1.expect)(answerOutsideThinking.first()).toBeVisible({ timeout: 60000 });
932
+ }
877
933
  async assertActiveTokenIsPresent() {
878
934
  await (0, test_1.expect)(this.activeIcon).toBeVisible({ timeout: 60000 });
879
935
  }
@@ -4,6 +4,7 @@ const _8_11_1 = require("../../fixtures/8.11");
4
4
  const UtilitiesPage_1 = require("../../pages/8.11/UtilitiesPage");
5
5
  const _setup_1 = require("../../test-setup.js");
6
6
  const apiHelpers_1 = require("../../utils/apiHelpers");
7
+ const agentInstanceHistoryHelpers_1 = require("../../utils/agentInstanceHistoryHelpers");
7
8
  const sleep_1 = require("../../utils/sleep");
8
9
  const users_1 = require("../../utils/users");
9
10
  const testUser = (0, users_1.getTestUser)('fourteenthUser');
@@ -62,6 +63,21 @@ const aiAgentSubProcessTestCases = [
62
63
  bpmnPath: './resources/agentic_ai/ai-agent-subprocess-with-tools_aws_key.bpmn',
63
64
  },
64
65
  ];
66
+ const agentReasoningTestCases = [
67
+ {
68
+ name: 'Readable reasoning appears in Operate',
69
+ bpmnPath: './resources/agentic_ai/ai-agent-chat-with-tools_anthropic_bedrock_apikey.bpmn',
70
+ provider: 'anthropic-adaptive',
71
+ reasoningMode: 'readable',
72
+ },
73
+ {
74
+ name: 'Encrypted reasoning stays hidden in Operate',
75
+ bpmnPath: './resources/agentic_ai/ai-agent-chat-with-tools_open_ai.bpmn',
76
+ provider: 'openai-encrypted',
77
+ reasoningMode: 'encrypted-only',
78
+ },
79
+ ];
80
+ const agentReasoningResources = new Map();
65
81
  const regexTestCases = [
66
82
  {
67
83
  regex_ai_agent_time: /The current date and time in ([A-Za-zÀ-ÿ .,'-]+(?:, [A-Za-zÀ-ÿ .,'-]+)*), is approximately ((?:0?[1-9]|1[0-2]):[0-5][0-9] (?:AM|PM)) on ([A-Z][a-z]+ (?:[1-9]|[12][0-9]|3[01]), \d{4})\./,
@@ -206,4 +222,75 @@ _8_11_1.test.describe('Agentic Orchestation User Flows', () => {
206
222
  });
207
223
  }
208
224
  });
225
+ _8_11_1.test.describe('AI Agent reasoning in Operate', () => {
226
+ // Playwright requires destructured fixture arguments.
227
+ // eslint-disable-next-line no-empty-pattern
228
+ _8_11_1.test.afterEach(async ({}, testInfo) => {
229
+ const resources = agentReasoningResources.get(testInfo.testId);
230
+ if (!resources) {
231
+ return;
232
+ }
233
+ const cleanupErrors = await (0, agentInstanceHistoryHelpers_1.cleanupAgentReasoningResources)(resources.processInstanceKey, resources.processDefinitionKey, authToken);
234
+ agentReasoningResources.delete(testInfo.testId);
235
+ if (cleanupErrors.length === 1) {
236
+ throw cleanupErrors[0];
237
+ }
238
+ if (cleanupErrors.length > 1) {
239
+ throw new AggregateError(cleanupErrors, 'Agent reasoning resource cleanup failed');
240
+ }
241
+ });
242
+ for (const params of agentReasoningTestCases) {
243
+ (0, _8_11_1.test)(params.name, async ({ appsPage, operateHomePage, operateProcessInstancePage, operateProcessesPage, taskPanelPage, taskDetailsPage, }, testInfo) => {
244
+ _8_11_1.test.slow();
245
+ const suffix = await (0, _setup_1.generateRandomStringAsync)(6);
246
+ const processId = `ai-agent-reasoning-${params.provider}-${suffix.toLowerCase()}`;
247
+ const processName = `AI Agent ${params.name} ${suffix}`;
248
+ const resources = {};
249
+ agentReasoningResources.set(testInfo.testId, resources);
250
+ const processDefinitionKey = await (0, agentInstanceHistoryHelpers_1.deployAgentReasoningProcess)(params.bpmnPath, authToken, processId, processName, params.provider);
251
+ resources.processDefinitionKey = processDefinitionKey;
252
+ const processInstanceKey = await (0, apiHelpers_1.createProcessInstance)(processDefinitionKey, authToken);
253
+ resources.processInstanceKey = processInstanceKey;
254
+ await _8_11_1.test.step('Wait for the agent feedback task and committed history', async () => {
255
+ await appsPage.clickCamundaApps();
256
+ await appsPage.clickTasklist(clusterName);
257
+ await taskPanelPage.openTask(processName);
258
+ await taskDetailsPage.clickAssignToMeButton();
259
+ await taskDetailsPage.assertAIResponse(/Answer:/);
260
+ });
261
+ const historyKeys = await (0, agentInstanceHistoryHelpers_1.waitForAgentReasoningHistory)(processInstanceKey, authToken, params.reasoningMode);
262
+ await _8_11_1.test.step('Verify the conversation in Operate', async () => {
263
+ await appsPage.clickCamundaApps();
264
+ await appsPage.clickOperate(clusterName);
265
+ await operateHomePage.clickProcessesTab();
266
+ await operateProcessesPage.applyMoreFilters('Process Instance Key(s)', processInstanceKey);
267
+ await operateProcessesPage.clickProcessInstanceLink(processName);
268
+ await operateProcessInstancePage.closePopOverIfVisible();
269
+ await operateProcessInstancePage.assertActiveTokenIsPresent();
270
+ await operateProcessInstancePage.openAgentConversation('AI_Agent');
271
+ if (params.reasoningMode === 'readable') {
272
+ await operateProcessInstancePage.assertReadableReasoning(historyKeys.reasoningHistoryItemKey, historyKeys.answerHistoryItemKey);
273
+ }
274
+ else {
275
+ await operateProcessInstancePage.assertEncryptedReasoningIsHidden(historyKeys.reasoningHistoryItemKey, historyKeys.answerHistoryItemKey);
276
+ }
277
+ });
278
+ await _8_11_1.test.step('Complete the feedback task and finish the process', async () => {
279
+ await appsPage.clickCamundaApps();
280
+ await appsPage.clickTasklist(clusterName);
281
+ await taskPanelPage.openTask(processName);
282
+ await taskDetailsPage.checkChecklistBox('Are you satisfied with the result?');
283
+ await taskDetailsPage.clickCompleteTaskButton();
284
+ await appsPage.clickCamundaApps();
285
+ await appsPage.clickOperate(clusterName);
286
+ await operateHomePage.clickProcessesTab();
287
+ await operateProcessesPage.clickProcessCompletedCheckbox();
288
+ await operateProcessesPage.applyMoreFilters('Process Instance Key(s)', processInstanceKey);
289
+ await operateProcessesPage.clickProcessInstanceLink(processName);
290
+ await operateProcessInstancePage.assertProcessCompleteStatusWithRetry();
291
+ resources.processInstanceKey = undefined;
292
+ });
293
+ });
294
+ }
295
+ });
209
296
  });
@@ -0,0 +1,11 @@
1
+ import { type AgentReasoningProvider } from './agentReasoningBpmnFixtures';
2
+ export type AgentReasoningHistoryKeys = {
3
+ agentInstanceKey: string;
4
+ answerHistoryItemKey: string;
5
+ reasoningHistoryItemKey: string;
6
+ };
7
+ export type { AgentReasoningProvider } from './agentReasoningBpmnFixtures';
8
+ export declare function deployAgentReasoningProcess(bpmnPath: string, authToken: string, processId: string, processName: string, provider: AgentReasoningProvider): Promise<string>;
9
+ export declare function cancelAgentReasoningProcessInstance(processInstanceKey: string, authToken: string): Promise<void>;
10
+ export declare function cleanupAgentReasoningResources(processInstanceKey: string | undefined, processDefinitionKey: string | undefined, authToken: string): Promise<unknown[]>;
11
+ export declare function waitForAgentReasoningHistory(processInstanceKey: string, authToken: string, reasoningMode: 'readable' | 'encrypted-only'): Promise<AgentReasoningHistoryKeys>;
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.waitForAgentReasoningHistory = exports.cleanupAgentReasoningResources = exports.cancelAgentReasoningProcessInstance = exports.deployAgentReasoningProcess = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ const agentReasoningBpmnFixtures_1 = require("./agentReasoningBpmnFixtures");
6
+ const apiHelpers_1 = require("./apiHelpers");
7
+ const REASONING_TYPE = 'camunda.agenticai.content.type';
8
+ const FINAL_ANSWER_MARKER = 'Answer:';
9
+ async function deployAgentReasoningProcess(bpmnPath, authToken, processId, processName, provider) {
10
+ const bpmn = (0, agentReasoningBpmnFixtures_1.buildAgentReasoningBpmn)(bpmnPath, processId, processName, provider);
11
+ const request = await (0, apiHelpers_1.getApiRequestContext)();
12
+ const multipart = {
13
+ resources: {
14
+ name: `${processId}.bpmn`,
15
+ mimeType: 'application/vnd.bpmn+xml',
16
+ buffer: Buffer.from(bpmn),
17
+ },
18
+ };
19
+ if (process.env.IS_MT === 'true') {
20
+ multipart.tenantId = '<default>';
21
+ }
22
+ const response = await request.post((0, apiHelpers_1.buildZeebeApiUrl)('/v2/deployments'), {
23
+ headers: { Authorization: authToken },
24
+ multipart,
25
+ });
26
+ (0, test_1.expect)(response.status(), `Reasoning process deployment failed for ${processName}`).toBe(200);
27
+ const body = (await response.json());
28
+ const processDefinitionKey = body.deployments?.[0]?.processDefinition?.processDefinitionKey;
29
+ if (processDefinitionKey === undefined || processDefinitionKey === null) {
30
+ throw new Error(`Reasoning process deployment returned no process definition key for ${processName}`);
31
+ }
32
+ return String(processDefinitionKey);
33
+ }
34
+ exports.deployAgentReasoningProcess = deployAgentReasoningProcess;
35
+ async function cancelAgentReasoningProcessInstance(processInstanceKey, authToken) {
36
+ const request = await (0, apiHelpers_1.getApiRequestContext)();
37
+ const response = await request.post((0, apiHelpers_1.buildZeebeApiUrl)(`/v2/process-instances/${encodeURIComponent(processInstanceKey)}/cancellation`), {
38
+ headers: { Authorization: authToken },
39
+ data: {},
40
+ });
41
+ (0, test_1.expect)([204, 409], `Cleanup cancellation failed for process instance ${processInstanceKey}`).toContain(response.status());
42
+ }
43
+ exports.cancelAgentReasoningProcessInstance = cancelAgentReasoningProcessInstance;
44
+ async function deleteAgentReasoningProcessDefinition(processDefinitionKey, authToken) {
45
+ const request = await (0, apiHelpers_1.getApiRequestContext)();
46
+ const response = await request.post((0, apiHelpers_1.buildZeebeApiUrl)(`/v2/resources/${encodeURIComponent(processDefinitionKey)}/deletion`), {
47
+ headers: { Authorization: authToken },
48
+ data: {},
49
+ });
50
+ (0, test_1.expect)(response.status(), `Cleanup deletion failed for process definition ${processDefinitionKey}`).toBe(200);
51
+ }
52
+ async function cleanupAgentReasoningResources(processInstanceKey, processDefinitionKey, authToken) {
53
+ const cleanupErrors = [];
54
+ if (processInstanceKey !== undefined) {
55
+ try {
56
+ await cancelAgentReasoningProcessInstance(processInstanceKey, authToken);
57
+ }
58
+ catch (error) {
59
+ cleanupErrors.push(error);
60
+ }
61
+ }
62
+ if (processDefinitionKey !== undefined) {
63
+ try {
64
+ await deleteAgentReasoningProcessDefinition(processDefinitionKey, authToken);
65
+ }
66
+ catch (error) {
67
+ cleanupErrors.push(error);
68
+ }
69
+ }
70
+ return cleanupErrors;
71
+ }
72
+ exports.cleanupAgentReasoningResources = cleanupAgentReasoningResources;
73
+ function isPlainObject(value) {
74
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
75
+ return false;
76
+ }
77
+ const prototype = Object.getPrototypeOf(value);
78
+ return prototype === Object.prototype || prototype === null;
79
+ }
80
+ function summarizeAgentInstances(instances) {
81
+ if (instances.length === 0) {
82
+ return '[]';
83
+ }
84
+ return JSON.stringify(instances.map((instance) => ({
85
+ agentInstanceKey: instance.agentInstanceKey,
86
+ elementId: typeof instance.elementId === 'string' ? instance.elementId : undefined,
87
+ processInstanceKey: instance.processInstanceKey,
88
+ })));
89
+ }
90
+ function getReasoningObjects(item) {
91
+ return (item.content ?? [])
92
+ .filter((content) => content.contentType === 'OBJECT')
93
+ .map((content) => content.object)
94
+ .filter((object) => isPlainObject(object) && object[REASONING_TYPE] === 'reasoning');
95
+ }
96
+ function hasReadableReasoning(item) {
97
+ return getReasoningObjects(item).some((object) => typeof object.text === 'string' && object.text.trim().length > 0);
98
+ }
99
+ function hasEncryptedReasoningWithoutReadableText(item) {
100
+ const reasoningObjects = getReasoningObjects(item);
101
+ return (reasoningObjects.some((object) => {
102
+ const payload = object.payload;
103
+ const hasEncryptedPayload = isPlainObject(payload) &&
104
+ typeof payload.encrypted_content === 'string' &&
105
+ payload.encrypted_content.length > 0;
106
+ const hasReadableText = typeof object.text === 'string' && object.text.trim().length > 0;
107
+ return hasEncryptedPayload && !hasReadableText;
108
+ }) &&
109
+ !reasoningObjects.some((object) => typeof object.text === 'string' && object.text.trim().length > 0));
110
+ }
111
+ function hasFinalAnswer(item) {
112
+ return (item.content ?? []).some((content) => content.contentType === 'TEXT' &&
113
+ typeof content.text === 'string' &&
114
+ content.text.includes(FINAL_ANSWER_MARKER));
115
+ }
116
+ function isCommittedAssistantItem(item) {
117
+ return item.role === 'ASSISTANT' && item.commitStatus === 'COMMITTED';
118
+ }
119
+ async function searchAgentInstances(processInstanceKey, authToken) {
120
+ const request = await (0, apiHelpers_1.getApiRequestContext)();
121
+ const response = await (0, apiHelpers_1.retryOnTransientApiError)(async () => {
122
+ const result = await request.post((0, apiHelpers_1.buildZeebeApiUrl)('/v2/agent-instances/search'), {
123
+ headers: { Authorization: authToken },
124
+ data: { filter: { processInstanceKey } },
125
+ });
126
+ (0, test_1.expect)(result.status(), `Agent instance search failed for process instance ${processInstanceKey}`).toBe(200);
127
+ return result;
128
+ }, `Agent instance search for process instance ${processInstanceKey}`);
129
+ const body = (await response.json());
130
+ if (!Array.isArray(body.items)) {
131
+ throw new Error(`Agent instance search returned no item list for process instance ${processInstanceKey}`);
132
+ }
133
+ return body.items;
134
+ }
135
+ async function searchCommittedHistory(agentInstanceKey, authToken) {
136
+ const request = await (0, apiHelpers_1.getApiRequestContext)();
137
+ const response = await (0, apiHelpers_1.retryOnTransientApiError)(async () => {
138
+ const result = await request.post((0, apiHelpers_1.buildZeebeApiUrl)(`/v2/agent-instances/${encodeURIComponent(agentInstanceKey)}/history/search`), {
139
+ headers: { Authorization: authToken },
140
+ data: {
141
+ filter: { commitStatus: 'COMMITTED' },
142
+ sort: [{ field: 'producedAt', order: 'ASC' }],
143
+ },
144
+ });
145
+ if (result.status() !== 404) {
146
+ (0, test_1.expect)(result.status(), `Committed agent history search failed for agent instance ${agentInstanceKey}`).toBe(200);
147
+ }
148
+ return result;
149
+ }, `Committed agent history search for agent instance ${agentInstanceKey}`);
150
+ if (response.status() === 404) {
151
+ return [];
152
+ }
153
+ const body = (await response.json());
154
+ if (!Array.isArray(body.items)) {
155
+ throw new Error(`Committed agent history search returned no item list for agent instance ${agentInstanceKey}`);
156
+ }
157
+ return body.items;
158
+ }
159
+ async function waitForAgentReasoningHistory(processInstanceKey, authToken, reasoningMode) {
160
+ let agentInstance;
161
+ const findMatchingAgentInstance = (instances) => {
162
+ agentInstance = instances.find((instance) => instance.processInstanceKey === processInstanceKey &&
163
+ instance.elementId === 'AI_Agent' &&
164
+ typeof instance.agentInstanceKey === 'string' &&
165
+ instance.agentInstanceKey.length > 0);
166
+ return agentInstance !== undefined;
167
+ };
168
+ let instances = await searchAgentInstances(processInstanceKey, authToken);
169
+ if (!findMatchingAgentInstance(instances)) {
170
+ try {
171
+ await test_1.expect
172
+ .poll(async () => {
173
+ instances = await searchAgentInstances(processInstanceKey, authToken);
174
+ return findMatchingAgentInstance(instances);
175
+ }, {
176
+ timeout: 120000,
177
+ intervals: [1000, 2000, 5000],
178
+ message: `No AI Agent instance appeared for process instance ${processInstanceKey}`,
179
+ })
180
+ .toBe(true);
181
+ }
182
+ catch (error) {
183
+ const detailsError = new Error(`No AI Agent instance appeared for process instance ${processInstanceKey}. Last search response: ${summarizeAgentInstances(instances)}`);
184
+ Object.assign(detailsError, { cause: error });
185
+ throw detailsError;
186
+ }
187
+ }
188
+ if (agentInstance === undefined ||
189
+ typeof agentInstance.agentInstanceKey !== 'string') {
190
+ throw new Error(`AI Agent instance was not resolved for process instance ${processInstanceKey}`);
191
+ }
192
+ const agentInstanceKey = agentInstance.agentInstanceKey;
193
+ let reasoningItem;
194
+ let answerItem;
195
+ const findCommittedEvidence = (history) => {
196
+ const assistantItems = history.filter(isCommittedAssistantItem);
197
+ reasoningItem = assistantItems.find((item) => reasoningMode === 'readable'
198
+ ? hasReadableReasoning(item)
199
+ : hasEncryptedReasoningWithoutReadableText(item));
200
+ answerItem = assistantItems.find(hasFinalAnswer);
201
+ return (typeof reasoningItem?.historyItemKey === 'string' &&
202
+ typeof answerItem?.historyItemKey === 'string');
203
+ };
204
+ let history = await searchCommittedHistory(agentInstanceKey, authToken);
205
+ if (!findCommittedEvidence(history)) {
206
+ await test_1.expect
207
+ .poll(async () => {
208
+ history = await searchCommittedHistory(agentInstanceKey, authToken);
209
+ return findCommittedEvidence(history);
210
+ }, {
211
+ timeout: 180000,
212
+ intervals: [1000, 2000, 5000],
213
+ message: reasoningMode === 'readable'
214
+ ? `Committed readable reasoning and final-answer history did not appear for agent instance ${agentInstanceKey}`
215
+ : `Committed encrypted-only reasoning and final-answer history did not appear for agent instance ${agentInstanceKey}`,
216
+ })
217
+ .toBe(true);
218
+ }
219
+ if (typeof reasoningItem?.historyItemKey !== 'string' ||
220
+ typeof answerItem?.historyItemKey !== 'string') {
221
+ throw new Error(`Committed reasoning evidence was not resolved for agent instance ${agentInstanceKey}`);
222
+ }
223
+ return {
224
+ agentInstanceKey,
225
+ reasoningHistoryItemKey: reasoningItem.historyItemKey,
226
+ answerHistoryItemKey: answerItem.historyItemKey,
227
+ };
228
+ }
229
+ exports.waitForAgentReasoningHistory = waitForAgentReasoningHistory;
@@ -0,0 +1,2 @@
1
+ export type AgentReasoningProvider = 'anthropic-adaptive' | 'openai-encrypted';
2
+ export declare function buildAgentReasoningBpmn(bpmnPath: string, processId: string, processName: string, provider: AgentReasoningProvider): string;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildAgentReasoningBpmn = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const REASONING_SYSTEM_PROMPT = 'Solve the quantitative problem carefully before answering. Use the supplied ' +
9
+ 'facts and verify the result with an equation. Return a concise explanation ' +
10
+ 'and start the final response with "Answer:".';
11
+ const REASONING_USER_PROMPT = '="A box contains red and blue marbles. There are 20 more red marbles than ' +
12
+ 'blue marbles. After removing 4 red marbles and adding 2 blue marbles, there ' +
13
+ 'are twice as many red marbles as blue marbles. How many of each color were ' +
14
+ 'in the box originally? Include a short equation that verifies your answer."';
15
+ function escapeXml(value) {
16
+ return value
17
+ .replace(/&/g, '&amp;')
18
+ .replace(/</g, '&lt;')
19
+ .replace(/>/g, '&gt;')
20
+ .replace(/"/g, '&quot;')
21
+ .replace(/'/g, '&apos;');
22
+ }
23
+ function replaceXmlAttribute(element, name, value) {
24
+ const attribute = new RegExp(`\\b${name}="[^"]*"`);
25
+ if (!attribute.test(element)) {
26
+ throw new Error(`The BPMN process tag has no ${name} attribute`);
27
+ }
28
+ return element.replace(attribute, `${name}="${escapeXml(value)}"`);
29
+ }
30
+ function replaceProcessIdentity(bpmn, processId, processName) {
31
+ const processTag = bpmn.match(/<bpmn:process\b[^>]*>/)?.[0];
32
+ if (!processTag) {
33
+ throw new Error('The BPMN fixture has no bpmn:process element');
34
+ }
35
+ const originalProcessId = processTag.match(/\bid="([^"]*)"/)?.[1];
36
+ if (!originalProcessId) {
37
+ throw new Error('The BPMN process element has no id');
38
+ }
39
+ const updatedTag = replaceXmlAttribute(replaceXmlAttribute(processTag, 'id', processId), 'name', processName);
40
+ return bpmn
41
+ .replace(processTag, updatedTag)
42
+ .split(`bpmnElement="${originalProcessId}"`)
43
+ .join(`bpmnElement="${processId}"`);
44
+ }
45
+ function getAgentTask(bpmn) {
46
+ const agentTask = bpmn.match(/<bpmn:serviceTask\b[^>]*\bid="AI_Agent"[\s\S]*?<\/bpmn:serviceTask>/)?.[0];
47
+ if (!agentTask) {
48
+ throw new Error('The BPMN fixture has no AI_Agent service task');
49
+ }
50
+ return agentTask;
51
+ }
52
+ function replaceAgentTask(bpmn, agentTask, updatedTask) {
53
+ return bpmn.replace(agentTask, updatedTask);
54
+ }
55
+ function replaceInputSource(bpmn, target, source) {
56
+ const agentTask = getAgentTask(bpmn);
57
+ const input = (agentTask.match(/<zeebe:input\b[\s\S]*?\/>/g) ?? []).find((entry) => entry.includes(`target="${target}"`));
58
+ if (!input) {
59
+ throw new Error(`The BPMN fixture has no input mapping for ${target}`);
60
+ }
61
+ const updatedInput = input.replace(/\bsource="[^"]*"/, `source="${escapeXml(source)}"`);
62
+ return replaceAgentTask(bpmn, agentTask, agentTask.replace(input, updatedInput));
63
+ }
64
+ function appendInputMappings(bpmn, mappings) {
65
+ const closingTag = '</zeebe:ioMapping>';
66
+ const agentTask = getAgentTask(bpmn);
67
+ const index = agentTask.indexOf(closingTag);
68
+ if (index < 0) {
69
+ throw new Error('The AI_Agent task has no zeebe:ioMapping element');
70
+ }
71
+ const newMappings = mappings
72
+ .map(({ source, target }) => ` <zeebe:input source="${escapeXml(source)}" target="${escapeXml(target)}" />`)
73
+ .join('\n');
74
+ const updatedTask = `${agentTask.slice(0, index)}${newMappings}\n${agentTask.slice(index)}`;
75
+ return replaceAgentTask(bpmn, agentTask, updatedTask);
76
+ }
77
+ function markAgentTask(bpmn) {
78
+ const agentTask = getAgentTask(bpmn);
79
+ if (!agentTask.includes('</bpmn:extensionElements>')) {
80
+ throw new Error('The BPMN fixture has no AI_Agent task extensions');
81
+ }
82
+ if (/<zeebe:agentDefinition\b/.test(agentTask)) {
83
+ throw new Error('The BPMN fixture already has an AI_Agent definition');
84
+ }
85
+ return replaceAgentTask(bpmn, agentTask, agentTask.replace('</bpmn:extensionElements>', ' <zeebe:agentDefinition agentType="aiAgentTask" />\n' +
86
+ ' </bpmn:extensionElements>'));
87
+ }
88
+ function buildAgentReasoningBpmn(bpmnPath, processId, processName, provider) {
89
+ let bpmn = replaceProcessIdentity(fs_1.default.readFileSync(bpmnPath, 'utf8'), processId, processName);
90
+ bpmn = markAgentTask(bpmn);
91
+ bpmn = replaceInputSource(bpmn, 'data.systemPrompt.prompt', REASONING_SYSTEM_PROMPT);
92
+ bpmn = replaceInputSource(bpmn, 'data.userPrompt.prompt', REASONING_USER_PROMPT);
93
+ if (provider === 'anthropic-adaptive') {
94
+ bpmn = replaceInputSource(bpmn, 'data.limits.maxModelCalls', '=3');
95
+ bpmn = appendInputMappings(bpmn, [
96
+ {
97
+ source: '=8192',
98
+ target: 'provider.anthropic.model.parameters.maxTokens',
99
+ },
100
+ {
101
+ source: 'adaptive',
102
+ target: 'provider.anthropic.model.parameters.thinking.mode',
103
+ },
104
+ {
105
+ source: 'summarized',
106
+ target: 'provider.anthropic.model.parameters.thinking.display',
107
+ },
108
+ {
109
+ source: 'high',
110
+ target: 'provider.anthropic.model.parameters.effort',
111
+ },
112
+ ]);
113
+ }
114
+ else {
115
+ bpmn = replaceInputSource(bpmn, 'provider.openai.model.model', 'gpt-5-mini-2025-08-07');
116
+ bpmn = replaceInputSource(bpmn, 'data.limits.maxModelCalls', '=3');
117
+ bpmn = appendInputMappings(bpmn, [
118
+ {
119
+ source: 'medium',
120
+ target: 'provider.openai.api.responses.effort',
121
+ },
122
+ {
123
+ source: '=4096',
124
+ target: 'provider.openai.api.responses.maxOutputTokens',
125
+ },
126
+ ]);
127
+ }
128
+ return bpmn;
129
+ }
130
+ exports.buildAgentReasoningBpmn = buildAgentReasoningBpmn;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1455",
3
+ "version": "0.0.1457",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",