optimizely-opal.opal-tools-sdk 0.1.3.dev0__tar.gz → 0.1.5.dev0__tar.gz

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 (22) hide show
  1. optimizely_opal_opal_tools_sdk-0.1.5.dev0/PKG-INFO +253 -0
  2. optimizely_opal_opal_tools_sdk-0.1.5.dev0/README.md +229 -0
  3. optimizely_opal_opal_tools_sdk-0.1.5.dev0/opal_tools_sdk/__init__.py +20 -0
  4. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/opal_tools_sdk/decorators.py +6 -5
  5. optimizely_opal_opal_tools_sdk-0.1.5.dev0/opal_tools_sdk/logging.py +37 -0
  6. optimizely_opal_opal_tools_sdk-0.1.5.dev0/opal_tools_sdk/models.py +126 -0
  7. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/opal_tools_sdk/service.py +10 -10
  8. optimizely_opal_opal_tools_sdk-0.1.5.dev0/optimizely_opal.opal_tools_sdk.egg-info/PKG-INFO +253 -0
  9. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/optimizely_opal.opal_tools_sdk.egg-info/SOURCES.txt +1 -0
  10. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/pyproject.toml +1 -1
  11. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/setup.py +1 -1
  12. optimizely_opal_opal_tools_sdk-0.1.3.dev0/PKG-INFO +0 -138
  13. optimizely_opal_opal_tools_sdk-0.1.3.dev0/README.md +0 -114
  14. optimizely_opal_opal_tools_sdk-0.1.3.dev0/opal_tools_sdk/__init__.py +0 -6
  15. optimizely_opal_opal_tools_sdk-0.1.3.dev0/opal_tools_sdk/models.py +0 -69
  16. optimizely_opal_opal_tools_sdk-0.1.3.dev0/optimizely_opal.opal_tools_sdk.egg-info/PKG-INFO +0 -138
  17. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/opal_tools_sdk/_registry.py +0 -0
  18. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/opal_tools_sdk/auth.py +0 -0
  19. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/optimizely_opal.opal_tools_sdk.egg-info/dependency_links.txt +0 -0
  20. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/optimizely_opal.opal_tools_sdk.egg-info/requires.txt +0 -0
  21. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/optimizely_opal.opal_tools_sdk.egg-info/top_level.txt +0 -0
  22. {optimizely_opal_opal_tools_sdk-0.1.3.dev0 → optimizely_opal_opal_tools_sdk-0.1.5.dev0}/setup.cfg +0 -0
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.4
2
+ Name: optimizely-opal.opal-tools-sdk
3
+ Version: 0.1.5.dev0
4
+ Summary: SDK for creating Opal-compatible tools services
5
+ Home-page: https://github.com/optimizely/opal-tools-sdk
6
+ Author: Optimizely
7
+ Author-email: Optimizely <opal-team@optimizely.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/optimizely/opal-tools-sdk
10
+ Project-URL: Bug Tracker, https://github.com/optimizely/opal-tools-sdk/issues
11
+ Keywords: opal,tools,sdk,ai,llm
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: fastapi>=0.100.0
19
+ Requires-Dist: pydantic>=2.0.0
20
+ Requires-Dist: httpx>=0.24.1
21
+ Dynamic: author
22
+ Dynamic: home-page
23
+ Dynamic: requires-python
24
+
25
+ # Opal Tools SDK for Python
26
+
27
+ This SDK simplifies the creation of tools services compatible with the Opal Tools Management Service.
28
+
29
+ ## Features
30
+
31
+ - Easy definition of tool functions with decorators
32
+ - Automatic generation of discovery endpoints
33
+ - Parameter validation and type checking
34
+ - Authentication helpers
35
+ - FastAPI integration
36
+ - Island components for interactive UI responses
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install optimizely-opal.opal-tools-sdk
42
+ ```
43
+
44
+ Note: While the package is installed as `optimizely-opal.opal-tools-sdk`, you'll still import it in your code as `opal_tools_sdk`:
45
+
46
+ ```python
47
+ # Import using the package name
48
+ from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ```python
54
+ from opal_tools_sdk import ToolsService, tool
55
+ from pydantic import BaseModel
56
+ from fastapi import FastAPI
57
+
58
+ app = FastAPI()
59
+ tools_service = ToolsService(app)
60
+
61
+ class WeatherParameters(BaseModel):
62
+ location: str
63
+ units: str = "metric"
64
+
65
+ @tool("get_weather", "Gets current weather for a location")
66
+ async def get_weather(parameters: WeatherParameters):
67
+ # Implementation...
68
+ return {"temperature": 22, "condition": "sunny"}
69
+
70
+ # Discovery endpoint is automatically created at /discovery
71
+ ```
72
+
73
+ ## Authentication
74
+
75
+ The SDK provides two ways to require authentication for your tools:
76
+
77
+ ### 1. Using the `@requires_auth` decorator
78
+
79
+ ```python
80
+ from opal_tools_sdk import ToolsService, tool, AuthData
81
+ from opal_tools_sdk.auth import requires_auth
82
+ from pydantic import BaseModel
83
+ from fastapi import FastAPI
84
+ from typing import Optional
85
+
86
+ app = FastAPI()
87
+ tools_service = ToolsService(app)
88
+
89
+ class CalendarParameters(BaseModel):
90
+ date: str
91
+ timezone: str = "UTC"
92
+
93
+ # Single authentication requirement
94
+ @requires_auth(provider="google", scope_bundle="calendar", required=True)
95
+ @tool("get_calendar_events", "Gets calendar events for a date")
96
+ async def get_calendar_events(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
97
+ # The auth_data parameter contains authentication information
98
+ if auth_data:
99
+ token = auth_data["credentials"]["access_token"]
100
+
101
+ # Use the token to make authenticated requests
102
+ # ...
103
+
104
+ return {"events": ["Meeting at 10:00", "Lunch at 12:00"]}
105
+
106
+ # Multiple authentication requirements (tool can work with either provider)
107
+ @requires_auth(provider="google", scope_bundle="calendar", required=True)
108
+ @requires_auth(provider="microsoft", scope_bundle="outlook", required=True)
109
+ @tool("get_calendar_availability", "Check calendar availability")
110
+ async def get_calendar_availability(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
111
+ provider = ""
112
+ token = ""
113
+
114
+ if auth_data:
115
+ provider = auth_data["provider"]
116
+ token = auth_data["credentials"]["access_token"]
117
+
118
+ if provider == "google":
119
+ # Use Google Calendar API
120
+ pass
121
+ elif provider == "microsoft":
122
+ # Use Microsoft Outlook API
123
+ pass
124
+
125
+ return {"available": True, "provider_used": provider}
126
+ ```
127
+
128
+ ### 2. Specifying auth requirements in the `@tool` decorator
129
+
130
+ ```python
131
+ @tool(
132
+ "get_email",
133
+ "Gets emails from the user's inbox",
134
+ auth_requirements=[
135
+ {"provider": "google", "scope_bundle": "gmail", "required": True}
136
+ ]
137
+ )
138
+ async def get_email(parameters: EmailParameters, auth_data: Optional[AuthData] = None):
139
+ # Implementation...
140
+ return {"emails": ["Email 1", "Email 2"]}
141
+ ```
142
+
143
+ ## Island Components
144
+
145
+ The SDK includes Island components for creating interactive UI responses that allow users to input data and trigger actions.
146
+
147
+ ### Weather Tool with Interactive Island
148
+
149
+ ```python
150
+ from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
151
+ from pydantic import BaseModel
152
+ from fastapi import FastAPI
153
+
154
+ app = FastAPI()
155
+ tools_service = ToolsService(app)
156
+
157
+ class WeatherParameters(BaseModel):
158
+ location: str
159
+ units: str = "metric"
160
+
161
+ @tool("get_weather", "Gets current weather for a location")
162
+ async def get_weather(parameters: WeatherParameters):
163
+ # Get weather data (implementation details omitted)
164
+ weather_data = {"temperature": 22, "condition": "sunny", "humidity": 65}
165
+
166
+ # Create an interactive island for weather settings
167
+ island = IslandConfig(
168
+ fields=[
169
+ IslandConfig.Field(
170
+ name="location",
171
+ label="Location",
172
+ type="string",
173
+ value=parameters.location
174
+ ),
175
+ IslandConfig.Field(
176
+ name="units",
177
+ label="Temperature Units",
178
+ type="string",
179
+ value=parameters.units,
180
+ options=["metric", "imperial", "kelvin"]
181
+ ),
182
+ IslandConfig.Field(
183
+ name="current_temp",
184
+ label="Current Temperature",
185
+ type="string",
186
+ value=f"{weather_data['temperature']}°{'C' if parameters.units == 'metric' else 'F'}"
187
+ )
188
+ ],
189
+ actions=[
190
+ IslandConfig.Action(
191
+ name="refresh_weather",
192
+ label="Refresh Weather",
193
+ type="button",
194
+ endpoint="/tools/get_weather",
195
+ operation="update"
196
+ )
197
+ ]
198
+ )
199
+
200
+ return IslandResponse.create([island])
201
+ ```
202
+
203
+ ### Island Components
204
+
205
+ #### IslandConfig.Field
206
+ Fields represent data inputs in the UI:
207
+ - `name`: Programmatic field identifier
208
+ - `label`: Human-readable label
209
+ - `type`: Field type (`"string"`, `"boolean"`, `"json"`)
210
+ - `value`: Current field value (optional)
211
+ - `hidden`: Whether to hide from user (optional, default: False)
212
+ - `options`: Available options for selection (optional)
213
+
214
+ #### IslandConfig.Action
215
+ Actions represent buttons or operations:
216
+ - `name`: Programmatic action identifier
217
+ - `label`: Human-readable button label
218
+ - `type`: UI element type (typically `"button"`)
219
+ - `endpoint`: API endpoint to call
220
+ - `operation`: Operation type (default: `"create"`)
221
+
222
+ #### IslandConfig
223
+ Contains the complete island configuration:
224
+ - `fields`: List of IslandConfig.Field objects
225
+ - `actions`: List of IslandConfig.Action objects
226
+
227
+ #### IslandResponse
228
+ The response wrapper for islands:
229
+ - Use `IslandResponse.create([islands])` to create responses
230
+ - Supports multiple islands per response
231
+
232
+ ## Type Definitions
233
+
234
+ The SDK provides several TypedDict and dataclass definitions for better type safety:
235
+
236
+ ### Authentication Types
237
+ - `AuthData`: TypedDict containing provider and credentials information
238
+ - `Credentials`: TypedDict with access_token, org_sso_id, customer_id, instance_id, and product_sku
239
+ - `AuthRequirement`: Dataclass for specifying authentication requirements
240
+
241
+ ### Execution Environment
242
+ - `Environment`: TypedDict specifying execution mode (`"headless"` or `"interactive"`)
243
+
244
+ ### Parameter Types
245
+ - `ParameterType`: Enum for supported parameter types (string, integer, number, boolean, list, dictionary)
246
+ - `Parameter`: Dataclass for tool parameter definitions
247
+ - `Function`: Dataclass for complete tool function definitions
248
+
249
+ These types are automatically imported when you import from `opal_tools_sdk` and provide better IDE support and type checking.
250
+
251
+ ## Documentation
252
+
253
+ See full documentation for more examples and configuration options.
@@ -0,0 +1,229 @@
1
+ # Opal Tools SDK for Python
2
+
3
+ This SDK simplifies the creation of tools services compatible with the Opal Tools Management Service.
4
+
5
+ ## Features
6
+
7
+ - Easy definition of tool functions with decorators
8
+ - Automatic generation of discovery endpoints
9
+ - Parameter validation and type checking
10
+ - Authentication helpers
11
+ - FastAPI integration
12
+ - Island components for interactive UI responses
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install optimizely-opal.opal-tools-sdk
18
+ ```
19
+
20
+ Note: While the package is installed as `optimizely-opal.opal-tools-sdk`, you'll still import it in your code as `opal_tools_sdk`:
21
+
22
+ ```python
23
+ # Import using the package name
24
+ from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from opal_tools_sdk import ToolsService, tool
31
+ from pydantic import BaseModel
32
+ from fastapi import FastAPI
33
+
34
+ app = FastAPI()
35
+ tools_service = ToolsService(app)
36
+
37
+ class WeatherParameters(BaseModel):
38
+ location: str
39
+ units: str = "metric"
40
+
41
+ @tool("get_weather", "Gets current weather for a location")
42
+ async def get_weather(parameters: WeatherParameters):
43
+ # Implementation...
44
+ return {"temperature": 22, "condition": "sunny"}
45
+
46
+ # Discovery endpoint is automatically created at /discovery
47
+ ```
48
+
49
+ ## Authentication
50
+
51
+ The SDK provides two ways to require authentication for your tools:
52
+
53
+ ### 1. Using the `@requires_auth` decorator
54
+
55
+ ```python
56
+ from opal_tools_sdk import ToolsService, tool, AuthData
57
+ from opal_tools_sdk.auth import requires_auth
58
+ from pydantic import BaseModel
59
+ from fastapi import FastAPI
60
+ from typing import Optional
61
+
62
+ app = FastAPI()
63
+ tools_service = ToolsService(app)
64
+
65
+ class CalendarParameters(BaseModel):
66
+ date: str
67
+ timezone: str = "UTC"
68
+
69
+ # Single authentication requirement
70
+ @requires_auth(provider="google", scope_bundle="calendar", required=True)
71
+ @tool("get_calendar_events", "Gets calendar events for a date")
72
+ async def get_calendar_events(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
73
+ # The auth_data parameter contains authentication information
74
+ if auth_data:
75
+ token = auth_data["credentials"]["access_token"]
76
+
77
+ # Use the token to make authenticated requests
78
+ # ...
79
+
80
+ return {"events": ["Meeting at 10:00", "Lunch at 12:00"]}
81
+
82
+ # Multiple authentication requirements (tool can work with either provider)
83
+ @requires_auth(provider="google", scope_bundle="calendar", required=True)
84
+ @requires_auth(provider="microsoft", scope_bundle="outlook", required=True)
85
+ @tool("get_calendar_availability", "Check calendar availability")
86
+ async def get_calendar_availability(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
87
+ provider = ""
88
+ token = ""
89
+
90
+ if auth_data:
91
+ provider = auth_data["provider"]
92
+ token = auth_data["credentials"]["access_token"]
93
+
94
+ if provider == "google":
95
+ # Use Google Calendar API
96
+ pass
97
+ elif provider == "microsoft":
98
+ # Use Microsoft Outlook API
99
+ pass
100
+
101
+ return {"available": True, "provider_used": provider}
102
+ ```
103
+
104
+ ### 2. Specifying auth requirements in the `@tool` decorator
105
+
106
+ ```python
107
+ @tool(
108
+ "get_email",
109
+ "Gets emails from the user's inbox",
110
+ auth_requirements=[
111
+ {"provider": "google", "scope_bundle": "gmail", "required": True}
112
+ ]
113
+ )
114
+ async def get_email(parameters: EmailParameters, auth_data: Optional[AuthData] = None):
115
+ # Implementation...
116
+ return {"emails": ["Email 1", "Email 2"]}
117
+ ```
118
+
119
+ ## Island Components
120
+
121
+ The SDK includes Island components for creating interactive UI responses that allow users to input data and trigger actions.
122
+
123
+ ### Weather Tool with Interactive Island
124
+
125
+ ```python
126
+ from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
127
+ from pydantic import BaseModel
128
+ from fastapi import FastAPI
129
+
130
+ app = FastAPI()
131
+ tools_service = ToolsService(app)
132
+
133
+ class WeatherParameters(BaseModel):
134
+ location: str
135
+ units: str = "metric"
136
+
137
+ @tool("get_weather", "Gets current weather for a location")
138
+ async def get_weather(parameters: WeatherParameters):
139
+ # Get weather data (implementation details omitted)
140
+ weather_data = {"temperature": 22, "condition": "sunny", "humidity": 65}
141
+
142
+ # Create an interactive island for weather settings
143
+ island = IslandConfig(
144
+ fields=[
145
+ IslandConfig.Field(
146
+ name="location",
147
+ label="Location",
148
+ type="string",
149
+ value=parameters.location
150
+ ),
151
+ IslandConfig.Field(
152
+ name="units",
153
+ label="Temperature Units",
154
+ type="string",
155
+ value=parameters.units,
156
+ options=["metric", "imperial", "kelvin"]
157
+ ),
158
+ IslandConfig.Field(
159
+ name="current_temp",
160
+ label="Current Temperature",
161
+ type="string",
162
+ value=f"{weather_data['temperature']}°{'C' if parameters.units == 'metric' else 'F'}"
163
+ )
164
+ ],
165
+ actions=[
166
+ IslandConfig.Action(
167
+ name="refresh_weather",
168
+ label="Refresh Weather",
169
+ type="button",
170
+ endpoint="/tools/get_weather",
171
+ operation="update"
172
+ )
173
+ ]
174
+ )
175
+
176
+ return IslandResponse.create([island])
177
+ ```
178
+
179
+ ### Island Components
180
+
181
+ #### IslandConfig.Field
182
+ Fields represent data inputs in the UI:
183
+ - `name`: Programmatic field identifier
184
+ - `label`: Human-readable label
185
+ - `type`: Field type (`"string"`, `"boolean"`, `"json"`)
186
+ - `value`: Current field value (optional)
187
+ - `hidden`: Whether to hide from user (optional, default: False)
188
+ - `options`: Available options for selection (optional)
189
+
190
+ #### IslandConfig.Action
191
+ Actions represent buttons or operations:
192
+ - `name`: Programmatic action identifier
193
+ - `label`: Human-readable button label
194
+ - `type`: UI element type (typically `"button"`)
195
+ - `endpoint`: API endpoint to call
196
+ - `operation`: Operation type (default: `"create"`)
197
+
198
+ #### IslandConfig
199
+ Contains the complete island configuration:
200
+ - `fields`: List of IslandConfig.Field objects
201
+ - `actions`: List of IslandConfig.Action objects
202
+
203
+ #### IslandResponse
204
+ The response wrapper for islands:
205
+ - Use `IslandResponse.create([islands])` to create responses
206
+ - Supports multiple islands per response
207
+
208
+ ## Type Definitions
209
+
210
+ The SDK provides several TypedDict and dataclass definitions for better type safety:
211
+
212
+ ### Authentication Types
213
+ - `AuthData`: TypedDict containing provider and credentials information
214
+ - `Credentials`: TypedDict with access_token, org_sso_id, customer_id, instance_id, and product_sku
215
+ - `AuthRequirement`: Dataclass for specifying authentication requirements
216
+
217
+ ### Execution Environment
218
+ - `Environment`: TypedDict specifying execution mode (`"headless"` or `"interactive"`)
219
+
220
+ ### Parameter Types
221
+ - `ParameterType`: Enum for supported parameter types (string, integer, number, boolean, list, dictionary)
222
+ - `Parameter`: Dataclass for tool parameter definitions
223
+ - `Function`: Dataclass for complete tool function definitions
224
+
225
+ These types are automatically imported when you import from `opal_tools_sdk` and provide better IDE support and type checking.
226
+
227
+ ## Documentation
228
+
229
+ See full documentation for more examples and configuration options.
@@ -0,0 +1,20 @@
1
+ from .service import ToolsService
2
+ from .decorators import tool
3
+ from .auth import requires_auth
4
+ from .logging import register_logger_factory
5
+ from .models import AuthData, AuthRequirement, Credentials, Environment, IslandConfig, IslandResponse
6
+
7
+ __version__ = "0.1.0"
8
+ __all__ = [
9
+ "ToolsService",
10
+ "tool",
11
+ "requires_auth",
12
+ "register_logger_factory",
13
+ # Models
14
+ "AuthData",
15
+ "AuthRequirement",
16
+ "Credentials",
17
+ "Environment",
18
+ "IslandConfig",
19
+ "IslandResponse",
20
+ ]
@@ -6,8 +6,9 @@ from fastapi import APIRouter, Depends, Header, HTTPException
6
6
  from pydantic import BaseModel
7
7
 
8
8
  from .models import Parameter, ParameterType, AuthRequirement
9
+ from .logging import get_logger
9
10
 
10
- logger = logging.getLogger("opal_tools_sdk")
11
+ logger = get_logger(__name__)
11
12
 
12
13
  def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str, Any]]] = None):
13
14
  """Decorator to register a function as an Opal tool.
@@ -95,9 +96,9 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
95
96
  required=required
96
97
  ))
97
98
 
98
- print(f"Registered parameter: {field_name} of type {param_type.value}, required: {required}")
99
+ logger.info(f"Registered parameter: {field_name} of type {param_type.value}, required: {required}")
99
100
  else:
100
- print(f"Warning: No parameter model found for {name}")
101
+ logger.warning(f"Warning: No parameter model found for {name}")
101
102
 
102
103
  endpoint = f"/tools/{name}"
103
104
 
@@ -112,10 +113,10 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
112
113
  required=auth_req.get("required", True)
113
114
  ))
114
115
 
115
- print(f"Registering tool {name} with endpoint {endpoint}")
116
+ logger.info(f"Registering tool {name} with endpoint {endpoint}")
116
117
 
117
118
  if not _registry.services:
118
- print("No services registered in registry! Make sure to create ToolsService before decorating functions.")
119
+ logger.warning("No services registered in registry! Make sure to create ToolsService before decorating functions.")
119
120
 
120
121
  for service in _registry.services:
121
122
  service.register_tool(
@@ -0,0 +1,37 @@
1
+ import logging
2
+ import sys
3
+ from typing import Optional, Callable
4
+
5
+ # Type alias for a logger factory function
6
+ type LoggerFactory = Callable[[Optional[str]], logging.Logger]
7
+
8
+ # Internal variable to hold a custom logger factory
9
+ _custom_logger_factory: Optional[LoggerFactory] = None
10
+
11
+ def register_logger_factory(factory: LoggerFactory):
12
+ """
13
+ Register a custom logger factory function. This function should accept a name (str or None)
14
+ and return a logger instance (e.g., structlog or standard logger).
15
+ """
16
+ global _custom_logger_factory
17
+ _custom_logger_factory = factory
18
+
19
+ def get_logger(name: str = None) -> logging.Logger:
20
+ """
21
+ Returns a logger configured to output to the console, or uses a registered custom logger factory.
22
+ If no name is provided, uses the root logger.
23
+ """
24
+ if _custom_logger_factory is not None:
25
+ return _custom_logger_factory(name)
26
+ logger = logging.getLogger(name)
27
+ if not logger.handlers:
28
+ handler = logging.StreamHandler(sys.stdout)
29
+ formatter = logging.Formatter(
30
+ fmt='%(asctime)s %(levelname)s [%(name)s] %(message)s',
31
+ datefmt='%Y-%m-%d %H:%M:%S'
32
+ )
33
+ handler.setFormatter(formatter)
34
+ logger.addHandler(handler)
35
+ logger.setLevel(logging.INFO)
36
+ logger.propagate = False
37
+ return logger
@@ -0,0 +1,126 @@
1
+ from enum import Enum
2
+ from typing import List, Dict, Any, Optional, Literal, TypedDict
3
+ from dataclasses import dataclass
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class ParameterType(str, Enum):
9
+ """Types of parameters supported by Opal tools."""
10
+
11
+ string = "string"
12
+ integer = "integer"
13
+ number = "number"
14
+ boolean = "boolean"
15
+ list = "array" # Changed to match main service expectation
16
+ dictionary = "object" # Standard JSON schema type
17
+
18
+
19
+ @dataclass
20
+ class Parameter:
21
+ """Parameter definition for an Opal tool."""
22
+
23
+ name: str
24
+ param_type: ParameterType
25
+ description: str
26
+ required: bool
27
+
28
+ def to_dict(self) -> Dict[str, Any]:
29
+ """Convert to dictionary for the discovery endpoint."""
30
+ return {"name": self.name, "type": self.param_type.value, "description": self.description, "required": self.required}
31
+
32
+
33
+ @dataclass
34
+ class AuthRequirement:
35
+ """Authentication requirements for an Opal tool."""
36
+
37
+ provider: str # e.g., "google", "microsoft"
38
+ scope_bundle: str # e.g., "calendar", "drive"
39
+ required: bool = True
40
+
41
+ def to_dict(self) -> Dict[str, Any]:
42
+ """Convert to dictionary for the discovery endpoint."""
43
+ return {"provider": self.provider, "scope_bundle": self.scope_bundle, "required": self.required}
44
+
45
+
46
+ class Credentials(TypedDict):
47
+ """AuthData credentials."""
48
+
49
+ access_token: str
50
+ org_sso_id: Optional[str]
51
+ customer_id: str
52
+ instance_id: str
53
+ product_sku: str
54
+
55
+
56
+ class AuthData(TypedDict):
57
+ """Authentication data for an Opal tool."""
58
+
59
+ provider: str
60
+ credentials: Credentials
61
+
62
+
63
+ class Environment(TypedDict):
64
+ """Execution environment for an Opal tool. Interactive will provide interaction islands, while headless will not."""
65
+
66
+ execution_mode: Literal["headless", "interactive"]
67
+
68
+
69
+ @dataclass
70
+ class Function:
71
+ """Function definition for an Opal tool."""
72
+
73
+ name: str
74
+ description: str
75
+ parameters: List[Parameter]
76
+ endpoint: str
77
+ auth_requirements: Optional[List[AuthRequirement]] = None
78
+ http_method: str = "POST"
79
+
80
+ def to_dict(self) -> Dict[str, Any]:
81
+ """Convert to dictionary for the discovery endpoint."""
82
+ result = {
83
+ "name": self.name,
84
+ "description": self.description,
85
+ "parameters": [p.to_dict() for p in self.parameters],
86
+ "endpoint": self.endpoint,
87
+ "http_method": self.http_method,
88
+ }
89
+
90
+ if self.auth_requirements:
91
+ result["auth_requirements"] = [auth.to_dict() for auth in self.auth_requirements]
92
+
93
+ return result
94
+
95
+
96
+ # Interaction island related classes
97
+ class IslandConfig(BaseModel):
98
+ class Field(BaseModel):
99
+ name: str
100
+ label: str
101
+ type: Literal["string", "boolean", "json"]
102
+ value: str = Field(default="")
103
+ hidden: bool = Field(default=False)
104
+ options: list[str] = Field(default=[])
105
+
106
+ class Action(BaseModel):
107
+ name: str
108
+ label: str
109
+ type: str
110
+ endpoint: str
111
+ operation: str = Field(default="create")
112
+
113
+ fields: list[Field]
114
+ actions: list[Action]
115
+
116
+
117
+ class IslandResponse(BaseModel):
118
+ class ResponseConfig(BaseModel):
119
+ islands: list[IslandConfig]
120
+
121
+ type: Literal["island"]
122
+ config: ResponseConfig
123
+
124
+ @classmethod
125
+ def create(cls, islands: list[IslandConfig]):
126
+ return cls(type="island", config=cls.ResponseConfig(islands=islands))