@optimizely-opal/opal-tool-ocp-sdk 1.0.0-beta.7 → 1.0.0-beta.9
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/dist/auth/AuthUtils.d.ts +7 -0
- package/dist/auth/AuthUtils.d.ts.map +1 -1
- package/dist/auth/AuthUtils.js +27 -0
- package/dist/auth/AuthUtils.js.map +1 -1
- package/dist/auth/AuthUtils.test.js +99 -0
- package/dist/auth/AuthUtils.test.js.map +1 -1
- package/dist/function/GlobalToolFunction.d.ts +1 -1
- package/dist/function/GlobalToolFunction.d.ts.map +1 -1
- package/dist/function/GlobalToolFunction.js +4 -1
- package/dist/function/GlobalToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.d.ts +1 -1
- package/dist/function/ToolFunction.d.ts.map +1 -1
- package/dist/function/ToolFunction.js +12 -2
- package/dist/function/ToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.test.js +206 -0
- package/dist/function/ToolFunction.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/logging/ToolLogger.d.ts.map +1 -1
- package/dist/logging/ToolLogger.js +2 -1
- package/dist/logging/ToolLogger.js.map +1 -1
- package/dist/logging/ToolLogger.test.js +114 -2
- package/dist/logging/ToolLogger.test.js.map +1 -1
- package/dist/service/Service.d.ts +73 -0
- package/dist/service/Service.d.ts.map +1 -1
- package/dist/service/Service.js +188 -42
- package/dist/service/Service.js.map +1 -1
- package/dist/service/Service.test.js +380 -34
- package/dist/service/Service.test.js.map +1 -1
- package/dist/types/Models.d.ts +3 -1
- package/dist/types/Models.d.ts.map +1 -1
- package/dist/types/Models.js +5 -1
- package/dist/types/Models.js.map +1 -1
- package/dist/types/ToolError.d.ts +13 -0
- package/dist/types/ToolError.d.ts.map +1 -1
- package/dist/types/ToolError.js +30 -2
- package/dist/types/ToolError.js.map +1 -1
- package/dist/types/ToolError.test.js +27 -3
- package/dist/types/ToolError.test.js.map +1 -1
- package/dist/validation/ParameterValidator.test.js +2 -1
- package/dist/validation/ParameterValidator.test.js.map +1 -1
- package/package.json +1 -1
- package/src/auth/AuthUtils.test.ts +115 -1
- package/src/auth/AuthUtils.ts +30 -1
- package/src/function/GlobalToolFunction.ts +5 -2
- package/src/function/ToolFunction.test.ts +220 -0
- package/src/function/ToolFunction.ts +13 -4
- package/src/index.ts +1 -1
- package/src/logging/ToolLogger.test.ts +118 -2
- package/src/logging/ToolLogger.ts +2 -1
- package/src/service/Service.test.ts +474 -32
- package/src/service/Service.ts +245 -41
- package/src/types/Models.ts +3 -1
- package/src/types/ToolError.test.ts +33 -3
- package/src/types/ToolError.ts +32 -2
- package/src/validation/ParameterValidator.test.ts +4 -1
|
@@ -436,4 +436,224 @@ describe('ToolFunction', () => {
|
|
|
436
436
|
expect(toolFunction.getRequest()).toBe(mockRequest);
|
|
437
437
|
});
|
|
438
438
|
});
|
|
439
|
+
|
|
440
|
+
describe('internal request authentication', () => {
|
|
441
|
+
beforeEach(() => {
|
|
442
|
+
// Reset mocks before each test
|
|
443
|
+
jest.clearAllMocks();
|
|
444
|
+
setupAuthMocks();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should use internal authentication for /overrides endpoint', async () => {
|
|
448
|
+
const overridesRequest = {
|
|
449
|
+
path: '/overrides',
|
|
450
|
+
method: 'DELETE',
|
|
451
|
+
bodyJSON: {},
|
|
452
|
+
headers: {
|
|
453
|
+
get: jest.fn().mockImplementation((name: string) => {
|
|
454
|
+
if (name === 'Authorization' || name === 'authorization') return 'internal-token';
|
|
455
|
+
if (name === 'x-opal-thread-id') return 'test-thread-id';
|
|
456
|
+
return null;
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
mockProcessRequest.mockResolvedValue(mockResponse);
|
|
462
|
+
const toolFunctionOverrides = new TestToolFunction(overridesRequest);
|
|
463
|
+
const result = await toolFunctionOverrides.perform();
|
|
464
|
+
|
|
465
|
+
expect(result).toBe(mockResponse);
|
|
466
|
+
expect(mockProcessRequest).toHaveBeenCalledWith(overridesRequest, toolFunctionOverrides);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('should throw ToolError when internal authentication fails for /overrides endpoint', async () => {
|
|
470
|
+
const overridesRequest = {
|
|
471
|
+
path: '/overrides',
|
|
472
|
+
method: 'DELETE',
|
|
473
|
+
bodyJSON: {},
|
|
474
|
+
headers: {
|
|
475
|
+
get: jest.fn().mockImplementation((name: string) => {
|
|
476
|
+
if (name === 'x-opal-thread-id') return 'test-thread-id';
|
|
477
|
+
return null; // No Authorization header
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const toolFunctionOverrides = new TestToolFunction(overridesRequest);
|
|
483
|
+
const result = await toolFunctionOverrides.perform();
|
|
484
|
+
|
|
485
|
+
expect(result.status).toBe(401);
|
|
486
|
+
expect(result.bodyJSON).toEqual({
|
|
487
|
+
title: 'Unauthorized',
|
|
488
|
+
status: 401,
|
|
489
|
+
detail: 'Internal request authentication failed',
|
|
490
|
+
instance: '/overrides'
|
|
491
|
+
});
|
|
492
|
+
expect(mockProcessRequest).not.toHaveBeenCalled();
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('should use regular authentication for non-overrides endpoints', async () => {
|
|
496
|
+
const regularRequest = {
|
|
497
|
+
...mockRequest,
|
|
498
|
+
path: '/regular-tool',
|
|
499
|
+
headers: {
|
|
500
|
+
get: jest.fn().mockImplementation((name: string) => {
|
|
501
|
+
if (name === 'x-opal-thread-id') return 'test-thread-id';
|
|
502
|
+
return null;
|
|
503
|
+
})
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
mockProcessRequest.mockResolvedValue(mockResponse);
|
|
508
|
+
const toolFunctionRegular = new TestToolFunction(regularRequest);
|
|
509
|
+
const result = await toolFunctionRegular.perform();
|
|
510
|
+
|
|
511
|
+
expect(result).toBe(mockResponse);
|
|
512
|
+
expect(mockProcessRequest).toHaveBeenCalledWith(regularRequest, toolFunctionRegular);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('should handle internal authentication with valid Authorization header', async () => {
|
|
516
|
+
const overridesRequest = {
|
|
517
|
+
path: '/overrides',
|
|
518
|
+
method: 'PATCH',
|
|
519
|
+
bodyJSON: {
|
|
520
|
+
functions: [
|
|
521
|
+
{
|
|
522
|
+
name: 'test_tool',
|
|
523
|
+
description: 'Updated description'
|
|
524
|
+
}
|
|
525
|
+
]
|
|
526
|
+
},
|
|
527
|
+
headers: {
|
|
528
|
+
get: jest.fn().mockImplementation((name: string) => {
|
|
529
|
+
if (name === 'Authorization') return 'valid-internal-token';
|
|
530
|
+
if (name === 'x-opal-thread-id') return 'test-thread-id';
|
|
531
|
+
return null;
|
|
532
|
+
})
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
mockProcessRequest.mockResolvedValue(mockResponse);
|
|
537
|
+
const toolFunctionOverrides = new TestToolFunction(overridesRequest);
|
|
538
|
+
const result = await toolFunctionOverrides.perform();
|
|
539
|
+
|
|
540
|
+
expect(result).toBe(mockResponse);
|
|
541
|
+
expect(mockProcessRequest).toHaveBeenCalledWith(overridesRequest, toolFunctionOverrides);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('should handle internal authentication with lowercase authorization header', async () => {
|
|
545
|
+
const overridesRequest = {
|
|
546
|
+
path: '/overrides',
|
|
547
|
+
method: 'PATCH',
|
|
548
|
+
bodyJSON: {
|
|
549
|
+
functions: [
|
|
550
|
+
{
|
|
551
|
+
name: 'test_tool',
|
|
552
|
+
description: 'Updated description'
|
|
553
|
+
}
|
|
554
|
+
]
|
|
555
|
+
},
|
|
556
|
+
headers: {
|
|
557
|
+
get: jest.fn().mockImplementation((name: string) => {
|
|
558
|
+
if (name === 'authorization') return 'valid-internal-token';
|
|
559
|
+
if (name === 'x-opal-thread-id') return 'test-thread-id';
|
|
560
|
+
return null;
|
|
561
|
+
})
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
mockProcessRequest.mockResolvedValue(mockResponse);
|
|
566
|
+
const toolFunctionOverrides = new TestToolFunction(overridesRequest);
|
|
567
|
+
const result = await toolFunctionOverrides.perform();
|
|
568
|
+
|
|
569
|
+
expect(result).toBe(mockResponse);
|
|
570
|
+
expect(mockProcessRequest).toHaveBeenCalledWith(overridesRequest, toolFunctionOverrides);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('should fail internal authentication when token verification fails', async () => {
|
|
574
|
+
// Mock token verifier to return false for invalid token
|
|
575
|
+
mockTokenVerifier.verify.mockResolvedValue(false);
|
|
576
|
+
|
|
577
|
+
const overridesRequest = {
|
|
578
|
+
path: '/overrides',
|
|
579
|
+
method: 'DELETE',
|
|
580
|
+
bodyJSON: {},
|
|
581
|
+
headers: {
|
|
582
|
+
get: jest.fn().mockImplementation((name: string) => {
|
|
583
|
+
if (name === 'Authorization') return 'invalid-token';
|
|
584
|
+
if (name === 'x-opal-thread-id') return 'test-thread-id';
|
|
585
|
+
return null;
|
|
586
|
+
})
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
const toolFunctionOverrides = new TestToolFunction(overridesRequest);
|
|
591
|
+
const result = await toolFunctionOverrides.perform();
|
|
592
|
+
|
|
593
|
+
expect(result.status).toBe(401);
|
|
594
|
+
expect(result.bodyJSON).toEqual({
|
|
595
|
+
title: 'Unauthorized',
|
|
596
|
+
status: 401,
|
|
597
|
+
detail: 'Internal request authentication failed',
|
|
598
|
+
instance: '/overrides'
|
|
599
|
+
});
|
|
600
|
+
expect(mockProcessRequest).not.toHaveBeenCalled();
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it('should fail internal authentication when token verifier throws error', async () => {
|
|
604
|
+
// Mock token verifier to throw an error
|
|
605
|
+
mockTokenVerifier.verify.mockRejectedValue(new Error('Token service unavailable'));
|
|
606
|
+
|
|
607
|
+
const overridesRequest = {
|
|
608
|
+
path: '/overrides',
|
|
609
|
+
method: 'DELETE',
|
|
610
|
+
bodyJSON: {},
|
|
611
|
+
headers: {
|
|
612
|
+
get: jest.fn().mockImplementation((name: string) => {
|
|
613
|
+
if (name === 'Authorization') return 'some-token';
|
|
614
|
+
if (name === 'x-opal-thread-id') return 'test-thread-id';
|
|
615
|
+
return null;
|
|
616
|
+
})
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const toolFunctionOverrides = new TestToolFunction(overridesRequest);
|
|
621
|
+
const result = await toolFunctionOverrides.perform();
|
|
622
|
+
|
|
623
|
+
expect(result.status).toBe(401);
|
|
624
|
+
expect(result.bodyJSON).toEqual({
|
|
625
|
+
title: 'Unauthorized',
|
|
626
|
+
status: 401,
|
|
627
|
+
detail: 'Internal request authentication failed',
|
|
628
|
+
instance: '/overrides'
|
|
629
|
+
});
|
|
630
|
+
expect(mockProcessRequest).not.toHaveBeenCalled();
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('should fail internal authentication when headers object is missing', async () => {
|
|
634
|
+
const overridesRequest = {
|
|
635
|
+
path: '/overrides',
|
|
636
|
+
method: 'DELETE',
|
|
637
|
+
bodyJSON: {},
|
|
638
|
+
headers: {
|
|
639
|
+
get: jest.fn().mockImplementation((name: string) => {
|
|
640
|
+
if (name === 'x-opal-thread-id') return 'test-thread-id';
|
|
641
|
+
return null;
|
|
642
|
+
})
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
const toolFunctionOverrides = new TestToolFunction(overridesRequest);
|
|
647
|
+
const result = await toolFunctionOverrides.perform();
|
|
648
|
+
|
|
649
|
+
expect(result.status).toBe(401);
|
|
650
|
+
expect(result.bodyJSON).toEqual({
|
|
651
|
+
title: 'Unauthorized',
|
|
652
|
+
status: 401,
|
|
653
|
+
detail: 'Internal request authentication failed',
|
|
654
|
+
instance: '/overrides'
|
|
655
|
+
});
|
|
656
|
+
expect(mockProcessRequest).not.toHaveBeenCalled();
|
|
657
|
+
});
|
|
658
|
+
});
|
|
439
659
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Function, Response, Headers, amendLogContext } from '@zaiusinc/app-sdk';
|
|
2
|
-
import { authenticateRegularRequest } from '../auth/AuthUtils';
|
|
2
|
+
import { authenticateRegularRequest, authenticateInternalRequest } from '../auth/AuthUtils';
|
|
3
3
|
import { toolsService } from '../service/Service';
|
|
4
4
|
import { ToolLogger } from '../logging/ToolLogger';
|
|
5
5
|
import { ToolError } from '../types/ToolError';
|
|
@@ -17,7 +17,7 @@ export abstract class ToolFunction extends Function {
|
|
|
17
17
|
* @async
|
|
18
18
|
* @returns ReadyResponse containing ready status and optional reason when not ready
|
|
19
19
|
*/
|
|
20
|
-
protected ready(): Promise<ReadyResponse> {
|
|
20
|
+
protected ready(): Promise<ReadyResponse | boolean> {
|
|
21
21
|
return Promise.resolve({ ready: true });
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -68,7 +68,10 @@ export abstract class ToolFunction extends Function {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
if (this.request.path === '/ready') {
|
|
71
|
-
const
|
|
71
|
+
const readyResult = await this.ready();
|
|
72
|
+
const readyResponse = typeof readyResult === 'boolean'
|
|
73
|
+
? { ready: readyResult }
|
|
74
|
+
: readyResult;
|
|
72
75
|
return new Response(200, readyResponse);
|
|
73
76
|
}
|
|
74
77
|
|
|
@@ -82,6 +85,12 @@ export abstract class ToolFunction extends Function {
|
|
|
82
85
|
* @throws {ToolError} If authentication fails
|
|
83
86
|
*/
|
|
84
87
|
private async authorizeRequest(): Promise<void> {
|
|
85
|
-
|
|
88
|
+
// Use internal authentication for overrides endpoint (header-based token)
|
|
89
|
+
if (this.request.path === '/overrides') {
|
|
90
|
+
await authenticateInternalRequest(this.request);
|
|
91
|
+
} else {
|
|
92
|
+
// Use regular authentication for other endpoints (body-based token with org validation)
|
|
93
|
+
await authenticateRegularRequest(this.request);
|
|
94
|
+
}
|
|
86
95
|
}
|
|
87
96
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,4 +4,4 @@ export * from './types/Models';
|
|
|
4
4
|
export * from './types/ToolError';
|
|
5
5
|
export * from './decorator/Decorator';
|
|
6
6
|
export * from './auth/TokenVerifier';
|
|
7
|
-
export { Tool, Interaction, InteractionResult } from './service/Service';
|
|
7
|
+
export { Tool, Interaction, InteractionResult, NestedInteractions } from './service/Service';
|
|
@@ -32,6 +32,7 @@ describe('ToolLogger', () => {
|
|
|
32
32
|
const createMockRequest = (overrides: any = {}): App.Request => {
|
|
33
33
|
const defaultRequest = {
|
|
34
34
|
path: '/test-tool',
|
|
35
|
+
method: 'POST',
|
|
35
36
|
bodyJSON: {
|
|
36
37
|
parameters: {
|
|
37
38
|
name: 'test',
|
|
@@ -96,6 +97,7 @@ describe('ToolLogger', () => {
|
|
|
96
97
|
const expectedLog = {
|
|
97
98
|
event: 'opal_tool_request',
|
|
98
99
|
path: '/test-tool',
|
|
100
|
+
method: 'POST',
|
|
99
101
|
parameters: {
|
|
100
102
|
name: 'test',
|
|
101
103
|
value: 'data'
|
|
@@ -118,6 +120,7 @@ describe('ToolLogger', () => {
|
|
|
118
120
|
expectJsonLog({
|
|
119
121
|
event: 'opal_tool_request',
|
|
120
122
|
path: '/test-tool',
|
|
123
|
+
method: 'POST',
|
|
121
124
|
parameters: null
|
|
122
125
|
});
|
|
123
126
|
});
|
|
@@ -135,6 +138,7 @@ describe('ToolLogger', () => {
|
|
|
135
138
|
expectJsonLog({
|
|
136
139
|
event: 'opal_tool_request',
|
|
137
140
|
path: '/test-tool',
|
|
141
|
+
method: 'POST',
|
|
138
142
|
parameters: {
|
|
139
143
|
name: 'direct',
|
|
140
144
|
action: 'test'
|
|
@@ -167,6 +171,7 @@ describe('ToolLogger', () => {
|
|
|
167
171
|
expectJsonLog({
|
|
168
172
|
event: 'opal_tool_request',
|
|
169
173
|
path: '/test-tool',
|
|
174
|
+
method: 'POST',
|
|
170
175
|
parameters: {
|
|
171
176
|
username: 'john',
|
|
172
177
|
password: '[REDACTED]',
|
|
@@ -204,6 +209,7 @@ describe('ToolLogger', () => {
|
|
|
204
209
|
expectJsonLog({
|
|
205
210
|
event: 'opal_tool_request',
|
|
206
211
|
path: '/test-tool',
|
|
212
|
+
method: 'POST',
|
|
207
213
|
parameters: {
|
|
208
214
|
PASSWORD: '[REDACTED]',
|
|
209
215
|
API_KEY: '[REDACTED]',
|
|
@@ -232,6 +238,7 @@ describe('ToolLogger', () => {
|
|
|
232
238
|
expectJsonLog({
|
|
233
239
|
event: 'opal_tool_request',
|
|
234
240
|
path: '/test-tool',
|
|
241
|
+
method: 'POST',
|
|
235
242
|
parameters: {
|
|
236
243
|
description: `${'a'.repeat(100)}... (truncated, 150 chars total)`,
|
|
237
244
|
short_field: 'normal'
|
|
@@ -255,6 +262,7 @@ describe('ToolLogger', () => {
|
|
|
255
262
|
expectJsonLog({
|
|
256
263
|
event: 'opal_tool_request',
|
|
257
264
|
path: '/test-tool',
|
|
265
|
+
method: 'POST',
|
|
258
266
|
parameters: {
|
|
259
267
|
items: [
|
|
260
268
|
...largeArray.slice(0, 10),
|
|
@@ -291,6 +299,7 @@ describe('ToolLogger', () => {
|
|
|
291
299
|
expectJsonLog({
|
|
292
300
|
event: 'opal_tool_request',
|
|
293
301
|
path: '/test-tool',
|
|
302
|
+
method: 'POST',
|
|
294
303
|
parameters: {
|
|
295
304
|
user: {
|
|
296
305
|
name: 'John',
|
|
@@ -328,6 +337,7 @@ describe('ToolLogger', () => {
|
|
|
328
337
|
expectJsonLog({
|
|
329
338
|
event: 'opal_tool_request',
|
|
330
339
|
path: '/test-tool',
|
|
340
|
+
method: 'POST',
|
|
331
341
|
parameters: {
|
|
332
342
|
nullValue: null,
|
|
333
343
|
emptyString: '',
|
|
@@ -353,6 +363,7 @@ describe('ToolLogger', () => {
|
|
|
353
363
|
expectJsonLog({
|
|
354
364
|
event: 'opal_tool_request',
|
|
355
365
|
path: '/test-tool',
|
|
366
|
+
method: 'POST',
|
|
356
367
|
parameters: {
|
|
357
368
|
credentials: '[REDACTED]',
|
|
358
369
|
public_list: ['item1', 'item2']
|
|
@@ -381,6 +392,7 @@ describe('ToolLogger', () => {
|
|
|
381
392
|
expectJsonLog({
|
|
382
393
|
event: 'opal_tool_request',
|
|
383
394
|
path: '/test-tool',
|
|
395
|
+
method: 'POST',
|
|
384
396
|
parameters: {
|
|
385
397
|
auth: '[REDACTED]',
|
|
386
398
|
public_config: {
|
|
@@ -496,7 +508,9 @@ describe('ToolLogger', () => {
|
|
|
496
508
|
// First item: deeply nested object with inner parts replaced by placeholder
|
|
497
509
|
expect(items[0]).toEqual({
|
|
498
510
|
level2: {
|
|
499
|
-
level3:
|
|
511
|
+
level3: {
|
|
512
|
+
level4: '[MAX_DEPTH_EXCEEDED]'
|
|
513
|
+
}
|
|
500
514
|
}
|
|
501
515
|
});
|
|
502
516
|
|
|
@@ -509,7 +523,9 @@ describe('ToolLogger', () => {
|
|
|
509
523
|
// Fourth item: another deeply nested object with inner parts replaced by placeholder
|
|
510
524
|
expect(items[3]).toEqual({
|
|
511
525
|
level2: {
|
|
512
|
-
level3:
|
|
526
|
+
level3: {
|
|
527
|
+
level4: '[MAX_DEPTH_EXCEEDED]'
|
|
528
|
+
}
|
|
513
529
|
}
|
|
514
530
|
});
|
|
515
531
|
});
|
|
@@ -694,6 +710,7 @@ describe('ToolLogger', () => {
|
|
|
694
710
|
expectJsonLog({
|
|
695
711
|
event: 'opal_tool_request',
|
|
696
712
|
path: '/test-tool',
|
|
713
|
+
method: 'POST',
|
|
697
714
|
parameters: {}
|
|
698
715
|
});
|
|
699
716
|
});
|
|
@@ -712,6 +729,7 @@ describe('ToolLogger', () => {
|
|
|
712
729
|
expectJsonLog({
|
|
713
730
|
event: 'opal_tool_request',
|
|
714
731
|
path: '/test-tool',
|
|
732
|
+
method: 'POST',
|
|
715
733
|
parameters: {
|
|
716
734
|
field: 'value'
|
|
717
735
|
}
|
|
@@ -738,6 +756,7 @@ describe('ToolLogger', () => {
|
|
|
738
756
|
expectJsonLog({
|
|
739
757
|
event: 'opal_tool_request',
|
|
740
758
|
path: '/test-tool',
|
|
759
|
+
method: 'POST',
|
|
741
760
|
parameters: {
|
|
742
761
|
string: 'text',
|
|
743
762
|
number: 42,
|
|
@@ -749,5 +768,102 @@ describe('ToolLogger', () => {
|
|
|
749
768
|
}
|
|
750
769
|
});
|
|
751
770
|
});
|
|
771
|
+
|
|
772
|
+
it('should handle tool override request with enhanced parameter descriptions', () => {
|
|
773
|
+
const overrideRequest = {
|
|
774
|
+
tools: [
|
|
775
|
+
{
|
|
776
|
+
name: 'calculate_experiment_runtime',
|
|
777
|
+
description: 'OVERRIDDEN: Enhanced experiment runtime calculator with advanced features',
|
|
778
|
+
parameters: [
|
|
779
|
+
{
|
|
780
|
+
name: 'BCR',
|
|
781
|
+
type: 'number',
|
|
782
|
+
description: 'OVERRIDDEN: Enhanced baseline conversion rate with validation',
|
|
783
|
+
required: true
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
name: 'MDE',
|
|
787
|
+
type: 'number',
|
|
788
|
+
description: 'OVERRIDDEN: Enhanced minimum detectable effect calculation',
|
|
789
|
+
required: true
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
name: 'sigLevel',
|
|
793
|
+
type: 'number',
|
|
794
|
+
description: 'OVERRIDDEN: Enhanced statistical significance level',
|
|
795
|
+
required: true
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
name: 'numVariations',
|
|
799
|
+
type: 'number',
|
|
800
|
+
description: 'OVERRIDDEN: Enhanced number of variations handling',
|
|
801
|
+
required: true
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
name: 'dailyVisitors',
|
|
805
|
+
type: 'number',
|
|
806
|
+
description: 'OVERRIDDEN: Enhanced daily visitor count with forecasting',
|
|
807
|
+
required: true
|
|
808
|
+
}
|
|
809
|
+
]
|
|
810
|
+
}
|
|
811
|
+
// Note: NOT including calculate_sample_size in override
|
|
812
|
+
]
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
const req = createMockRequest({
|
|
816
|
+
path: '/overrides',
|
|
817
|
+
bodyJSON: overrideRequest
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
ToolLogger.logRequest(req);
|
|
821
|
+
|
|
822
|
+
expectJsonLog({
|
|
823
|
+
event: 'opal_tool_request',
|
|
824
|
+
path: '/overrides',
|
|
825
|
+
method: 'POST',
|
|
826
|
+
parameters: {
|
|
827
|
+
tools: [
|
|
828
|
+
{
|
|
829
|
+
name: 'calculate_experiment_runtime',
|
|
830
|
+
description: 'OVERRIDDEN: Enhanced experiment runtime calculator with advanced features',
|
|
831
|
+
parameters: [
|
|
832
|
+
{
|
|
833
|
+
name: 'BCR',
|
|
834
|
+
type: 'number',
|
|
835
|
+
description: 'OVERRIDDEN: Enhanced baseline conversion rate with validation',
|
|
836
|
+
required: true
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
name: 'MDE',
|
|
840
|
+
type: 'number',
|
|
841
|
+
description: 'OVERRIDDEN: Enhanced minimum detectable effect calculation',
|
|
842
|
+
required: true
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
name: 'sigLevel',
|
|
846
|
+
type: 'number',
|
|
847
|
+
description: 'OVERRIDDEN: Enhanced statistical significance level',
|
|
848
|
+
required: true
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
name: 'numVariations',
|
|
852
|
+
type: 'number',
|
|
853
|
+
description: 'OVERRIDDEN: Enhanced number of variations handling',
|
|
854
|
+
required: true
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
name: 'dailyVisitors',
|
|
858
|
+
type: 'number',
|
|
859
|
+
description: 'OVERRIDDEN: Enhanced daily visitor count with forecasting',
|
|
860
|
+
required: true
|
|
861
|
+
}
|
|
862
|
+
]
|
|
863
|
+
}
|
|
864
|
+
]
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
});
|
|
752
868
|
});
|
|
753
869
|
});
|
|
@@ -75,7 +75,7 @@ export class ToolLogger {
|
|
|
75
75
|
|
|
76
76
|
if (Array.isArray(data)) {
|
|
77
77
|
const truncated = data.slice(0, this.MAX_ARRAY_ITEMS);
|
|
78
|
-
const result = truncated.map((item) => this.redactSensitiveData(item, maxDepth
|
|
78
|
+
const result = truncated.map((item) => this.redactSensitiveData(item, maxDepth));
|
|
79
79
|
if (data.length > this.MAX_ARRAY_ITEMS) {
|
|
80
80
|
result.push(`... (${data.length - this.MAX_ARRAY_ITEMS} more items truncated)`);
|
|
81
81
|
}
|
|
@@ -146,6 +146,7 @@ export class ToolLogger {
|
|
|
146
146
|
const requestLog = {
|
|
147
147
|
event: 'opal_tool_request',
|
|
148
148
|
path: req.path,
|
|
149
|
+
method: req.method,
|
|
149
150
|
parameters: this.createParameterSummary(params)
|
|
150
151
|
};
|
|
151
152
|
|