@optimizely-opal/opal-tools-sdk 0.1.5-dev → 0.1.6-dev

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.
@@ -0,0 +1,5 @@
1
+ .changeset/pre.json
2
+ dist
3
+ lib
4
+ out
5
+ package-lock.json
package/.prettierrc ADDED
@@ -0,0 +1 @@
1
+ {}
package/README.md CHANGED
@@ -4,36 +4,83 @@ This SDK simplifies the creation of tools services compatible with the Opal Tool
4
4
 
5
5
  ## Features
6
6
 
7
- - Easy definition of tool functions with decorators
8
- - Automatic generation of discovery endpoints
9
- - Parameter validation and type checking
7
+ - Modern `registerTool` API with Zod schemas for type-safe tool definitions
8
+ - Legacy `@tool` decorator API for backwards compatibility
9
+ - Automatic type inference with Zod (registerTool only)
10
+ - Adaptive Block Document support for rich interactive UIs
11
+ - Runtime validation with Zod (registerTool only)
12
+ - Automatic discovery endpoint generation
10
13
  - Authentication helpers
11
14
  - Express integration
12
- - Island components for interactive UI responses
13
15
 
14
16
  ## Installation
15
17
 
16
18
  ```bash
17
- npm install @optimizely-opal/opal-tools-sdk
19
+ npm install @optimizely-opal/opal-tools-sdk express zod
18
20
  ```
19
21
 
20
- ## Usage
22
+ ## Quick Start
23
+
24
+ ### `registerTool`
21
25
 
22
26
  ```typescript
23
- import { ToolsService, tool, IslandResponse, IslandConfig } from '@optimizely-opal/opal-tools-sdk';
24
- import express from 'express';
27
+ import express from "express";
28
+ import { z } from "zod";
29
+ import { ToolsService, registerTool } from "@optimizely-opal/opal-tools-sdk";
25
30
 
26
31
  const app = express();
27
32
  const toolsService = new ToolsService(app);
28
33
 
34
+ // ✨ Types are automatically inferred from the inputSchema!
35
+ const getWeather = registerTool(
36
+ "get_weather",
37
+ {
38
+ description: "Gets current weather for a location",
39
+ inputSchema: {
40
+ location: z.string().describe("City name or location"),
41
+ units: z
42
+ .enum(["metric", "imperial"])
43
+ .optional()
44
+ .describe("Temperature units"),
45
+ },
46
+ },
47
+ async (params) => {
48
+ // params.location is typed as string
49
+ // params.units is typed as 'metric' | 'imperial' | undefined
50
+ return { temperature: 22, condition: "sunny" };
51
+ },
52
+ );
53
+
54
+ app.listen(3000);
55
+ ```
56
+
57
+ ### Legacy `@tool` Decorator API
58
+
59
+ ```typescript
60
+ import { ToolsService, tool, ParameterType } from '@optimizely-opal/opal-tools-sdk';
61
+
29
62
  interface WeatherParameters {
30
63
  location: string;
31
- units: string;
64
+ units?: string;
32
65
  }
33
66
 
34
67
  @tool({
35
68
  name: 'get_weather',
36
- description: 'Gets current weather for a location'
69
+ description: 'Gets current weather for a location',
70
+ parameters: [
71
+ {
72
+ name: 'location',
73
+ type: ParameterType.String,
74
+ description: 'City name or location',
75
+ required: true,
76
+ },
77
+ {
78
+ name: 'units',
79
+ type: ParameterType.String,
80
+ description: 'Temperature units',
81
+ required: false,
82
+ },
83
+ ],
37
84
  })
38
85
  async function getWeather(parameters: WeatherParameters) {
39
86
  // Implementation...
@@ -45,232 +92,210 @@ async function getWeather(parameters: WeatherParameters) {
45
92
 
46
93
  ## Authentication
47
94
 
48
- The SDK provides two ways to require authentication for your tools:
95
+ Both APIs support authentication. The second parameter of your handler receives the extra context object containing auth data.
49
96
 
50
- ### 1. Using the `@requiresAuth` decorator
97
+ ### With `registerTool`
51
98
 
52
99
  ```typescript
53
- import { ToolsService, tool } from '@optimizely-opal/opal-tools-sdk';
54
- import { requiresAuth } from '@optimizely-opal/opal-tools-sdk/auth';
55
- import express from 'express';
56
-
57
- const app = express();
58
- const toolsService = new ToolsService(app);
59
-
60
- interface CalendarParameters {
61
- date: string;
62
- timezone?: string;
63
- }
64
-
65
- // Single authentication requirement
66
- @requiresAuth({ provider: 'google', scopeBundle: 'calendar', required: true })
67
- @tool({
68
- name: 'get_calendar_events',
69
- description: 'Gets calendar events for a date'
70
- })
71
- async function getCalendarEvents(parameters: CalendarParameters, authData?: any) {
72
- // The authData parameter contains authentication information
73
- const token = authData?.credentials?.token || '';
74
-
75
- // Use the token to make authenticated requests
76
- // ...
77
-
78
- return { events: ['Meeting at 10:00', 'Lunch at 12:00'] };
79
- }
80
-
81
- // Multiple authentication requirements (tool can work with either provider)
82
- @requiresAuth({ provider: 'google', scopeBundle: 'calendar', required: true })
83
- @requiresAuth({ provider: 'microsoft', scopeBundle: 'outlook', required: true })
84
- @tool({
85
- name: 'get_calendar_availability',
86
- description: 'Check calendar availability'
87
- })
88
- async function getCalendarAvailability(parameters: CalendarParameters, authData?: any) {
89
- const provider = authData?.provider || '';
90
- const token = authData?.credentials?.token || '';
91
-
92
- if (provider === 'google') {
93
- // Use Google Calendar API
94
- } else if (provider === 'microsoft') {
95
- // Use Microsoft Outlook API
96
- }
97
-
98
- return { available: true, provider_used: provider };
99
- }
100
+ import { z } from "zod";
101
+
102
+ const getCalendarEvents = registerTool(
103
+ "get_calendar_events",
104
+ {
105
+ description: "Gets calendar events for a date",
106
+ inputSchema: {
107
+ date: z.string().describe("Date in YYYY-MM-DD format"),
108
+ },
109
+ authRequirements: {
110
+ provider: "google",
111
+ scopeBundle: "calendar",
112
+ required: true,
113
+ },
114
+ },
115
+ async (params, extra) => {
116
+ // Access auth data from extra context
117
+ const token = extra?.auth?.credentials?.access_token;
118
+ // Check execution mode if needed
119
+ const mode = extra?.mode; // 'headless' | 'interactive'
120
+
121
+ // Use token to make authenticated requests
122
+ return { events: ["Meeting at 10:00", "Lunch at 12:00"] };
123
+ },
124
+ );
100
125
  ```
101
126
 
102
- ### 2. Specifying auth requirements in the `@tool` decorator
127
+ ### With `@tool` Decorator
103
128
 
104
129
  ```typescript
105
- interface EmailParameters {
106
- limit?: number;
107
- folder?: string;
108
- }
109
-
110
130
  @tool({
111
- name: 'get_email',
112
- description: 'Gets emails from the user\'s inbox',
131
+ name: 'get_calendar_events',
132
+ description: 'Gets calendar events',
133
+ parameters: [
134
+ {
135
+ name: 'date',
136
+ type: ParameterType.String,
137
+ description: 'Date in YYYY-MM-DD format',
138
+ required: true,
139
+ },
140
+ ],
113
141
  authRequirements: {
114
142
  provider: 'google',
115
- scopeBundle: 'gmail',
116
- required: true
117
- }
143
+ scopeBundle: 'calendar',
144
+ required: true,
145
+ },
118
146
  })
119
- async function getEmail(parameters: EmailParameters, authData?: any) {
120
- // Implementation...
121
- return { emails: ['Email 1', 'Email 2'] };
147
+ async function getCalendarEvents(params: any, environment?: any) {
148
+ const token = environment?.auth?.credentials?.access_token;
149
+ return { events: [] };
122
150
  }
123
151
  ```
124
152
 
125
- ## Authentication
126
-
127
- The SDK provides authentication support for tools that require user credentials:
153
+ ## Adaptive Block Documents
128
154
 
129
- ```typescript
130
- import { ToolsService, tool, requiresAuth, AuthData } from '@optimizely-opal/opal-tools-sdk';
131
- import express from 'express';
132
-
133
- interface CalendarParameters {
134
- date: string;
135
- timezone?: string;
136
- }
155
+ Adaptive Block Documents enable rich, interactive UI responses with forms, buttons, and dynamic content.
137
156
 
138
- const app = express();
139
- const toolsService = new ToolsService(app);
157
+ ### Creating Adaptive Block Responses
140
158
 
141
- @requiresAuth({ provider: 'google', scopeBundle: 'calendar', required: true })
142
- @tool({
143
- name: 'get_calendar_events',
144
- description: 'Gets calendar events for a date'
145
- })
146
- async function getCalendarEvents(parameters: CalendarParameters, authData?: AuthData) {
147
- // The authData parameter contains authentication information
148
- if (authData) {
149
- const token = authData.credentials.access_token;
150
- // Use the token to make authenticated requests
151
- }
152
-
153
- return { events: ['Meeting at 10:00', 'Lunch at 12:00'] };
154
- }
159
+ ```typescript
160
+ import { z } from "zod";
161
+ import { registerTool, Block } from "@optimizely-opal/opal-tools-sdk";
162
+
163
+ const createTask = registerTool(
164
+ "create_task",
165
+ {
166
+ description: "Create a new task",
167
+ type: "block", // Specify this is an Adaptive Block tool
168
+ inputSchema: {
169
+ title: z.string().describe("Task title"),
170
+ description: z.string().optional().describe("Task description"),
171
+ },
172
+ },
173
+ async (params) => {
174
+ // Return a BlockResponse (plain object with content/data/artifact/etc)
175
+ return {
176
+ content: Block.Document({
177
+ children: [
178
+ Block.Heading({ children: "Task Created!", level: "1" }),
179
+ Block.Text({ children: `Created: ${params.title}` }),
180
+ Block.Input({
181
+ name: "notes",
182
+ placeholder: "Add notes...",
183
+ value: params.description || "",
184
+ }),
185
+ ],
186
+ actions: [
187
+ Block.Action({ name: "save", children: "Save Changes" }),
188
+ Block.Action({
189
+ name: "delete",
190
+ children: "Delete",
191
+ variant: "danger",
192
+ }),
193
+ ],
194
+ }),
195
+ data: { task_id: "123", created_at: new Date().toISOString() },
196
+ artifact: {
197
+ type: "task",
198
+ id: "task-123",
199
+ data: { title: params.title },
200
+ },
201
+ };
202
+ },
203
+ );
155
204
  ```
156
205
 
157
- ## Island Components
206
+ ### Adaptive Block Components
158
207
 
159
- The SDK includes Island components for creating interactive UI responses:
208
+ The SDK provides a type-safe `Block` namespace with factory functions for all components:
160
209
 
161
- ```typescript
162
- import {
163
- ToolsService,
164
- tool,
165
- IslandResponse,
166
- IslandConfig
167
- } from '@optimizely-opal/opal-tools-sdk';
168
- import express from 'express';
210
+ - `Block.Document()` - Root container with children and actions
211
+ - `Block.Heading()` - Headings with levels 1-6
212
+ - `Block.Text()` - Text content
213
+ - `Block.Input()` - Text input fields
214
+ - `Block.Textarea()` - Multi-line text areas
215
+ - `Block.Checkbox()` - Checkbox inputs
216
+ - `Block.Select()` - Dropdown selects
217
+ - `Block.Button()` - Action buttons
218
+ - `Block.Action()` - Document-level actions
219
+ - `Block.List()` - Ordered/unordered lists
220
+ - `Block.Table()` - Data tables
221
+ - And more...
169
222
 
170
- interface WeatherParameters {
171
- location: string;
172
- units?: string;
173
- }
223
+ See the generated [src/block.ts](./src/block.ts) for the complete list and TypeScript types.
174
224
 
175
- const app = express();
176
- const toolsService = new ToolsService(app);
225
+ ### Regenerating Adaptive Block Types
177
226
 
178
- @tool({
179
- name: 'get_weather',
180
- description: 'Gets current weather for a location'
181
- })
182
- async function getWeather(parameters: WeatherParameters) {
183
- // Get weather data (implementation details omitted)
184
- const weatherData = { temperature: 22, condition: 'sunny', humidity: 65 };
185
-
186
- // Create an interactive island for weather settings
187
- const island = new IslandConfig(
188
- [
189
- new IslandConfig.Field(
190
- 'location',
191
- 'Location',
192
- 'string',
193
- parameters.location
194
- ),
195
- new IslandConfig.Field(
196
- 'units',
197
- 'Temperature Units',
198
- 'string',
199
- parameters.units || 'metric',
200
- false,
201
- ['metric', 'imperial', 'kelvin']
202
- ),
203
- new IslandConfig.Field(
204
- 'current_temp',
205
- 'Current Temperature',
206
- 'string',
207
- `${weatherData.temperature}°${parameters.units === 'metric' ? 'C' : 'F'}`
208
- )
209
- ],
210
- [
211
- new IslandConfig.Action(
212
- 'refresh_weather',
213
- 'Refresh Weather',
214
- 'button',
215
- '/tools/get_weather',
216
- 'update'
217
- )
218
- ],
219
- 'button', // Optional island type
220
- 'plus' // Optional island icon
221
- );
222
-
223
- return IslandResponse.create([island]);
224
- }
227
+ The Adaptive Block types are auto-generated from the JSON schema. To regenerate:
228
+
229
+ ```bash
230
+ npm run generate:block
225
231
  ```
226
232
 
227
- ### Island Components
228
-
229
- #### IslandConfig.Field
230
- Fields represent data inputs in the UI:
231
- - `name`: Programmatic field identifier
232
- - `label`: Human-readable label
233
- - `type`: Field type (`'string'`, `'boolean'`, `'json'`)
234
- - `value`: Current field value (optional)
235
- - `hidden`: Whether to hide from user (optional, default: false)
236
- - `options`: Available options for selection (optional)
237
-
238
- #### IslandConfig.Action
239
- Actions represent buttons or operations:
240
- - `name`: Programmatic action identifier
241
- - `label`: Human-readable button label
242
- - `type`: UI element type (typically `'button'`)
243
- - `endpoint`: API endpoint to call
244
- - `operation`: Operation type (default: `'create'`)
245
-
246
- #### IslandConfig
247
- Contains the complete island configuration:
248
- - `fields`: Array of IslandConfig.Field objects
249
- - `actions`: Array of IslandConfig.Action objects
250
- - `type`: Optional island type for custom rendering (optional)
251
- - `icon`: Optional icon identifier for the island (optional)
252
-
253
- #### IslandResponse
254
- The response wrapper for islands:
255
- - Use `IslandResponse.create([islands])` to create responses
256
- - Supports multiple islands per response
233
+ This reads `block-document-spec.json` and generates TypeScript interfaces and factory functions in [src/block.ts](./src/block.ts).
234
+
235
+ **Note:** Don't edit `src/block.ts` manually - it will be overwritten on regeneration.
257
236
 
258
237
  ## Type Definitions
259
238
 
260
239
  The SDK provides comprehensive TypeScript type definitions:
261
240
 
262
241
  ### Authentication Types
263
- - `AuthData`: Interface containing provider and credentials information
264
- - `Credentials`: Interface with access_token, org_sso_id, customer_id, instance_id, and product_sku
265
- - `Environment`: Interface specifying execution mode (`'headless'` or `'interactive'`)
242
+
243
+ - `AuthData` - Provider and credentials information
244
+ - `Credentials` - Access tokens and org details
245
+ - `Environment` - Execution context with auth data
266
246
 
267
247
  ### Parameter Types
268
- - `ParameterType`: Enum for supported parameter types
269
- - `Parameter`: Class for tool parameter definitions
270
- - `Function`: Class for complete tool function definitions
271
248
 
272
- These types provide full IDE support and compile-time type checking for better development experience.
249
+ - `ParameterType` - Enum for parameter types (String, Number, Boolean, List, Dictionary)
250
+ - `Parameter` - Tool parameter definitions
251
+ - `Function` - Complete tool function definitions
252
+
253
+ ### Block Types
254
+
255
+ All Block Document components have full TypeScript interfaces with type checking and IDE autocomplete.
256
+
257
+ ## API Reference
258
+
259
+ ### `registerTool<TSchema, TReturn>(name, options, handler)`
260
+
261
+ Modern API for defining tools with Zod schemas.
262
+
263
+ **Parameters:**
264
+
265
+ - `name: string` - Tool name (required)
266
+ - `options: ToolOptions` - Configuration object
267
+ - `description: string` - Tool description (required)
268
+ - `inputSchema: Record<string, z.ZodTypeAny>` - Zod schema for parameters (required)
269
+ - `type?: 'json' | 'block'` - Response type (default: 'json')
270
+ - `authRequirements?` - Authentication requirements
271
+ - `handler: (params, extra?) => Result` - Tool implementation
272
+ - `params` - Validated input parameters (typed from schema)
273
+ - `extra?` - Optional extra context
274
+ - `mode: 'headless' | 'interactive'` - Execution mode
275
+ - `auth?: { provider, credentials }` - Auth data if provided
276
+
277
+ **Returns:** The handler function with full type inference
278
+
279
+ ### `@tool(options)`
280
+
281
+ Legacy decorator for defining tools.
282
+
283
+ **Options:**
284
+
285
+ - `name: string` - Tool name
286
+ - `description: string` - Tool description
287
+ - `parameters: ParameterDefinition[]` - Parameter definitions
288
+ - `authRequirements?` - Authentication requirements
289
+
290
+ ### `ToolsService`
291
+
292
+ Express service that manages tool registration and creates discovery endpoints.
293
+
294
+ ```typescript
295
+ const toolsService = new ToolsService(app);
296
+ // Automatically creates /discovery endpoint
297
+ ```
273
298
 
274
- ## Documentation
299
+ ## License
275
300
 
276
- See full documentation for more examples and configuration options.
301
+ MIT
package/dist/auth.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ interface AuthOptions {
2
+ provider: string;
3
+ required?: boolean;
4
+ scopeBundle: string;
5
+ }
1
6
  /**
2
7
  * Middleware to handle authentication requirements
3
8
  * @param req Express request
@@ -5,11 +10,6 @@
5
10
  * @param next Express next function
6
11
  */
7
12
  export declare function authMiddleware(req: any, res: any, next: any): any;
8
- interface AuthOptions {
9
- provider: string;
10
- scopeBundle: string;
11
- required?: boolean;
12
- }
13
13
  /**
14
14
  * Decorator to indicate that a tool requires authentication
15
15
  * @param options Authentication options
package/dist/auth.js CHANGED
@@ -11,7 +11,7 @@ exports.requiresAuth = requiresAuth;
11
11
  function authMiddleware(req, res, next) {
12
12
  const authHeader = req.headers.authorization;
13
13
  if (!authHeader && req.authRequired) {
14
- return res.status(401).json({ error: 'Authentication required' });
14
+ return res.status(401).json({ error: "Authentication required" });
15
15
  }
16
16
  // The Tools Management Service will provide the appropriate token
17
17
  // in the Authorization header
@@ -31,8 +31,8 @@ function requiresAuth(options) {
31
31
  const fn = originalMethod;
32
32
  fn.__authRequirements__ = {
33
33
  provider: options.provider,
34
+ required: options.required ?? true,
34
35
  scopeBundle: options.scopeBundle,
35
- required: options.required ?? true
36
36
  };
37
37
  return originalMethod.apply(this, args);
38
38
  };