@optimizely-opal/opal-tool-ocp-sdk 0.0.0-OCP-1487.9 → 0.0.0-OCP-1487.10

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.
@@ -9,9 +9,6 @@ jest.mock('../service/Service', () => ({
9
9
  registerInteraction: jest.fn()
10
10
  }
11
11
  }));
12
-
13
- // Mock base class for testing auto-detection
14
- class GlobalToolFunction {}
15
12
  describe('Decorators', () => {
16
13
  beforeEach(() => {
17
14
  jest.clearAllMocks();
@@ -39,8 +36,7 @@ describe('Decorators', () => {
39
36
  expect.any(Function),
40
37
  [],
41
38
  '/test-tool',
42
- [],
43
- false
39
+ []
44
40
  );
45
41
 
46
42
  // Ensure TestClass is considered "used" by TypeScript
@@ -82,8 +78,7 @@ describe('Decorators', () => {
82
78
  })
83
79
  ]),
84
80
  '/tool-with-params',
85
- [],
86
- false
81
+ []
87
82
  );
88
83
 
89
84
  // Ensure TestClass is considered "used" by TypeScript
@@ -124,8 +119,7 @@ describe('Decorators', () => {
124
119
  scopeBundle: 'calendar',
125
120
  required: true
126
121
  })
127
- ]),
128
- false
122
+ ])
129
123
  );
130
124
 
131
125
  // Ensure TestClass is considered "used" by TypeScript
@@ -206,8 +200,7 @@ describe('Decorators', () => {
206
200
  scopeBundle: 'drive',
207
201
  required: false
208
202
  })
209
- ]),
210
- false
203
+ ])
211
204
  );
212
205
 
213
206
  // Ensure TestClass is considered "used" by TypeScript
@@ -235,8 +228,7 @@ describe('Decorators', () => {
235
228
  expect.any(Function),
236
229
  [],
237
230
  '/empty-params',
238
- [],
239
- false
231
+ []
240
232
  );
241
233
 
242
234
  expect(TestClass).toBeDefined();
@@ -264,8 +256,7 @@ describe('Decorators', () => {
264
256
  expect.any(Function),
265
257
  [],
266
258
  '/no-auth',
267
- [],
268
- false
259
+ []
269
260
  );
270
261
 
271
262
  expect(TestClass).toBeDefined();
@@ -332,8 +323,7 @@ describe('Decorators', () => {
332
323
  expect.objectContaining({ name: 'dictParam', type: ParameterType.Dictionary })
333
324
  ]),
334
325
  '/multi-type',
335
- [],
336
- false
326
+ []
337
327
  );
338
328
  });
339
329
  });
@@ -452,8 +442,7 @@ describe('Decorators', () => {
452
442
  })
453
443
  ]),
454
444
  '/mixed-tool',
455
- [],
456
- false
445
+ []
457
446
  );
458
447
 
459
448
  expect(toolsService.registerInteraction).toHaveBeenCalledWith(
@@ -500,8 +489,7 @@ describe('Decorators', () => {
500
489
  scopeBundle: 'calendar',
501
490
  required: true // Should default to true
502
491
  })
503
- ]),
504
- false
492
+ ])
505
493
  );
506
494
  });
507
495
 
@@ -527,8 +515,7 @@ describe('Decorators', () => {
527
515
  expect.any(Function),
528
516
  [], // Should be empty array when parameters is undefined
529
517
  '/undefined-arrays',
530
- [], // Should be empty array when authRequirements is undefined
531
- false
518
+ [] // Should be empty array when authRequirements is undefined
532
519
  );
533
520
  });
534
521
  });
@@ -658,287 +645,5 @@ describe('Decorators', () => {
658
645
  expect(TestClass).toBeDefined();
659
646
  });
660
647
  });
661
-
662
- describe('global tool registration', () => {
663
- it('should auto-detect global tools when class extends GlobalToolFunction', () => {
664
- const config: ToolConfig = {
665
- name: 'autoGlobalTool',
666
- description: 'An auto-detected global tool',
667
- parameters: [],
668
- endpoint: '/auto-global-tool'
669
- };
670
-
671
- class TestGlobalClass extends GlobalToolFunction {
672
- @tool(config)
673
- public async testTool(): Promise<{ result: string }> {
674
- return { result: 'global-tool-result' };
675
- }
676
- }
677
-
678
- expect(toolsService.registerTool).toHaveBeenCalledWith(
679
- 'autoGlobalTool',
680
- 'An auto-detected global tool',
681
- expect.any(Function),
682
- [],
683
- '/auto-global-tool',
684
- [],
685
- true
686
- );
687
-
688
- expect(TestGlobalClass).toBeDefined();
689
- });
690
-
691
- it('should register regular tools when class does not extend GlobalToolFunction', () => {
692
- const config: ToolConfig = {
693
- name: 'regularTool',
694
- description: 'A regular tool',
695
- parameters: [],
696
- endpoint: '/regular-tool'
697
- };
698
-
699
- class TestRegularClass {
700
- @tool(config)
701
- public async testTool(): Promise<{ result: string }> {
702
- return { result: 'regular-tool-result' };
703
- }
704
- }
705
-
706
- expect(toolsService.registerTool).toHaveBeenCalledWith(
707
- 'regularTool',
708
- 'A regular tool',
709
- expect.any(Function),
710
- [],
711
- '/regular-tool',
712
- [],
713
- false
714
- );
715
-
716
- expect(TestRegularClass).toBeDefined();
717
- });
718
-
719
- it('should register tools as global when import context is set', () => {
720
- // Set global import context
721
- globalThis.__IMPORT_CONTEXT = 'global';
722
-
723
- const config: ToolConfig = {
724
- name: 'importedGlobalTool',
725
- description: 'A tool imported as global',
726
- parameters: [],
727
- endpoint: '/imported-global-tool'
728
- };
729
-
730
- class TestImportedClass {
731
- @tool(config)
732
- public async testTool(): Promise<{ result: string }> {
733
- return { result: 'imported-global-tool-result' };
734
- }
735
- }
736
-
737
- expect(toolsService.registerTool).toHaveBeenCalledWith(
738
- 'importedGlobalTool',
739
- 'A tool imported as global',
740
- expect.any(Function),
741
- [],
742
- '/imported-global-tool',
743
- [],
744
- true
745
- );
746
-
747
- // Clean up
748
- globalThis.__IMPORT_CONTEXT = undefined;
749
- expect(TestImportedClass).toBeDefined();
750
- });
751
-
752
- it('should register tools as regular when import context is set to regular', () => {
753
- // Set regular import context
754
- globalThis.__IMPORT_CONTEXT = 'regular';
755
-
756
- const config: ToolConfig = {
757
- name: 'importedRegularTool',
758
- description: 'A tool imported as regular',
759
- parameters: [],
760
- endpoint: '/imported-regular-tool'
761
- };
762
-
763
- class TestImportedClass {
764
- @tool(config)
765
- public async testTool(): Promise<{ result: string }> {
766
- return { result: 'imported-regular-tool-result' };
767
- }
768
- }
769
-
770
- expect(toolsService.registerTool).toHaveBeenCalledWith(
771
- 'importedRegularTool',
772
- 'A tool imported as regular',
773
- expect.any(Function),
774
- [],
775
- '/imported-regular-tool',
776
- [],
777
- false
778
- );
779
-
780
- // Clean up
781
- globalThis.__IMPORT_CONTEXT = undefined;
782
- expect(TestImportedClass).toBeDefined();
783
- });
784
-
785
- it('should handle mixed imports - both regular and global tools together', () => {
786
- // Test importing regular tools first
787
- globalThis.__IMPORT_CONTEXT = 'regular';
788
-
789
- const regularConfig: ToolConfig = {
790
- name: 'mixedRegularTool',
791
- description: 'A regular tool in mixed scenario',
792
- parameters: [],
793
- endpoint: '/mixed-regular-tool'
794
- };
795
-
796
- class RegularToolClass {
797
- @tool(regularConfig)
798
- public async regularTool(): Promise<{ result: string }> {
799
- return { result: 'mixed-regular-result' };
800
- }
801
- }
802
-
803
- // Switch to global context
804
- globalThis.__IMPORT_CONTEXT = 'global';
805
-
806
- const globalConfig: ToolConfig = {
807
- name: 'mixedGlobalTool',
808
- description: 'A global tool in mixed scenario',
809
- parameters: [],
810
- endpoint: '/mixed-global-tool'
811
- };
812
-
813
- class GlobalToolClass {
814
- @tool(globalConfig)
815
- public async globalTool(): Promise<{ result: string }> {
816
- return { result: 'mixed-global-result' };
817
- }
818
- }
819
-
820
- // Clear context
821
- globalThis.__IMPORT_CONTEXT = undefined;
822
-
823
- // Verify regular tool was registered as regular
824
- expect(toolsService.registerTool).toHaveBeenCalledWith(
825
- 'mixedRegularTool',
826
- 'A regular tool in mixed scenario',
827
- expect.any(Function),
828
- [],
829
- '/mixed-regular-tool',
830
- [],
831
- false
832
- );
833
-
834
- // Verify global tool was registered as global
835
- expect(toolsService.registerTool).toHaveBeenCalledWith(
836
- 'mixedGlobalTool',
837
- 'A global tool in mixed scenario',
838
- expect.any(Function),
839
- [],
840
- '/mixed-global-tool',
841
- [],
842
- true
843
- );
844
-
845
- expect(RegularToolClass).toBeDefined();
846
- expect(GlobalToolClass).toBeDefined();
847
- });
848
-
849
- it('should handle context switching correctly within same test', () => {
850
- let callCount = 0;
851
- const originalRegisterTool = jest.mocked(toolsService.registerTool);
852
-
853
- // Track call order and contexts
854
- originalRegisterTool.mockImplementation(() => {
855
- callCount++;
856
- // Store the isGlobal flag from each call for verification
857
- });
858
-
859
- // First: Import as regular
860
- globalThis.__IMPORT_CONTEXT = 'regular';
861
-
862
- class FirstClass {
863
- @tool({
864
- name: 'contextTest1',
865
- description: 'First context test',
866
- parameters: [],
867
- endpoint: '/context-test-1'
868
- })
869
- public async tool1() {
870
- return { result: 'test1' };
871
- }
872
- }
873
-
874
- // Second: Switch to global
875
- globalThis.__IMPORT_CONTEXT = 'global';
876
-
877
- class SecondClass {
878
- @tool({
879
- name: 'contextTest2',
880
- description: 'Second context test',
881
- parameters: [],
882
- endpoint: '/context-test-2'
883
- })
884
- public async tool2() {
885
- return { result: 'test2' };
886
- }
887
- }
888
-
889
- // Third: Clear context (should fallback to class inheritance)
890
- globalThis.__IMPORT_CONTEXT = undefined;
891
-
892
- class ThirdClass {
893
- @tool({
894
- name: 'contextTest3',
895
- description: 'Third context test',
896
- parameters: [],
897
- endpoint: '/context-test-3'
898
- })
899
- public async tool3() {
900
- return { result: 'test3' };
901
- }
902
- }
903
-
904
- // Verify the sequence of calls
905
- expect(toolsService.registerTool).toHaveBeenNthCalledWith(
906
- callCount - 2, // First call
907
- 'contextTest1',
908
- 'First context test',
909
- expect.any(Function),
910
- [],
911
- '/context-test-1',
912
- [],
913
- false // Should be regular
914
- );
915
-
916
- expect(toolsService.registerTool).toHaveBeenNthCalledWith(
917
- callCount - 1, // Second call
918
- 'contextTest2',
919
- 'Second context test',
920
- expect.any(Function),
921
- [],
922
- '/context-test-2',
923
- [],
924
- true // Should be global
925
- );
926
-
927
- expect(toolsService.registerTool).toHaveBeenNthCalledWith(
928
- callCount, // Third call
929
- 'contextTest3',
930
- 'Third context test',
931
- expect.any(Function),
932
- [],
933
- '/context-test-3',
934
- [],
935
- false // Should be regular (fallback to class check, ThirdClass doesn't extend GlobalToolFunction)
936
- );
937
-
938
- expect(FirstClass).toBeDefined();
939
- expect(SecondClass).toBeDefined();
940
- expect(ThirdClass).toBeDefined();
941
- });
942
- });
943
648
  });
944
649
 
@@ -1,44 +1,6 @@
1
1
  import { Parameter, AuthRequirement, ParameterType } from '../types/Models';
2
2
  import { toolsService } from '../service/Service';
3
3
 
4
- /**
5
- * Global context flag to track import context
6
- */
7
- declare global {
8
- var __IMPORT_CONTEXT: 'global' | 'regular' | undefined;
9
- }
10
-
11
- /**
12
- * Helper function to check if a class extends GlobalToolFunction
13
- */
14
- function isGlobalToolFunction(constructor: any): boolean {
15
- // Walk up the prototype chain to check for GlobalToolFunction
16
- let currentConstructor = constructor;
17
- while (currentConstructor) {
18
- if (currentConstructor.name === 'GlobalToolFunction') {
19
- return true;
20
- }
21
- currentConstructor = Object.getPrototypeOf(currentConstructor);
22
- }
23
- return false;
24
- }
25
-
26
- /**
27
- * Determine if tool should be registered as global based on definition context or import context
28
- */
29
- function shouldRegisterAsGlobal(targetConstructor: any): boolean {
30
- // First check if there's an active import context
31
- if (globalThis.__IMPORT_CONTEXT === 'global') {
32
- return true;
33
- }
34
- if (globalThis.__IMPORT_CONTEXT === 'regular') {
35
- return false;
36
- }
37
-
38
- // Fallback to checking where the tool is defined
39
- return isGlobalToolFunction(targetConstructor);
40
- }
41
-
42
4
  /**
43
5
  * Configuration for @tool decorator
44
6
  */
@@ -107,18 +69,14 @@ export function tool(config: ToolConfig) {
107
69
  return originalMethod.call(instance, params, authData);
108
70
  };
109
71
 
110
- // Determine if this tool should be registered as global based on definition or import context
111
- const isGlobal = shouldRegisterAsGlobal(target.constructor);
112
-
113
- // Register tool with global flag based on where it's defined or imported
72
+ // Immediately register with global ToolsService
114
73
  toolsService.registerTool(
115
74
  config.name,
116
75
  config.description,
117
76
  boundHandler,
118
77
  parameters,
119
78
  config.endpoint,
120
- authRequirements,
121
- isGlobal
79
+ authRequirements
122
80
  );
123
81
  };
124
82
  }
package/src/index.ts CHANGED
@@ -3,5 +3,4 @@ export * from './function/GlobalToolFunction';
3
3
  export * from './types/Models';
4
4
  export * from './decorator/Decorator';
5
5
  export * from './auth/TokenVerifier';
6
- export * from './utils/ImportUtils';
7
6
  export { Tool, Interaction, InteractionResult } from './service/Service';
@@ -1,7 +1,6 @@
1
1
  import { toolsService, Tool, Interaction } from './Service';
2
2
  import { Parameter, ParameterType, AuthRequirement, OptiIdAuthDataCredentials, OptiIdAuthData } from '../types/Models';
3
3
  import { ToolFunction } from '../function/ToolFunction';
4
- import { GlobalToolFunction } from '../function/GlobalToolFunction';
5
4
  import { logger } from '@zaiusinc/app-sdk';
6
5
 
7
6
  // Mock the logger and other app-sdk exports
@@ -12,9 +11,6 @@ jest.mock('@zaiusinc/app-sdk', () => ({
12
11
  Function: class {
13
12
  public constructor(public request: any) {}
14
13
  },
15
- GlobalFunction: class {
16
- public constructor(public request: any) {}
17
- },
18
14
  Response: jest.fn().mockImplementation((status, data) => ({
19
15
  status,
20
16
  data,
@@ -27,7 +23,6 @@ describe('ToolsService', () => {
27
23
  let mockTool: Tool<unknown>;
28
24
  let mockInteraction: Interaction<unknown>;
29
25
  let mockToolFunction: ToolFunction;
30
- let mockGlobalToolFunction: GlobalToolFunction;
31
26
 
32
27
  beforeEach(() => {
33
28
  // Clear registered functions and interactions before each test
@@ -45,18 +40,6 @@ describe('ToolsService', () => {
45
40
  request: {} as any
46
41
  } as any;
47
42
 
48
- // Create mock GlobalToolFunction - use actual class for instanceof check
49
- class MockGlobalToolFunction extends GlobalToolFunction {
50
- public ready = jest.fn().mockResolvedValue(true);
51
- public perform = jest.fn();
52
- public validateBearerToken = jest.fn().mockReturnValue(true);
53
-
54
- public constructor() {
55
- super({} as any);
56
- }
57
- }
58
- mockGlobalToolFunction = new MockGlobalToolFunction();
59
-
60
43
  // Create mock tool handler
61
44
  const mockToolHandler = jest.fn().mockResolvedValue({ result: 'success' });
62
45
 
@@ -111,7 +94,6 @@ describe('ToolsService', () => {
111
94
  describe('processRequest', () => {
112
95
  describe('discovery endpoint', () => {
113
96
  it('should return registered functions for discovery endpoint', async () => {
114
- // Register a regular (non-global) tool
115
97
  toolsService.registerTool(
116
98
  mockTool.name,
117
99
  mockTool.description,
@@ -120,17 +102,6 @@ describe('ToolsService', () => {
120
102
  mockTool.endpoint
121
103
  );
122
104
 
123
- // Register a global tool (should not be included for regular ToolFunction)
124
- toolsService.registerTool(
125
- 'globalTool',
126
- 'Global tool description',
127
- jest.fn().mockResolvedValue({ result: 'global' }),
128
- [new Parameter('param1', ParameterType.String, 'Test parameter', true)],
129
- '/global-tool',
130
- undefined,
131
- true // global tool
132
- );
133
-
134
105
  const discoveryRequest = createMockRequest({ path: '/discovery' });
135
106
  const response = await toolsService.processRequest(discoveryRequest, mockToolFunction);
136
107
 
@@ -142,11 +113,9 @@ describe('ToolsService', () => {
142
113
  const responseData = response.bodyJSON as { functions: unknown[] };
143
114
  expect(responseData).toHaveProperty('functions');
144
115
  expect(Array.isArray(responseData.functions)).toBe(true);
145
-
146
- // Should only return the non-global tool (global tool should be filtered out)
147
116
  expect(responseData.functions).toHaveLength(1);
148
117
 
149
- // Test the registered function structure - should be the non-global tool
118
+ // Test the registered function structure
150
119
  const registeredFunction = responseData.functions[0];
151
120
  expect(registeredFunction).toEqual({
152
121
  name: mockTool.name,
@@ -156,11 +125,6 @@ describe('ToolsService', () => {
156
125
  http_method: 'POST',
157
126
  auth_requirements: [{ provider: 'OptiID', scope_bundle: 'default', required: true }]
158
127
  });
159
-
160
- // Verify the global tool is not included
161
- const toolNames = responseData.functions.map((f: any) => f.name);
162
- expect(toolNames).toContain(mockTool.name);
163
- expect(toolNames).not.toContain('globalTool');
164
128
  });
165
129
 
166
130
  it('should return empty functions array when no tools are registered', async () => {
@@ -236,139 +200,6 @@ describe('ToolsService', () => {
236
200
  });
237
201
  });
238
202
 
239
- describe('global discovery endpoint', () => {
240
- it('should return only global tools when called from GlobalToolFunction', async () => {
241
- // Register a regular tool (non-global)
242
- toolsService.registerTool(
243
- 'regularTool',
244
- 'Regular tool description',
245
- jest.fn().mockResolvedValue({ result: 'regular' }),
246
- [new Parameter('param1', ParameterType.String, 'Test parameter', true)],
247
- '/regular-tool',
248
- undefined,
249
- false // not global
250
- );
251
-
252
- // Register a global tool
253
- toolsService.registerTool(
254
- 'globalTool',
255
- 'Global tool description',
256
- jest.fn().mockResolvedValue({ result: 'global' }),
257
- [new Parameter('param1', ParameterType.String, 'Test parameter', true)],
258
- '/global-tool',
259
- undefined,
260
- true // global
261
- );
262
-
263
- const discoveryRequest = createMockRequest({ path: '/discovery' });
264
- const response = await toolsService.processRequest(discoveryRequest, mockGlobalToolFunction);
265
-
266
- expect(response.status).toBe(200);
267
- expect(response).toHaveProperty('bodyJSON');
268
-
269
- // Test the actual response structure
270
- const responseData = response.bodyJSON as { functions: any[] };
271
- expect(responseData).toHaveProperty('functions');
272
- expect(Array.isArray(responseData.functions)).toBe(true);
273
-
274
- // Should only return the global tool
275
- expect(responseData.functions).toHaveLength(1);
276
- expect(responseData.functions[0].name).toBe('globalTool');
277
- expect(responseData.functions[0].endpoint).toBe('/global-tool');
278
- });
279
-
280
- it('should return empty array when no global tools are registered ' +
281
- 'and called from GlobalToolFunction', async () => {
282
- // Register only regular tools (non-global)
283
- toolsService.registerTool(
284
- 'regularTool1',
285
- 'Regular tool 1 description',
286
- jest.fn().mockResolvedValue({ result: 'regular1' }),
287
- [new Parameter('param1', ParameterType.String, 'Test parameter', true)],
288
- '/regular-tool-1',
289
- undefined,
290
- false // not global
291
- );
292
-
293
- toolsService.registerTool(
294
- 'regularTool2',
295
- 'Regular tool 2 description',
296
- jest.fn().mockResolvedValue({ result: 'regular2' }),
297
- [new Parameter('param1', ParameterType.String, 'Test parameter', true)],
298
- '/regular-tool-2',
299
- undefined,
300
- false // not global
301
- );
302
-
303
- const discoveryRequest = createMockRequest({ path: '/discovery' });
304
- const response = await toolsService.processRequest(discoveryRequest, mockGlobalToolFunction);
305
-
306
- expect(response.status).toBe(200);
307
- expect(response).toHaveProperty('bodyJSON');
308
-
309
- // Test the actual response structure
310
- const responseData = response.bodyJSON as { functions: any[] };
311
- expect(responseData).toHaveProperty('functions');
312
- expect(Array.isArray(responseData.functions)).toBe(true);
313
-
314
- // Should return empty array since no global tools are registered
315
- expect(responseData.functions).toHaveLength(0);
316
- });
317
-
318
- it('should return multiple global tools when called from GlobalToolFunction', async () => {
319
- // Register multiple global tools
320
- toolsService.registerTool(
321
- 'globalTool1',
322
- 'Global tool 1 description',
323
- jest.fn().mockResolvedValue({ result: 'global1' }),
324
- [new Parameter('param1', ParameterType.String, 'Test parameter', true)],
325
- '/global-tool-1',
326
- undefined,
327
- true // global
328
- );
329
-
330
- toolsService.registerTool(
331
- 'globalTool2',
332
- 'Global tool 2 description',
333
- jest.fn().mockResolvedValue({ result: 'global2' }),
334
- [new Parameter('param1', ParameterType.String, 'Test parameter', true)],
335
- '/global-tool-2',
336
- undefined,
337
- true // global
338
- );
339
-
340
- // Register a regular tool (should not be included)
341
- toolsService.registerTool(
342
- 'regularTool',
343
- 'Regular tool description',
344
- jest.fn().mockResolvedValue({ result: 'regular' }),
345
- [new Parameter('param1', ParameterType.String, 'Test parameter', true)],
346
- '/regular-tool',
347
- undefined,
348
- false // not global
349
- );
350
-
351
- const discoveryRequest = createMockRequest({ path: '/discovery' });
352
- const response = await toolsService.processRequest(discoveryRequest, mockGlobalToolFunction);
353
-
354
- expect(response.status).toBe(200);
355
- expect(response).toHaveProperty('bodyJSON');
356
-
357
- // Test the actual response structure
358
- const responseData = response.bodyJSON as { functions: any[] };
359
- expect(responseData).toHaveProperty('functions');
360
- expect(Array.isArray(responseData.functions)).toBe(true);
361
-
362
- // Should return only the 2 global tools
363
- expect(responseData.functions).toHaveLength(2);
364
-
365
- const toolNames = responseData.functions.map((f) => f.name);
366
- expect(toolNames).toContain('globalTool1');
367
- expect(toolNames).toContain('globalTool2');
368
- expect(toolNames).not.toContain('regularTool');
369
- });
370
- });
371
-
372
203
  describe('tool execution', () => {
373
204
  beforeEach(() => {
374
205
  toolsService.registerTool(