@optimizely-opal/opal-tool-ocp-sdk 1.0.0-beta.8 โ 1.0.0
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/README.md +60 -197
- 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 +11 -3
- package/dist/logging/ToolLogger.d.ts.map +1 -1
- package/dist/logging/ToolLogger.js +114 -13
- package/dist/logging/ToolLogger.js.map +1 -1
- package/dist/logging/ToolLogger.test.js +177 -71
- package/dist/logging/ToolLogger.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/package.json +3 -3
- package/src/index.ts +1 -1
- package/src/logging/ToolLogger.test.ts +225 -74
- package/src/logging/ToolLogger.ts +129 -15
- package/src/types/Models.ts +3 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ A TypeScript SDK for building Opal tools in Optimizely Connect Platform. This SD
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- ๐ฏ **Decorator-based Tool Registration** - Use `@tool`
|
|
9
|
+
- ๐ฏ **Decorator-based Tool Registration** - Use `@tool` decorator to easily register functions
|
|
10
10
|
- ๐ **Global and Regular Function Modes** - SDK can be used in either global or organization-scoped mode
|
|
11
11
|
- ๐ง **Type-safe Development** - Full TypeScript support with comprehensive type definitions
|
|
12
12
|
- ๐๏ธ **Abstract Base Classes** - Extend `ToolFunction` or `GlobalToolFunction` for standardized request processing
|
|
@@ -16,35 +16,51 @@ A TypeScript SDK for building Opal tools in Optimizely Connect Platform. This SD
|
|
|
16
16
|
- โ
**Automatic Validation** - SDK automatically validates parameters and returns RFC 9457 compliant error responses
|
|
17
17
|
- ๐งช **Comprehensive Testing** - Fully tested with Jest
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
The SDK extends the functionality of OCP apps. You need to have an OCP app to use this SDK.
|
|
22
|
+
Learn how to get started with OCP app development [here](https://docs.developers.optimizely.com/optimizely-connect-platform/docs/developer-platform-overview-ocp2).
|
|
20
23
|
|
|
24
|
+
Start by adding the SDK to your existing OCP app. In your OCP app folder, execute:
|
|
21
25
|
```bash
|
|
22
|
-
|
|
26
|
+
yarn add @optimizely-opal/opal-tool-ocp-sdk
|
|
23
27
|
```
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
To add an Opal too registry to your OCP app,
|
|
30
|
+
add a [function](https://docs.developers.optimizely.com/optimizely-connect-platform/docs/functions-ocp2) to your app manifest and mark it as an Opal tool function:
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
**`app.yml`**
|
|
33
|
+
```yaml
|
|
34
|
+
functions:
|
|
35
|
+
opal_tool: # A unique key for your function.
|
|
36
|
+
entry_point: OpalToolFunction # The name of the class implementing your tool.
|
|
37
|
+
description: Opal tool function # A brief description of this function.
|
|
38
|
+
opal_tool: true
|
|
29
39
|
```
|
|
30
40
|
|
|
31
|
-
|
|
41
|
+
Next, create and implement function class - both file name and class must match the value of `entry_point` property from app manifest.
|
|
42
|
+
Function class must extend either `ToolFunction` or `GlobalToolFunction` class from `@optimizely-opal/opal-tool-ocp-sdk` (See 'Function modes' section below).
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
**`src/functions/OpalToolFunction`**
|
|
45
|
+
```typescript
|
|
46
|
+
import { ToolFunction } from '@optimizely-opal/opal-tool-ocp-sdk';
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
- **Global Functions** (`GlobalToolFunction`) - Platform-wide functions that work across all organizations
|
|
48
|
+
export class OpalToolFunction extends ToolFunction {
|
|
37
49
|
|
|
38
|
-
|
|
50
|
+
}
|
|
51
|
+
```
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
Next, implement tools methods and annotate them with `@tool` decorator. Each such method is a tool in your registry.
|
|
54
|
+
You can define mulitple tool methods in your app.
|
|
55
|
+
Tool methods can be defined either as tool class instance methods or in separate classes.
|
|
56
|
+
If tools are defined in separate classes, these classes need to be imported into the function class.
|
|
41
57
|
|
|
58
|
+
**`src/functions/OpalToolFunction`**
|
|
42
59
|
```typescript
|
|
43
|
-
import { ToolFunction
|
|
60
|
+
import { ToolFunction } from '@optimizely-opal/opal-tool-ocp-sdk';
|
|
44
61
|
|
|
45
|
-
export class
|
|
62
|
+
export class OpalToolFunction extends ToolFunction {
|
|
46
63
|
|
|
47
|
-
// Register a simple tool without authentication
|
|
48
64
|
@tool({
|
|
49
65
|
name: 'create_task',
|
|
50
66
|
description: 'Creates a new task in the system',
|
|
@@ -71,118 +87,42 @@ export class MyToolFunction extends ToolFunction {
|
|
|
71
87
|
priority: params.priority || 'medium'
|
|
72
88
|
};
|
|
73
89
|
}
|
|
74
|
-
|
|
75
|
-
// Register a tool with OptiID authentication
|
|
76
|
-
@tool({
|
|
77
|
-
name: 'secure_task',
|
|
78
|
-
description: 'Creates a secure task with OptiID authentication',
|
|
79
|
-
endpoint: '/secure-task',
|
|
80
|
-
parameters: [
|
|
81
|
-
{
|
|
82
|
-
name: 'title',
|
|
83
|
-
type: ParameterType.String,
|
|
84
|
-
description: 'The task title',
|
|
85
|
-
required: true
|
|
86
|
-
}
|
|
87
|
-
],
|
|
88
|
-
authRequirements: [
|
|
89
|
-
{
|
|
90
|
-
provider: 'OptiID',
|
|
91
|
-
scopeBundle: 'tasks',
|
|
92
|
-
required: true
|
|
93
|
-
}
|
|
94
|
-
]
|
|
95
|
-
})
|
|
96
|
-
async createSecureTask(params: { title: string }, authData?: OptiIdAuthData) {
|
|
97
|
-
if (!authData) {
|
|
98
|
-
throw new Error('OptiID authentication required');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const { customer_id, instance_id, access_token } = authData.credentials;
|
|
102
|
-
return {
|
|
103
|
-
id: '456',
|
|
104
|
-
title: params.title,
|
|
105
|
-
customer_id,
|
|
106
|
-
instance_id
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Register an interaction
|
|
111
|
-
@interaction({
|
|
112
|
-
name: 'task_webhook',
|
|
113
|
-
endpoint: '/webhook/task'
|
|
114
|
-
})
|
|
115
|
-
async handleTaskWebhook(data: any): Promise<InteractionResult> {
|
|
116
|
-
return new InteractionResult(
|
|
117
|
-
`Task ${data.taskId} was updated`,
|
|
118
|
-
`https://app.example.com/tasks/${data.taskId}`
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
90
|
}
|
|
122
91
|
```
|
|
123
92
|
|
|
124
|
-
|
|
93
|
+
The format of `params` attribute matches the parameters defined in `@tool` decorator.
|
|
125
94
|
|
|
126
|
-
|
|
95
|
+
When the function is called by Opal, the SDK automatically:
|
|
127
96
|
|
|
128
|
-
|
|
129
|
-
|
|
97
|
+
- **Routes requests** to your registered tools based on endpoints
|
|
98
|
+
- **Handles authentication** and OptiID token validation before calling your methods
|
|
99
|
+
- **Provides discovery** at `/discovery` endpoint for OCP platform integration
|
|
100
|
+
- **Returns proper HTTP responses** with correct status codes and JSON formatting
|
|
130
101
|
|
|
131
|
-
|
|
102
|
+
Optionally, implement `ready` method to define when tool registry can be registered and called by Opal.
|
|
103
|
+
OCP will call this method to show tool readiness in the UI. Only functions marked as `ready` can be registered and called by Opal.
|
|
104
|
+
The value of `reason` property from the response will be displayed in OCP UI to inform users why the tool is not ready to be registered.
|
|
105
|
+
```typescript
|
|
106
|
+
protected override async ready(): Promise<ReadyResponse> {
|
|
107
|
+
// validation logic
|
|
108
|
+
if (!isValid) {
|
|
109
|
+
return { ready: false, reason: 'Configure the app first.' };
|
|
110
|
+
}
|
|
132
111
|
|
|
133
|
-
|
|
134
|
-
name: 'global_utility',
|
|
135
|
-
description: 'A utility tool available to all organizations',
|
|
136
|
-
endpoint: '/global-utility',
|
|
137
|
-
parameters: [
|
|
138
|
-
{
|
|
139
|
-
name: 'operation',
|
|
140
|
-
type: ParameterType.String,
|
|
141
|
-
description: 'The operation to perform',
|
|
142
|
-
required: true
|
|
143
|
-
}
|
|
144
|
-
]
|
|
145
|
-
})
|
|
146
|
-
async globalUtility(params: { operation: string }, authData?: OptiIdAuthData) {
|
|
147
|
-
return {
|
|
148
|
-
result: `Performed ${params.operation} globally`,
|
|
149
|
-
organizationId: authData?.credentials.customer_id || 'unknown'
|
|
150
|
-
};
|
|
112
|
+
return { ready: true };
|
|
151
113
|
}
|
|
152
|
-
}
|
|
153
114
|
```
|
|
154
115
|
|
|
155
|
-
### Function Modes
|
|
156
|
-
|
|
157
|
-
The SDK operates in one of two modes based on the base class you extend:
|
|
158
|
-
|
|
159
|
-
- **Regular Function Mode** (`ToolFunction`): All tools are organization-scoped and validate organization IDs
|
|
160
|
-
- **Global Function Mode** (`GlobalToolFunction`): All tools are platform-wide.
|
|
161
|
-
|
|
162
|
-
The discovery endpoint returns all tools registered within that function mode.
|
|
163
|
-
|
|
164
|
-
Your function class inherits a `perform()` method from `ToolFunction` or `GlobalToolFunction` that serves as the main entry point for handling all incoming requests. When called, the SDK automatically:
|
|
165
|
-
|
|
166
|
-
- **Routes requests** to your registered tools and interactions based on endpoints
|
|
167
|
-
- **Handles authentication** and OptiID token validation before calling your methods
|
|
168
|
-
- **Provides discovery** at `/discovery` endpoint for OCP platform integration
|
|
169
|
-
- **Returns proper HTTP responses** with correct status codes and JSON formatting
|
|
170
|
-
|
|
171
116
|
## Core Concepts
|
|
172
117
|
|
|
173
118
|
### Tools
|
|
174
119
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
- Have a name, description, and endpoint
|
|
178
|
-
- Define parameters with types and validation
|
|
179
|
-
- Can require authentication
|
|
180
|
-
- Return structured responses
|
|
181
|
-
- Are automatically registered based on the function mode you choose
|
|
120
|
+
Each OCP app with an Opal too function is a tool registry in Opal. Tool registry constist of one or more tools.
|
|
121
|
+
Each tool have name, description and a list of input parameters.
|
|
182
122
|
|
|
183
123
|
### Function Modes
|
|
184
124
|
|
|
185
|
-
#### Regular Functions (`ToolFunction`)
|
|
125
|
+
#### Regular Functions (function extends `ToolFunction` class)
|
|
186
126
|
|
|
187
127
|
Regular functions are scoped to specific organizations and validate that requests come from the same organization:
|
|
188
128
|
|
|
@@ -191,7 +131,7 @@ Regular functions are scoped to specific organizations and validate that request
|
|
|
191
131
|
- **Per-Organization Configuration**: Can implement organization-specific configuration, authentication credentials, and API keys since they're tied to a single organization
|
|
192
132
|
- **Per-Organization Authentication**: Can store and use organization-specific authentication tokens, connection strings, and other sensitive data securely
|
|
193
133
|
|
|
194
|
-
#### Global Functions (`GlobalToolFunction`)
|
|
134
|
+
#### Global Functions (function extends `GlobalToolFunction` class)
|
|
195
135
|
|
|
196
136
|
Global functions work across all organizations without organization validation:
|
|
197
137
|
|
|
@@ -201,14 +141,6 @@ Global functions work across all organizations without organization validation:
|
|
|
201
141
|
- **No Per-Organization Authentication**: Cannot store organization-specific credentials or authentication data
|
|
202
142
|
- **Global Discovery**: Have a global discovery URL that can be used by any organization without requiring them to install the app first
|
|
203
143
|
|
|
204
|
-
### Interactions
|
|
205
|
-
|
|
206
|
-
Interactions are event handlers that process incoming data (like webhooks):
|
|
207
|
-
|
|
208
|
-
- Have a name and endpoint
|
|
209
|
-
- Process unstructured data
|
|
210
|
-
- Return interaction results with messages and optional links
|
|
211
|
-
|
|
212
144
|
### Parameters
|
|
213
145
|
|
|
214
146
|
Supported parameter types:
|
|
@@ -226,7 +158,9 @@ enum ParameterType {
|
|
|
226
158
|
|
|
227
159
|
### Parameter Validation
|
|
228
160
|
|
|
229
|
-
The SDK automatically validates all incoming parameters against the parameter definitions you specify for your tools.
|
|
161
|
+
The SDK automatically validates all incoming parameters against the parameter definitions you specify for your tools.
|
|
162
|
+
When Opal sends requests to your tools, the SDK performs validation before calling your handler methods and automatically returns an error message that Opal understands.
|
|
163
|
+
This allow Opal to auto-correct.
|
|
230
164
|
|
|
231
165
|
#### Automatic Validation Features
|
|
232
166
|
|
|
@@ -235,36 +169,6 @@ The SDK automatically validates all incoming parameters against the parameter de
|
|
|
235
169
|
- **Early Error Response**: Returns validation errors immediately without calling your handler if validation fails
|
|
236
170
|
- **RFC 9457 Compliance**: Error responses follow the RFC 9457 Problem Details for HTTP APIs specification
|
|
237
171
|
|
|
238
|
-
#### Validation Error Response Format
|
|
239
|
-
|
|
240
|
-
When parameter validation fails, the SDK returns a standardized error response with HTTP status 400 and `Content-Type: application/problem+json`:
|
|
241
|
-
|
|
242
|
-
```json
|
|
243
|
-
{
|
|
244
|
-
"title": "One or more validation errors occurred.",
|
|
245
|
-
"status": 400,
|
|
246
|
-
"detail": "See 'errors' field for details.",
|
|
247
|
-
"instance": "/your-tool-endpoint",
|
|
248
|
-
"errors": [
|
|
249
|
-
{
|
|
250
|
-
"field": "title",
|
|
251
|
-
"message": "Parameter 'title' must be a string, but received number"
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
"field": "priority",
|
|
255
|
-
"message": "Required parameter 'priority' is missing"
|
|
256
|
-
}
|
|
257
|
-
]
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
**Benefits:**
|
|
262
|
-
|
|
263
|
-
- **Reduced Boilerplate**: No need to write parameter validation code in your handlers
|
|
264
|
-
- **Consistent Error Format**: All validation errors follow the same RFC 9457 standard format
|
|
265
|
-
- **Better Developer Experience**: Clear, actionable error messages for API consumers
|
|
266
|
-
- **Type Safety**: Validation ensures your handlers receive correctly typed parameters
|
|
267
|
-
|
|
268
172
|
### Authentication
|
|
269
173
|
|
|
270
174
|
The SDK supports authentication and authorization mechanisms:
|
|
@@ -434,16 +338,16 @@ This will return:
|
|
|
434
338
|
|
|
435
339
|
### Handler Function Signatures
|
|
436
340
|
|
|
437
|
-
All tool
|
|
341
|
+
All tool handler methods follow this signature pattern:
|
|
438
342
|
|
|
439
343
|
```typescript
|
|
440
344
|
async handlerMethod(
|
|
441
|
-
params: TParams, // Tool parameters
|
|
345
|
+
params: TParams, // Tool parameters
|
|
442
346
|
authData?: OptiIdAuthData // OptiID user authentication data (if authenticated)
|
|
443
347
|
): Promise<TResult>
|
|
444
348
|
```
|
|
445
349
|
|
|
446
|
-
- **params**: The input parameters for tools
|
|
350
|
+
- **params**: The input parameters for tools
|
|
447
351
|
- **authData**: Available when OptiID user authentication is configured and successful
|
|
448
352
|
|
|
449
353
|
|
|
@@ -463,17 +367,6 @@ interface ToolConfig {
|
|
|
463
367
|
}
|
|
464
368
|
```
|
|
465
369
|
|
|
466
|
-
#### `@interaction(config: InteractionConfig)`
|
|
467
|
-
|
|
468
|
-
Registers a method as an interaction handler.
|
|
469
|
-
|
|
470
|
-
```typescript
|
|
471
|
-
interface InteractionConfig {
|
|
472
|
-
name: string;
|
|
473
|
-
endpoint: string;
|
|
474
|
-
}
|
|
475
|
-
```
|
|
476
|
-
|
|
477
370
|
### Base Classes
|
|
478
371
|
|
|
479
372
|
#### `ToolFunction`
|
|
@@ -507,10 +400,8 @@ Extend this class for tools that work across organizations. The `perform` method
|
|
|
507
400
|
Key model classes with generic type support:
|
|
508
401
|
|
|
509
402
|
- `Tool<TAuthData>` - Represents a registered tool with typed auth data
|
|
510
|
-
- `Interaction<TAuthData>` - Represents an interaction handler with typed auth data
|
|
511
403
|
- `Parameter` - Defines tool parameters
|
|
512
404
|
- `AuthRequirement` - Defines authentication needs
|
|
513
|
-
- `InteractionResult` - Response from interactions
|
|
514
405
|
- `OptiIdAuthData` - OptiID specific authentication data
|
|
515
406
|
- `ReadyResponse` - Response type for the ready method containing status and optional reason
|
|
516
407
|
- `ToolError` - Custom error class for RFC 9457 Problem Details error responses with configurable HTTP status codes
|
|
@@ -639,7 +530,7 @@ yarn lint
|
|
|
639
530
|
### Function with Authentication
|
|
640
531
|
|
|
641
532
|
```typescript
|
|
642
|
-
import { ToolFunction, tool,
|
|
533
|
+
import { ToolFunction, tool, ParameterType, OptiIdAuthData } from '@optimizely-opal/opal-ocp-sdk';
|
|
643
534
|
|
|
644
535
|
export class AuthenticatedFunction extends ToolFunction {
|
|
645
536
|
|
|
@@ -659,24 +550,6 @@ export class AuthenticatedFunction extends ToolFunction {
|
|
|
659
550
|
return { success: true, customer_id };
|
|
660
551
|
}
|
|
661
552
|
|
|
662
|
-
// Interaction with authentication example
|
|
663
|
-
@interaction({
|
|
664
|
-
name: 'authenticated_webhook',
|
|
665
|
-
endpoint: '/secure-webhook'
|
|
666
|
-
})
|
|
667
|
-
async handleSecureWebhook(data: any, authData?: OptiIdAuthData): Promise<InteractionResult> {
|
|
668
|
-
if (!authData) {
|
|
669
|
-
return new InteractionResult('Authentication required for webhook processing');
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
const { customer_id } = authData.credentials;
|
|
673
|
-
|
|
674
|
-
// Process webhook data with authentication context
|
|
675
|
-
return new InteractionResult(
|
|
676
|
-
`Webhook processed for customer ${customer_id}: ${data.eventType}`,
|
|
677
|
-
`https://app.example.com/events/${data.eventId}`
|
|
678
|
-
);
|
|
679
|
-
}
|
|
680
553
|
}
|
|
681
554
|
```
|
|
682
555
|
|
|
@@ -748,7 +621,7 @@ export class TaskTool {
|
|
|
748
621
|
|
|
749
622
|
**tools/NotificationTool.ts:**
|
|
750
623
|
```typescript
|
|
751
|
-
import { tool,
|
|
624
|
+
import { tool, ParameterType, OptiIdAuthData } from '@optimizely-opal/opal-ocp-sdk';
|
|
752
625
|
|
|
753
626
|
export class NotificationTool {
|
|
754
627
|
@tool({
|
|
@@ -779,16 +652,6 @@ export class NotificationTool {
|
|
|
779
652
|
};
|
|
780
653
|
}
|
|
781
654
|
|
|
782
|
-
@interaction({
|
|
783
|
-
name: 'notification_webhook',
|
|
784
|
-
endpoint: '/webhook/notification'
|
|
785
|
-
})
|
|
786
|
-
async handleNotificationWebhook(data: any): Promise<InteractionResult> {
|
|
787
|
-
return new InteractionResult(
|
|
788
|
-
`Notification ${data.notificationId} was delivered`,
|
|
789
|
-
`https://app.example.com/notifications/${data.notificationId}`
|
|
790
|
-
);
|
|
791
|
-
}
|
|
792
655
|
}
|
|
793
656
|
```
|
|
794
657
|
|
|
@@ -816,7 +679,7 @@ This approach provides several benefits:
|
|
|
816
679
|
|
|
817
680
|
## Decorator Behavior and Instance Context
|
|
818
681
|
|
|
819
|
-
The `@tool`
|
|
682
|
+
The `@tool` decorator provide intelligent instance context management that behaves differently depending on where the decorated methods are defined:
|
|
820
683
|
|
|
821
684
|
### ToolFunction Subclass Context
|
|
822
685
|
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ 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';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.InteractionResult = exports.Interaction = exports.Tool = void 0;
|
|
17
|
+
exports.NestedInteractions = exports.InteractionResult = exports.Interaction = exports.Tool = void 0;
|
|
18
18
|
__exportStar(require("./function/ToolFunction"), exports);
|
|
19
19
|
__exportStar(require("./function/GlobalToolFunction"), exports);
|
|
20
20
|
__exportStar(require("./types/Models"), exports);
|
|
@@ -25,4 +25,5 @@ var Service_1 = require("./service/Service");
|
|
|
25
25
|
Object.defineProperty(exports, "Tool", { enumerable: true, get: function () { return Service_1.Tool; } });
|
|
26
26
|
Object.defineProperty(exports, "Interaction", { enumerable: true, get: function () { return Service_1.Interaction; } });
|
|
27
27
|
Object.defineProperty(exports, "InteractionResult", { enumerable: true, get: function () { return Service_1.InteractionResult; } });
|
|
28
|
+
Object.defineProperty(exports, "NestedInteractions", { enumerable: true, get: function () { return Service_1.NestedInteractions; } });
|
|
28
29
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,gEAA8C;AAC9C,iDAA+B;AAC/B,oDAAkC;AAClC,wDAAsC;AACtC,uDAAqC;AACrC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,gEAA8C;AAC9C,iDAA+B;AAC/B,oDAAkC;AAClC,wDAAsC;AACtC,uDAAqC;AACrC,6CAA6F;AAApF,+FAAA,IAAI,OAAA;AAAE,sGAAA,WAAW,OAAA;AAAE,4GAAA,iBAAiB,OAAA;AAAE,6GAAA,kBAAkB,OAAA"}
|
|
@@ -4,12 +4,10 @@ import * as App from '@zaiusinc/app-sdk';
|
|
|
4
4
|
*/
|
|
5
5
|
export declare class ToolLogger {
|
|
6
6
|
private static readonly SENSITIVE_FIELDS;
|
|
7
|
-
private static readonly MAX_PARAM_LENGTH;
|
|
8
|
-
private static readonly MAX_ARRAY_ITEMS;
|
|
9
7
|
/**
|
|
10
8
|
* Redacts sensitive data from an object
|
|
11
9
|
*/
|
|
12
|
-
private static
|
|
10
|
+
private static redactSensitiveDataAndTruncate;
|
|
13
11
|
/**
|
|
14
12
|
* Checks if a field name is considered sensitive
|
|
15
13
|
*/
|
|
@@ -22,6 +20,16 @@ export declare class ToolLogger {
|
|
|
22
20
|
* Calculates content length of response data
|
|
23
21
|
*/
|
|
24
22
|
private static calculateContentLength;
|
|
23
|
+
/**
|
|
24
|
+
* Extracts the response body as a string or parsed JSON object
|
|
25
|
+
*/
|
|
26
|
+
private static getResponseBody;
|
|
27
|
+
/**
|
|
28
|
+
* Creates a summary of response body with security redaction and truncation
|
|
29
|
+
* For failed responses (4xx, 5xx): returns full body with redacted sensitive data
|
|
30
|
+
* For successful responses (2xx): returns first 100 chars with redacted sensitive data
|
|
31
|
+
*/
|
|
32
|
+
private static createResponseBodySummary;
|
|
25
33
|
/**
|
|
26
34
|
* Logs an incoming request
|
|
27
35
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolLogger.d.ts","sourceRoot":"","sources":["../../src/logging/ToolLogger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ToolLogger.d.ts","sourceRoot":"","sources":["../../src/logging/ToolLogger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,mBAAmB,CAAC;AAMzC;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAyCtC;IAEF;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,8BAA8B;IA4D7C;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAO/B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAQrC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAYrC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IA2C9B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,yBAAyB;IA0CxC;;OAEG;WACW,UAAU,CACtB,GAAG,EAAE,GAAG,CAAC,OAAO,GACf,IAAI;IAaP;;OAEG;WACW,WAAW,CACvB,GAAG,EAAE,GAAG,CAAC,OAAO,EAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ,EACtB,gBAAgB,CAAC,EAAE,MAAM,GACxB,IAAI;CAoBR"}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ToolLogger = void 0;
|
|
4
4
|
const app_sdk_1 = require("@zaiusinc/app-sdk");
|
|
5
|
+
const MAX_PARAM_LOG_LENGTH = 128;
|
|
6
|
+
const MAX_BODY_LOG_LENGTH = 256;
|
|
7
|
+
const MAX_ARRAY_ITEMS = 2;
|
|
5
8
|
/**
|
|
6
9
|
* Utility class for logging Opal tool requests and responses with security considerations
|
|
7
10
|
*/
|
|
@@ -45,12 +48,13 @@ class ToolLogger {
|
|
|
45
48
|
'jwt',
|
|
46
49
|
'bearer_token'
|
|
47
50
|
];
|
|
48
|
-
static MAX_PARAM_LENGTH = 100;
|
|
49
|
-
static MAX_ARRAY_ITEMS = 10;
|
|
50
51
|
/**
|
|
51
52
|
* Redacts sensitive data from an object
|
|
52
53
|
*/
|
|
53
|
-
static
|
|
54
|
+
static redactSensitiveDataAndTruncate(data, maxDepth = 5, accumulatedLength = 0) {
|
|
55
|
+
if (accumulatedLength > MAX_BODY_LOG_LENGTH) {
|
|
56
|
+
return '';
|
|
57
|
+
}
|
|
54
58
|
if (maxDepth <= 0) {
|
|
55
59
|
return '[MAX_DEPTH_EXCEEDED]';
|
|
56
60
|
}
|
|
@@ -58,31 +62,42 @@ class ToolLogger {
|
|
|
58
62
|
return data;
|
|
59
63
|
}
|
|
60
64
|
if (typeof data === 'string') {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
if (data.length > MAX_PARAM_LOG_LENGTH) {
|
|
66
|
+
const lead = data.substring(0, MAX_PARAM_LOG_LENGTH - 10);
|
|
67
|
+
const tail = data.substring(data.length - 10);
|
|
68
|
+
return `${lead}...[${data.length - MAX_PARAM_LOG_LENGTH} truncated]...${tail}`;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
return data;
|
|
72
|
+
}
|
|
64
73
|
}
|
|
65
74
|
if (typeof data === 'number' || typeof data === 'boolean') {
|
|
66
75
|
return data;
|
|
67
76
|
}
|
|
68
77
|
if (Array.isArray(data)) {
|
|
69
|
-
const truncated = data.slice(0,
|
|
70
|
-
const result = truncated.map((item) => this.
|
|
71
|
-
if (data.length >
|
|
72
|
-
result.push(`... (${data.length -
|
|
78
|
+
const truncated = data.slice(0, MAX_ARRAY_ITEMS);
|
|
79
|
+
const result = truncated.map((item) => this.redactSensitiveDataAndTruncate(item, maxDepth, accumulatedLength));
|
|
80
|
+
if (data.length > MAX_ARRAY_ITEMS) {
|
|
81
|
+
result.push(`... (${data.length - MAX_ARRAY_ITEMS} more items truncated)`);
|
|
73
82
|
}
|
|
74
83
|
return result;
|
|
75
84
|
}
|
|
76
85
|
if (typeof data === 'object') {
|
|
77
86
|
const result = {};
|
|
78
87
|
for (const [key, value] of Object.entries(data)) {
|
|
88
|
+
if (accumulatedLength > MAX_BODY_LOG_LENGTH) {
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
79
91
|
// Check if this field contains sensitive data
|
|
80
92
|
const isSensitive = this.isSensitiveField(key);
|
|
81
93
|
if (isSensitive) {
|
|
82
94
|
result[key] = '[REDACTED]';
|
|
83
95
|
}
|
|
84
96
|
else {
|
|
85
|
-
result[key] = this.
|
|
97
|
+
result[key] = this.redactSensitiveDataAndTruncate(value, maxDepth - 1, accumulatedLength);
|
|
98
|
+
}
|
|
99
|
+
if (result[key]) {
|
|
100
|
+
accumulatedLength += JSON.stringify(result[key]).length;
|
|
86
101
|
}
|
|
87
102
|
}
|
|
88
103
|
return result;
|
|
@@ -103,7 +118,7 @@ class ToolLogger {
|
|
|
103
118
|
if (!params) {
|
|
104
119
|
return null;
|
|
105
120
|
}
|
|
106
|
-
return this.
|
|
121
|
+
return this.redactSensitiveDataAndTruncate(params);
|
|
107
122
|
}
|
|
108
123
|
/**
|
|
109
124
|
* Calculates content length of response data
|
|
@@ -119,6 +134,87 @@ class ToolLogger {
|
|
|
119
134
|
return 'unknown';
|
|
120
135
|
}
|
|
121
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Extracts the response body as a string or parsed JSON object
|
|
139
|
+
*/
|
|
140
|
+
static getResponseBody(response) {
|
|
141
|
+
if (!response) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const contentType = response.headers?.get('content-type') || '';
|
|
146
|
+
const isJson = contentType.includes('application/json') || contentType.includes('application/problem+json');
|
|
147
|
+
const isText = contentType.startsWith('text/');
|
|
148
|
+
if (!isJson && !isText) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
// Try to access bodyAsU8Array - this may throw
|
|
152
|
+
const bodyData = response.bodyAsU8Array;
|
|
153
|
+
if (!bodyData) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
// Convert Uint8Array to string
|
|
157
|
+
const bodyString = Buffer.from(bodyData).toString();
|
|
158
|
+
if (!bodyString) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
// Try to parse as JSON if content-type indicates JSON
|
|
162
|
+
if (isJson) {
|
|
163
|
+
try {
|
|
164
|
+
return JSON.parse(bodyString);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// If JSON parsing fails, return as string
|
|
168
|
+
return bodyString;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Return as plain text for non-JSON content types
|
|
172
|
+
return bodyString;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Creates a summary of response body with security redaction and truncation
|
|
180
|
+
* For failed responses (4xx, 5xx): returns full body with redacted sensitive data
|
|
181
|
+
* For successful responses (2xx): returns first 100 chars with redacted sensitive data
|
|
182
|
+
*/
|
|
183
|
+
static createResponseBodySummary(response, success) {
|
|
184
|
+
const body = this.getResponseBody(response);
|
|
185
|
+
if (body === null || body === undefined) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
// For objects (parsed JSON), apply redaction
|
|
189
|
+
if (typeof body === 'object') {
|
|
190
|
+
// For failed responses, don't truncate strings within the object
|
|
191
|
+
const redactedBody = this.redactSensitiveDataAndTruncate(body, 5);
|
|
192
|
+
// For successful responses, truncate to first MAX_BODY_LOG_LENGTH chars
|
|
193
|
+
if (success) {
|
|
194
|
+
const bodyString = JSON.stringify(redactedBody);
|
|
195
|
+
if (bodyString.length > MAX_BODY_LOG_LENGTH) {
|
|
196
|
+
const truncated = bodyString.substring(0, MAX_BODY_LOG_LENGTH);
|
|
197
|
+
return `${truncated}... (truncated)`;
|
|
198
|
+
}
|
|
199
|
+
return redactedBody;
|
|
200
|
+
}
|
|
201
|
+
// For failed responses, return full redacted body
|
|
202
|
+
return redactedBody;
|
|
203
|
+
}
|
|
204
|
+
// For strings (plain text or unparseable JSON)
|
|
205
|
+
if (typeof body === 'string') {
|
|
206
|
+
// For successful responses, truncate to first 100 chars
|
|
207
|
+
if (success) {
|
|
208
|
+
if (body.length > MAX_BODY_LOG_LENGTH) {
|
|
209
|
+
return `${body.substring(0, MAX_BODY_LOG_LENGTH)}... (truncated)`;
|
|
210
|
+
}
|
|
211
|
+
return body;
|
|
212
|
+
}
|
|
213
|
+
// For failed responses, return full body
|
|
214
|
+
return body;
|
|
215
|
+
}
|
|
216
|
+
return body;
|
|
217
|
+
}
|
|
122
218
|
/**
|
|
123
219
|
* Logs an incoming request
|
|
124
220
|
*/
|
|
@@ -137,6 +233,7 @@ class ToolLogger {
|
|
|
137
233
|
* Logs a successful response
|
|
138
234
|
*/
|
|
139
235
|
static logResponse(req, response, processingTimeMs) {
|
|
236
|
+
const success = response.status >= 200 && response.status < 300;
|
|
140
237
|
const responseLog = {
|
|
141
238
|
event: 'opal_tool_response',
|
|
142
239
|
path: req.path,
|
|
@@ -144,8 +241,12 @@ class ToolLogger {
|
|
|
144
241
|
status: response.status,
|
|
145
242
|
contentType: response.headers?.get('content-type') || 'unknown',
|
|
146
243
|
contentLength: this.calculateContentLength(response),
|
|
147
|
-
success
|
|
244
|
+
success
|
|
148
245
|
};
|
|
246
|
+
const responseBodySummary = this.createResponseBodySummary(response, success);
|
|
247
|
+
if (responseBodySummary) {
|
|
248
|
+
responseLog.responseBody = responseBodySummary;
|
|
249
|
+
}
|
|
149
250
|
// Log with Zaius audience so developers only see requests for accounts they have access to
|
|
150
251
|
app_sdk_1.logger.info(app_sdk_1.LogVisibility.Zaius, JSON.stringify(responseLog));
|
|
151
252
|
}
|