@optimizely-opal/opal-tool-ocp-sdk 0.0.0-beta.1 → 0.0.0-beta.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.
Files changed (46) hide show
  1. package/README.md +106 -51
  2. package/dist/auth/TokenVerifier.d.ts +31 -0
  3. package/dist/auth/TokenVerifier.d.ts.map +1 -0
  4. package/dist/auth/TokenVerifier.js +127 -0
  5. package/dist/auth/TokenVerifier.js.map +1 -0
  6. package/dist/auth/TokenVerifier.test.d.ts +2 -0
  7. package/dist/auth/TokenVerifier.test.d.ts.map +1 -0
  8. package/dist/auth/TokenVerifier.test.js +114 -0
  9. package/dist/auth/TokenVerifier.test.js.map +1 -0
  10. package/dist/decorator/Decorator.d.ts +4 -2
  11. package/dist/decorator/Decorator.d.ts.map +1 -1
  12. package/dist/decorator/Decorator.js +26 -4
  13. package/dist/decorator/Decorator.js.map +1 -1
  14. package/dist/decorator/Decorator.test.js +110 -0
  15. package/dist/decorator/Decorator.test.js.map +1 -1
  16. package/dist/function/ToolFunction.d.ts +14 -1
  17. package/dist/function/ToolFunction.d.ts.map +1 -1
  18. package/dist/function/ToolFunction.js +59 -3
  19. package/dist/function/ToolFunction.js.map +1 -1
  20. package/dist/function/ToolFunction.test.js +229 -104
  21. package/dist/function/ToolFunction.test.js.map +1 -1
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/service/Service.d.ts +14 -13
  27. package/dist/service/Service.d.ts.map +1 -1
  28. package/dist/service/Service.js +25 -19
  29. package/dist/service/Service.js.map +1 -1
  30. package/dist/service/Service.test.js +122 -36
  31. package/dist/service/Service.test.js.map +1 -1
  32. package/dist/types/Models.d.ts +5 -5
  33. package/dist/types/Models.d.ts.map +1 -1
  34. package/dist/types/Models.js +9 -9
  35. package/dist/types/Models.js.map +1 -1
  36. package/package.json +10 -3
  37. package/src/auth/TokenVerifier.test.ts +152 -0
  38. package/src/auth/TokenVerifier.ts +145 -0
  39. package/src/decorator/Decorator.test.ts +126 -0
  40. package/src/decorator/Decorator.ts +32 -4
  41. package/src/function/ToolFunction.test.ts +259 -109
  42. package/src/function/ToolFunction.ts +66 -5
  43. package/src/index.ts +1 -0
  44. package/src/service/Service.test.ts +139 -28
  45. package/src/service/Service.ts +31 -24
  46. package/src/types/Models.ts +4 -4
@@ -1,12 +1,16 @@
1
1
  import { toolsService, Tool, Interaction } from './Service';
2
2
  import { Parameter, ParameterType, AuthRequirement, OptiIdAuthDataCredentials, OptiIdAuthData } from '../types/Models';
3
+ import { ToolFunction } from '../function/ToolFunction';
3
4
  import { logger } from '@zaiusinc/app-sdk';
4
5
 
5
- // Mock the logger
6
+ // Mock the logger and other app-sdk exports
6
7
  jest.mock('@zaiusinc/app-sdk', () => ({
7
8
  logger: {
8
9
  error: jest.fn()
9
10
  },
11
+ Function: class {
12
+ public constructor(public request: any) {}
13
+ },
10
14
  Response: jest.fn().mockImplementation((status, data) => ({
11
15
  status,
12
16
  data,
@@ -18,6 +22,7 @@ jest.mock('@zaiusinc/app-sdk', () => ({
18
22
  describe('ToolsService', () => {
19
23
  let mockTool: Tool<unknown>;
20
24
  let mockInteraction: Interaction<unknown>;
25
+ let mockToolFunction: ToolFunction;
21
26
 
22
27
  beforeEach(() => {
23
28
  // Clear registered functions and interactions before each test
@@ -27,6 +32,14 @@ describe('ToolsService', () => {
27
32
  // Reset all mocks
28
33
  jest.clearAllMocks();
29
34
 
35
+ // Create mock ToolFunction
36
+ mockToolFunction = {
37
+ ready: jest.fn().mockResolvedValue(true),
38
+ perform: jest.fn(),
39
+ validateBearerToken: jest.fn().mockReturnValue(true),
40
+ request: {} as any
41
+ } as any;
42
+
30
43
  // Create mock tool handler
31
44
  const mockToolHandler = jest.fn().mockResolvedValue({ result: 'success' });
32
45
 
@@ -90,7 +103,7 @@ describe('ToolsService', () => {
90
103
  );
91
104
 
92
105
  const discoveryRequest = createMockRequest({ path: '/discovery' });
93
- const response = await toolsService.processRequest(discoveryRequest);
106
+ const response = await toolsService.processRequest(discoveryRequest, mockToolFunction);
94
107
 
95
108
  expect(response.status).toBe(200);
96
109
  expect(response).toHaveProperty('bodyJSON');
@@ -109,13 +122,14 @@ describe('ToolsService', () => {
109
122
  description: mockTool.description,
110
123
  parameters: mockTool.parameters.map((p: Parameter) => p.toJSON()),
111
124
  endpoint: mockTool.endpoint,
112
- http_method: 'POST'
125
+ http_method: 'POST',
126
+ auth_requirements: [{ provider: 'OptiID', scope_bundle: 'default', required: true }]
113
127
  });
114
128
  });
115
129
 
116
130
  it('should return empty functions array when no tools are registered', async () => {
117
131
  const discoveryRequest = createMockRequest({ path: '/discovery' });
118
- const response = await toolsService.processRequest(discoveryRequest);
132
+ const response = await toolsService.processRequest(discoveryRequest, mockToolFunction);
119
133
 
120
134
  expect(response.status).toBe(200);
121
135
  expect(response.bodyAsU8Array).toBeDefined();
@@ -149,7 +163,7 @@ describe('ToolsService', () => {
149
163
  );
150
164
 
151
165
  const discoveryRequest = createMockRequest({ path: '/discovery' });
152
- const response = await toolsService.processRequest(discoveryRequest);
166
+ const response = await toolsService.processRequest(discoveryRequest, mockToolFunction);
153
167
 
154
168
  expect(response.status).toBe(200);
155
169
 
@@ -168,7 +182,8 @@ describe('ToolsService', () => {
168
182
  description: mockTool.description,
169
183
  parameters: mockTool.parameters.map((p: Parameter) => p.toJSON()),
170
184
  endpoint: mockTool.endpoint,
171
- http_method: 'POST'
185
+ http_method: 'POST',
186
+ auth_requirements: [{ provider: 'OptiID', scope_bundle: 'default', required: true }]
172
187
  });
173
188
 
174
189
  expect(secondFunction).toEqual({
@@ -177,7 +192,10 @@ describe('ToolsService', () => {
177
192
  parameters: [],
178
193
  endpoint: '/second-tool',
179
194
  http_method: 'POST',
180
- auth_requirements: authRequirements.map((auth) => auth.toJSON())
195
+ auth_requirements: [
196
+ { provider: 'oauth2', scope_bundle: 'calendar', required: true },
197
+ { provider: 'OptiID', scope_bundle: 'default', required: true }
198
+ ]
181
199
  });
182
200
  });
183
201
  });
@@ -195,15 +213,101 @@ describe('ToolsService', () => {
195
213
 
196
214
  it('should execute tool successfully with parameters', async () => {
197
215
  const mockRequest = createMockRequest();
198
- const response = await toolsService.processRequest(mockRequest);
216
+ const response = await toolsService.processRequest(mockRequest, mockToolFunction);
217
+
218
+ expect(response.status).toBe(200);
219
+ expect(mockTool.handler).toHaveBeenCalledWith(
220
+ mockToolFunction, // functionContext
221
+ { param1: 'test-value' },
222
+ undefined
223
+ );
224
+ });
225
+
226
+ it('should execute tool with existing ToolFunction instance context', async () => {
227
+ // Create a mock ToolFunction instance
228
+ const mockToolFunctionInstance = {
229
+ someProperty: 'test-value',
230
+ someMethod: jest.fn(),
231
+ ready: jest.fn().mockResolvedValue(true),
232
+ perform: jest.fn(),
233
+ validateBearerToken: jest.fn().mockReturnValue(true),
234
+ request: {} as any
235
+ } as any;
236
+
237
+ const mockRequest = createMockRequest();
238
+ const response = await toolsService.processRequest(mockRequest, mockToolFunctionInstance);
199
239
 
200
240
  expect(response.status).toBe(200);
201
241
  expect(mockTool.handler).toHaveBeenCalledWith(
242
+ mockToolFunctionInstance, // functionContext - existing instance
202
243
  { param1: 'test-value' },
203
244
  undefined
204
245
  );
205
246
  });
206
247
 
248
+ it('should allow handler in ToolFunction subclass to access request object', async () => {
249
+ // Create a mock class that extends ToolFunction
250
+ class MockToolFunction extends ToolFunction {
251
+ public testMethod() {
252
+ return `path: ${this.request.path}`;
253
+ }
254
+
255
+ public getRequestPath() {
256
+ return this.request.path;
257
+ }
258
+ }
259
+
260
+ // Create an instance with a mock request
261
+ const mockRequest = createMockRequest({ path: '/test-path' });
262
+ const mockToolFunctionInstance = new MockToolFunction(mockRequest);
263
+
264
+ // Create a handler that will use the ToolFunction instance's methods and properties
265
+ const handlerThatAccessesRequest = jest.fn().mockImplementation((
266
+ functionContext: any,
267
+ params: any,
268
+ _authData: any
269
+ ) => {
270
+ // This simulates what would happen in a decorated method of a ToolFunction subclass
271
+ if (functionContext && functionContext instanceof MockToolFunction) {
272
+ return Promise.resolve({
273
+ success: true,
274
+ requestPath: functionContext.getRequestPath(), // Use public method to access request
275
+ testMethodResult: functionContext.testMethod(),
276
+ receivedParams: params
277
+ });
278
+ }
279
+ return Promise.resolve({ success: false, error: 'No valid function context' });
280
+ });
281
+
282
+ // Register a tool with our custom handler
283
+ toolsService.registerTool(
284
+ 'test-toolfunction-access',
285
+ 'Test handler access to ToolFunction instance',
286
+ handlerThatAccessesRequest,
287
+ [],
288
+ '/test-toolfunction-access'
289
+ );
290
+
291
+ const testRequest = createMockRequest({
292
+ path: '/test-toolfunction-access',
293
+ bodyJSON: { action: 'test' }
294
+ });
295
+
296
+ const response = await toolsService.processRequest(testRequest, mockToolFunctionInstance);
297
+
298
+ expect(response.status).toBe(200);
299
+ expect((response as any).data).toBeDefined();
300
+ expect((response as any).data.success).toBe(true);
301
+ expect((response as any).data.requestPath).toBe('/test-path');
302
+ expect((response as any).data.testMethodResult).toBe('path: /test-path');
303
+ expect((response as any).data.receivedParams).toEqual({ action: 'test' });
304
+ expect(handlerThatAccessesRequest).toHaveBeenCalledWith(
305
+ mockToolFunctionInstance, // functionContext is the ToolFunction instance
306
+ { action: 'test' },
307
+ undefined
308
+ );
309
+ });
310
+
207
311
  it('should execute tool with OptiID auth data when provided', async () => {
208
312
  const authData = new OptiIdAuthData(
209
313
  'optiId',
@@ -221,10 +325,11 @@ describe('ToolsService', () => {
221
325
  })
222
326
  });
223
327
 
224
- const response = await toolsService.processRequest(requestWithAuth);
328
+ const response = await toolsService.processRequest(requestWithAuth, mockToolFunction);
225
329
 
226
330
  expect(response.status).toBe(200);
227
331
  expect(mockTool.handler).toHaveBeenCalledWith(
332
+ mockToolFunction, // functionContext
228
333
  { param1: 'test-value' },
229
334
  authData
230
335
  );
@@ -236,10 +341,11 @@ describe('ToolsService', () => {
236
341
  body: JSON.stringify({ param1: 'test-value' })
237
342
  });
238
343
 
239
- const response = await toolsService.processRequest(requestWithoutWrapper);
344
+ const response = await toolsService.processRequest(requestWithoutWrapper, mockToolFunction);
240
345
 
241
346
  expect(response.status).toBe(200);
242
347
  expect(mockTool.handler).toHaveBeenCalledWith(
348
+ mockToolFunction, // functionContext
243
349
  { param1: 'test-value' },
244
350
  undefined
245
351
  );
@@ -250,7 +356,7 @@ describe('ToolsService', () => {
250
356
  jest.mocked(mockTool.handler).mockRejectedValueOnce(new Error(errorMessage));
251
357
 
252
358
  const mockRequest = createMockRequest();
253
- const response = await toolsService.processRequest(mockRequest);
359
+ const response = await toolsService.processRequest(mockRequest, mockToolFunction);
254
360
 
255
361
  expect(response.status).toBe(500);
256
362
  expect(logger.error).toHaveBeenCalledWith(
@@ -263,7 +369,7 @@ describe('ToolsService', () => {
263
369
  jest.mocked(mockTool.handler).mockRejectedValueOnce({});
264
370
 
265
371
  const mockRequest = createMockRequest();
266
- const response = await toolsService.processRequest(mockRequest);
372
+ const response = await toolsService.processRequest(mockRequest, mockToolFunction);
267
373
 
268
374
  expect(response.status).toBe(500);
269
375
  });
@@ -285,10 +391,10 @@ describe('ToolsService', () => {
285
391
  body: JSON.stringify({ data: { param1: 'test-value' } })
286
392
  });
287
393
 
288
- const response = await toolsService.processRequest(interactionRequest);
394
+ const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
289
395
 
290
396
  expect(response.status).toBe(200);
291
- expect(mockInteraction.handler).toHaveBeenCalledWith({ param1: 'test-value' }, undefined);
397
+ expect(mockInteraction.handler).toHaveBeenCalledWith(mockToolFunction, { param1: 'test-value' }, undefined);
292
398
  });
293
399
 
294
400
  it('should handle interaction request body without data wrapper', async () => {
@@ -298,10 +404,10 @@ describe('ToolsService', () => {
298
404
  body: JSON.stringify({ param1: 'test-value' })
299
405
  });
300
406
 
301
- const response = await toolsService.processRequest(interactionRequest);
407
+ const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
302
408
 
303
409
  expect(response.status).toBe(200);
304
- expect(mockInteraction.handler).toHaveBeenCalledWith({ param1: 'test-value' }, undefined);
410
+ expect(mockInteraction.handler).toHaveBeenCalledWith(mockToolFunction, { param1: 'test-value' }, undefined);
305
411
  });
306
412
 
307
413
  it('should execute interaction with OptiID auth data when provided', async () => {
@@ -322,10 +428,11 @@ describe('ToolsService', () => {
322
428
  })
323
429
  });
324
430
 
325
- const response = await toolsService.processRequest(interactionRequest);
431
+ const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
326
432
 
327
433
  expect(response.status).toBe(200);
328
434
  expect(mockInteraction.handler).toHaveBeenCalledWith(
435
+ mockToolFunction, // functionContext
329
436
  { param1: 'test-value' },
330
437
  authData
331
438
  );
@@ -349,10 +456,11 @@ describe('ToolsService', () => {
349
456
  })
350
457
  });
351
458
 
352
- const response = await toolsService.processRequest(interactionRequest);
459
+ const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
353
460
 
354
461
  expect(response.status).toBe(200);
355
462
  expect(mockInteraction.handler).toHaveBeenCalledWith(
463
+ mockToolFunction, // functionContext
356
464
  {
357
465
  param1: 'test-value',
358
466
  auth: authData
@@ -370,7 +478,7 @@ describe('ToolsService', () => {
370
478
  bodyJSON: { data: { param1: 'test-value' } }
371
479
  });
372
480
 
373
- const response = await toolsService.processRequest(interactionRequest);
481
+ const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
374
482
 
375
483
  expect(response.status).toBe(500);
376
484
  expect(logger.error).toHaveBeenCalledWith(
@@ -383,7 +491,7 @@ describe('ToolsService', () => {
383
491
  describe('error cases', () => {
384
492
  it('should return 404 when no matching tool or interaction is found', async () => {
385
493
  const unknownRequest = createMockRequest({ path: '/unknown-endpoint' });
386
- const response = await toolsService.processRequest(unknownRequest);
494
+ const response = await toolsService.processRequest(unknownRequest, mockToolFunction);
387
495
 
388
496
  expect(response.status).toBe(404);
389
497
  });
@@ -406,7 +514,7 @@ describe('ToolsService', () => {
406
514
  path: '/optid-auth-tool'
407
515
  });
408
516
 
409
- const response = await toolsService.processRequest(authRequest);
517
+ const response = await toolsService.processRequest(authRequest, mockToolFunction);
410
518
 
411
519
  expect(response.status).toBe(200);
412
520
  });
@@ -427,10 +535,10 @@ describe('ToolsService', () => {
427
535
  body: null
428
536
  });
429
537
 
430
- const response = await toolsService.processRequest(requestWithNullBody);
538
+ const response = await toolsService.processRequest(requestWithNullBody, mockToolFunction);
431
539
 
432
540
  expect(response.status).toBe(200);
433
- expect(mockTool.handler).toHaveBeenCalledWith(null, undefined);
541
+ expect(mockTool.handler).toHaveBeenCalledWith(mockToolFunction, null, undefined);
434
542
  });
435
543
 
436
544
  it('should handle request with undefined bodyJSON', async () => {
@@ -447,10 +555,10 @@ describe('ToolsService', () => {
447
555
  body: undefined
448
556
  });
449
557
 
450
- const response = await toolsService.processRequest(requestWithUndefinedBody);
558
+ const response = await toolsService.processRequest(requestWithUndefinedBody, mockToolFunction);
451
559
 
452
560
  expect(response.status).toBe(200);
453
- expect(mockTool.handler).toHaveBeenCalledWith(undefined, undefined);
561
+ expect(mockTool.handler).toHaveBeenCalledWith(mockToolFunction, undefined, undefined);
454
562
  });
455
563
 
456
564
  it('should extract auth data from bodyJSON when body exists', async () => {
@@ -478,10 +586,11 @@ describe('ToolsService', () => {
478
586
  })
479
587
  });
480
588
 
481
- const response = await toolsService.processRequest(requestWithAuth);
589
+ const response = await toolsService.processRequest(requestWithAuth, mockToolFunction);
482
590
 
483
591
  expect(response.status).toBe(200);
484
592
  expect(mockTool.handler).toHaveBeenCalledWith(
593
+ mockToolFunction, // functionContext
485
594
  { param1: 'test-value' },
486
595
  authData
487
596
  );
@@ -506,10 +615,11 @@ describe('ToolsService', () => {
506
615
  })
507
616
  });
508
617
 
509
- const response = await toolsService.processRequest(requestWithoutAuth);
618
+ const response = await toolsService.processRequest(requestWithoutAuth, mockToolFunction);
510
619
 
511
620
  expect(response.status).toBe(200);
512
621
  expect(mockTool.handler).toHaveBeenCalledWith(
622
+ mockToolFunction, // functionContext
513
623
  { param1: 'test-value' },
514
624
  undefined
515
625
  );
@@ -537,10 +647,11 @@ describe('ToolsService', () => {
537
647
  body: ''
538
648
  });
539
649
 
540
- const response = await toolsService.processRequest(requestWithAuthButNoBody);
650
+ const response = await toolsService.processRequest(requestWithAuthButNoBody, mockToolFunction);
541
651
 
542
652
  expect(response.status).toBe(200);
543
653
  expect(mockTool.handler).toHaveBeenCalledWith(
654
+ mockToolFunction, // functionContext
544
655
  { param1: 'test-value' },
545
656
  authData
546
657
  );
@@ -2,6 +2,12 @@
2
2
  import { AuthRequirement, Parameter } from '../types/Models';
3
3
  import * as App from '@zaiusinc/app-sdk';
4
4
  import { logger } from '@zaiusinc/app-sdk';
5
+ import { ToolFunction } from '../function/ToolFunction';
6
+
7
+ /**
8
+ * Default OptiID authentication requirement that will be enforced for all tools
9
+ */
10
+ const DEFAULT_OPTIID_AUTH = new AuthRequirement('OptiID', 'default', true);
5
11
 
6
12
 
7
13
 
@@ -22,7 +28,7 @@ export class Interaction<TAuthData> {
22
28
  public constructor(
23
29
  public name: string,
24
30
  public endpoint: string,
25
- public handler: (data: unknown, authData?: TAuthData) => Promise<InteractionResult>
31
+ public handler: (functionContext: ToolFunction, data: unknown, authData?: TAuthData) => Promise<InteractionResult>
26
32
  ) {}
27
33
  }
28
34
 
@@ -42,15 +48,15 @@ export class Tool<TAuthData> {
42
48
  * @param parameters Function parameters
43
49
  * @param endpoint API endpoint
44
50
  * @param handler Function implementing the tool
45
- * @param authRequirements Authentication requirements (optional)
51
+ * @param authRequirements Authentication requirements (mandatory - OptiID enforced)
46
52
  */
47
53
  public constructor(
48
54
  public name: string,
49
55
  public description: string,
50
56
  public parameters: Parameter[],
51
57
  public endpoint: string,
52
- public handler: (params: unknown, authData?: TAuthData) => Promise<unknown>,
53
- public authRequirements?: AuthRequirement[]
58
+ public handler: (functionContext: ToolFunction, params: unknown, authData?: TAuthData) => Promise<unknown>,
59
+ public authRequirements: AuthRequirement[] = [DEFAULT_OPTIID_AUTH]
54
60
  ) {}
55
61
 
56
62
  /**
@@ -62,13 +68,10 @@ export class Tool<TAuthData> {
62
68
  description: this.description,
63
69
  parameters: this.parameters.map((p) => p.toJSON()),
64
70
  endpoint: this.endpoint,
65
- http_method: this.httpMethod
71
+ http_method: this.httpMethod,
72
+ auth_requirements: this.authRequirements.map((auth) => auth.toJSON())
66
73
  };
67
74
 
68
- if (this.authRequirements && this.authRequirements.length > 0) {
69
- result.auth_requirements = this.authRequirements.map((auth) => auth.toJSON());
70
- }
71
-
72
75
  return result;
73
76
  }
74
77
  }
@@ -78,18 +81,19 @@ export class ToolsService {
78
81
  private interactions: Map<string, Interaction<any>> = new Map();
79
82
 
80
83
  /**
81
- * Extract Bearer token from Authorization header
82
- * @param headers Request headers (Map-like object or Headers object with get method)
83
- * @returns Bearer token string or undefined
84
+ * Enforce OptiID authentication for tools by ensuring OptiID auth requirement is present
85
+ * @param authRequirements Original authentication requirements
86
+ * @returns Enforced authentication requirements with OptiID
84
87
  */
85
- public extractBearerToken(headers: App.Headers): string | undefined {
86
- let bearerToken: string | undefined;
88
+ private enforceOptiIdAuth(authRequirements?: AuthRequirement[]): AuthRequirement[] {
89
+ const hasOptiIdProvider = authRequirements
90
+ && authRequirements.some((auth) => auth.provider.toLowerCase() === 'optiid');
87
91
 
88
- const authHeader = headers ? headers.get('authorization') : undefined;
89
- if (authHeader && authHeader.startsWith('Bearer ')) {
90
- bearerToken = authHeader.substring(7).trim();
92
+ if (hasOptiIdProvider) {
93
+ return authRequirements;
91
94
  }
92
- return bearerToken;
95
+
96
+ return [...(authRequirements || []), DEFAULT_OPTIID_AUTH];
93
97
  }
94
98
 
95
99
  /**
@@ -104,12 +108,14 @@ export class ToolsService {
104
108
  public registerTool<TAuthData>(
105
109
  name: string,
106
110
  description: string,
107
- handler: (params: unknown, authData?: TAuthData) => Promise<unknown>,
111
+ handler: (functionContext: ToolFunction, params: unknown, authData?: TAuthData) => Promise<unknown>,
108
112
  parameters: Parameter[],
109
113
  endpoint: string,
110
114
  authRequirements?: AuthRequirement[]
111
115
  ): void {
112
- const func = new Tool<TAuthData>(name, description, parameters, endpoint, handler, authRequirements);
116
+ // Enforce OptiID authentication for all tools
117
+ const enforcedAuthRequirements = this.enforceOptiIdAuth(authRequirements);
118
+ const func = new Tool<TAuthData>(name, description, parameters, endpoint, handler, enforcedAuthRequirements);
113
119
  this.functions.set(endpoint, func);
114
120
  }
115
121
 
@@ -121,14 +127,15 @@ export class ToolsService {
121
127
  */
122
128
  public registerInteraction<TAuthData>(
123
129
  name: string,
124
- handler: (data: unknown, authData?: TAuthData) => Promise<InteractionResult>,
130
+ handler: (functionContext: ToolFunction, data: unknown, authData?: TAuthData) => Promise<InteractionResult>,
125
131
  endpoint: string
126
132
  ): void {
127
133
  const func = new Interaction<TAuthData>(name, endpoint, handler);
128
134
  this.interactions.set(endpoint, func);
129
135
  }
130
136
 
131
- public async processRequest(req: App.Request): Promise<App.Response> {
137
+ public async processRequest(req: App.Request,
138
+ functionContext: ToolFunction): Promise<App.Response> {
132
139
  if (req.path === '/discovery') {
133
140
  return new App.Response(200, { functions: Array.from(this.functions.values()).map((f) => f.toJSON()) });
134
141
  } else {
@@ -145,7 +152,7 @@ export class ToolsService {
145
152
  // Extract auth data from body JSON
146
153
  const authData = req.bodyJSON ? req.bodyJSON.auth : undefined;
147
154
 
148
- const result = await func.handler(params, authData);
155
+ const result = await func.handler(functionContext, params, authData);
149
156
  return new App.Response(200, result);
150
157
  } catch (error: any) {
151
158
  logger.error(`Error in function ${func.name}:`, error);
@@ -166,7 +173,7 @@ export class ToolsService {
166
173
  // Extract auth data from body JSON
167
174
  const authData = req.bodyJSON ? req.bodyJSON.auth : undefined;
168
175
 
169
- const result = await interaction.handler(params, authData);
176
+ const result = await interaction.handler(functionContext, params, authData);
170
177
  return new App.Response(200, result);
171
178
  } catch (error: any) {
172
179
  logger.error(`Error in function ${interaction.name}:`, error);
@@ -48,10 +48,10 @@ export class Parameter {
48
48
  export class OptiIdAuthDataCredentials {
49
49
 
50
50
  public constructor(
51
- public customerId: string,
52
- public instanceId: string,
53
- public accessToken: string,
54
- public productSku: string
51
+ public customer_id: string,
52
+ public instance_id: string,
53
+ public access_token: string,
54
+ public product_sku: string
55
55
  ) {}
56
56
  }
57
57