griptape-nodes 0.64.11__py3-none-any.whl → 0.65.0__py3-none-any.whl

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 (55) hide show
  1. griptape_nodes/app/app.py +25 -5
  2. griptape_nodes/cli/commands/init.py +65 -54
  3. griptape_nodes/cli/commands/libraries.py +92 -85
  4. griptape_nodes/cli/commands/self.py +121 -0
  5. griptape_nodes/common/node_executor.py +2142 -101
  6. griptape_nodes/exe_types/base_iterative_nodes.py +1004 -0
  7. griptape_nodes/exe_types/connections.py +114 -19
  8. griptape_nodes/exe_types/core_types.py +225 -7
  9. griptape_nodes/exe_types/flow.py +3 -3
  10. griptape_nodes/exe_types/node_types.py +681 -225
  11. griptape_nodes/exe_types/param_components/README.md +414 -0
  12. griptape_nodes/exe_types/param_components/api_key_provider_parameter.py +200 -0
  13. griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +2 -0
  14. griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +79 -5
  15. griptape_nodes/exe_types/param_types/parameter_button.py +443 -0
  16. griptape_nodes/machines/control_flow.py +77 -38
  17. griptape_nodes/machines/dag_builder.py +148 -70
  18. griptape_nodes/machines/parallel_resolution.py +61 -35
  19. griptape_nodes/machines/sequential_resolution.py +11 -113
  20. griptape_nodes/retained_mode/events/app_events.py +1 -0
  21. griptape_nodes/retained_mode/events/base_events.py +16 -13
  22. griptape_nodes/retained_mode/events/connection_events.py +3 -0
  23. griptape_nodes/retained_mode/events/execution_events.py +35 -0
  24. griptape_nodes/retained_mode/events/flow_events.py +15 -2
  25. griptape_nodes/retained_mode/events/library_events.py +347 -0
  26. griptape_nodes/retained_mode/events/node_events.py +48 -0
  27. griptape_nodes/retained_mode/events/os_events.py +86 -3
  28. griptape_nodes/retained_mode/events/project_events.py +15 -1
  29. griptape_nodes/retained_mode/events/workflow_events.py +48 -1
  30. griptape_nodes/retained_mode/griptape_nodes.py +6 -2
  31. griptape_nodes/retained_mode/managers/config_manager.py +10 -8
  32. griptape_nodes/retained_mode/managers/event_manager.py +168 -0
  33. griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +2 -0
  34. griptape_nodes/retained_mode/managers/fitness_problems/libraries/old_xdg_location_warning_problem.py +43 -0
  35. griptape_nodes/retained_mode/managers/flow_manager.py +664 -123
  36. griptape_nodes/retained_mode/managers/library_manager.py +1134 -138
  37. griptape_nodes/retained_mode/managers/model_manager.py +2 -3
  38. griptape_nodes/retained_mode/managers/node_manager.py +148 -25
  39. griptape_nodes/retained_mode/managers/object_manager.py +3 -1
  40. griptape_nodes/retained_mode/managers/operation_manager.py +3 -1
  41. griptape_nodes/retained_mode/managers/os_manager.py +1158 -122
  42. griptape_nodes/retained_mode/managers/secrets_manager.py +2 -3
  43. griptape_nodes/retained_mode/managers/settings.py +21 -1
  44. griptape_nodes/retained_mode/managers/sync_manager.py +2 -3
  45. griptape_nodes/retained_mode/managers/workflow_manager.py +358 -104
  46. griptape_nodes/retained_mode/retained_mode.py +3 -3
  47. griptape_nodes/traits/button.py +44 -2
  48. griptape_nodes/traits/file_system_picker.py +2 -2
  49. griptape_nodes/utils/file_utils.py +101 -0
  50. griptape_nodes/utils/git_utils.py +1226 -0
  51. griptape_nodes/utils/library_utils.py +122 -0
  52. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.0.dist-info}/METADATA +2 -1
  53. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.0.dist-info}/RECORD +55 -47
  54. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.0.dist-info}/WHEEL +1 -1
  55. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,414 @@
1
+ # Using ApiKeyProviderParameter Component
2
+
3
+ The `ApiKeyProviderParameter` component provides a reusable way to add user-provided API key support to nodes that use the Griptape Cloud proxy API. When users provide their own API key, it's automatically forwarded to the provider via the `X-GTC-PROXY-AUTH-API-KEY` header.
4
+
5
+ ## Overview
6
+
7
+ This component automatically adds:
8
+
9
+ - A toggle parameter to enable user-provided API key (always uses proxy API)
10
+ - A button on the toggle to open secrets settings (filtered to the relevant API key)
11
+ - An informational message that shows/hides based on whether the user API key is set
12
+ - Helper methods to validate and retrieve API keys
13
+
14
+ **Key Simplification:** All requests go through the Griptape Cloud proxy API. When a user provides their own API key, it's simply forwarded via the `X-GTC-PROXY-AUTH-API-KEY` header - no need to switch between different API endpoints or configurations!
15
+
16
+ ## Example: Flux Image Generation Node
17
+
18
+ The `FluxImageGeneration` node demonstrates a complete implementation. Here's how it works:
19
+
20
+ ### Step 1: Import the Component
21
+
22
+ Add this import at the top of your node file:
23
+
24
+ ```python
25
+ from griptape_nodes.exe_types.param_components.api_key_provider_parameter import ApiKeyProviderParameter
26
+ ```
27
+
28
+ **File location:** Add this to your imports section (usually near the top with other imports)
29
+
30
+ ### Step 2: Define API Key Configuration Constants
31
+
32
+ Define class-level constants for your API key information. These will be used when initializing the component:
33
+
34
+ ```python
35
+ class YourNode(SuccessFailureNode):
36
+ # API key configuration
37
+ USER_API_KEY_NAME = "YOUR_API_KEY_NAME" # e.g., "BFL_API_KEY", "OPENAI_API_KEY"
38
+ USER_API_KEY_URL = "https://example.com/api/keys" # URL where users can get their API key
39
+ USER_API_KEY_PROVIDER_NAME = "Your Provider Name" # e.g., "BlackForest Labs", "OpenAI"
40
+ ```
41
+
42
+ **File location:** Add these as class attributes, typically near the top of your class definition (after `SERVICE_NAME` and `API_KEY_NAME` if you have them)
43
+
44
+ ### Step 3: Initialize the Component in `__init__`
45
+
46
+ In your node's `__init__` method, create and initialize the component:
47
+
48
+ ```python
49
+ def __init__(self, **kwargs: Any) -> None:
50
+ super().__init__(**kwargs)
51
+ # ... your other initialization code ...
52
+
53
+ # Add API key provider component
54
+ self._api_key_provider = ApiKeyProviderParameter(
55
+ node=self,
56
+ api_key_name=self.USER_API_KEY_NAME,
57
+ provider_name=self.USER_API_KEY_PROVIDER_NAME,
58
+ api_key_url=self.USER_API_KEY_URL,
59
+ )
60
+ self._api_key_provider.add_parameters()
61
+
62
+ # ... rest of your initialization ...
63
+ ```
64
+
65
+ **File location:** Add this code in your `__init__` method, typically after any base class initialization and before adding other parameters
66
+
67
+ ### Step 4: Override `after_value_set` Method
68
+
69
+ Add this method to handle visibility updates when the API key provider toggle changes:
70
+
71
+ ```python
72
+ def after_value_set(self, parameter: Parameter, value: Any) -> None:
73
+ self._api_key_provider.after_value_set(parameter, value)
74
+ return super().after_value_set(parameter, value)
75
+ ```
76
+
77
+ **File location:** Add this method to your node class, typically near other lifecycle methods like `_process` or `_validate_api_key`
78
+
79
+ ### Step 5: Use the Component in Your Processing Logic
80
+
81
+ In your `_process` method (or wherever you need to get the API key), use the component's validation method:
82
+
83
+ ```python
84
+ def _process(self) -> None:
85
+ # ... your setup code ...
86
+
87
+ try:
88
+ validation_result = self._api_key_provider.validate_api_key()
89
+ except ValueError as e:
90
+ self._set_status_results(was_successful=False, result_details=str(e))
91
+ self._handle_failure_exception(e)
92
+ return
93
+
94
+ # Build headers: always use proxy API key, optionally add user API key
95
+ headers = {
96
+ "Authorization": f"Bearer {validation_result.proxy_api_key}",
97
+ "Content-Type": "application/json",
98
+ }
99
+ if validation_result.user_api_key:
100
+ headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
101
+
102
+ # Use headers in your API calls - always goes through proxy API
103
+ # ... rest of your processing ...
104
+ ```
105
+
106
+ **File location:** Add this at the beginning of your `_process` method, before making any API calls
107
+
108
+ ### Step 6: (Optional) Create a Helper Method
109
+
110
+ If you had an existing `_validate_api_key` method, you can simplify it to delegate to the component:
111
+
112
+ ```python
113
+ def _validate_api_key(self) -> ApiKeyValidationResult:
114
+ """Validate and return API keys for proxy API usage.
115
+
116
+ Returns:
117
+ ApiKeyValidationResult: Named tuple containing proxy_api_key and optional user_api_key
118
+ """
119
+ return self._api_key_provider.validate_api_key()
120
+ ```
121
+
122
+ **File location:** Replace your existing `_validate_api_key` method with this, or add it if you don't have one. Don't forget to import `ApiKeyValidationResult`:
123
+
124
+ ```python
125
+ from griptape_nodes.exe_types.param_components.api_key_provider_parameter import (
126
+ ApiKeyProviderParameter,
127
+ ApiKeyValidationResult,
128
+ )
129
+ ```
130
+
131
+ ## Complete Example
132
+
133
+ Here's a complete minimal example showing all the pieces together:
134
+
135
+ ```python
136
+ from __future__ import annotations
137
+
138
+ from typing import Any
139
+
140
+ from griptape_nodes.exe_types.core_types import Parameter, ParameterMode
141
+ from griptape_nodes.exe_types.node_types import SuccessFailureNode
142
+ from griptape_nodes.exe_types.param_components.api_key_provider_parameter import ApiKeyProviderParameter
143
+
144
+
145
+ class ExampleNode(SuccessFailureNode):
146
+ """Example node with API key provider switching."""
147
+
148
+ # API key configuration
149
+ USER_API_KEY_NAME = "EXAMPLE_API_KEY"
150
+ USER_API_KEY_URL = "https://example.com/api/keys"
151
+ USER_API_KEY_PROVIDER_NAME = "Example Provider"
152
+
153
+ def __init__(self, **kwargs: Any) -> None:
154
+ super().__init__(**kwargs)
155
+ self.category = "API Nodes"
156
+ self.description = "Example node with API key switching"
157
+
158
+ # Add API key provider component
159
+ self._api_key_provider = ApiKeyProviderParameter(
160
+ node=self,
161
+ api_key_name=self.USER_API_KEY_NAME,
162
+ provider_name=self.USER_API_KEY_PROVIDER_NAME,
163
+ api_key_url=self.USER_API_KEY_URL,
164
+ )
165
+ self._api_key_provider.add_parameters()
166
+
167
+ # Add your other parameters here
168
+ self.add_parameter(
169
+ Parameter(
170
+ name="input_text",
171
+ type="str",
172
+ tooltip="Input text",
173
+ )
174
+ )
175
+
176
+ def after_value_set(self, parameter: Parameter, value: Any) -> None:
177
+ self._api_key_provider.after_value_set(parameter, value)
178
+ return super().after_value_set(parameter, value)
179
+
180
+ def _process(self) -> None:
181
+ # Get API keys for proxy API usage
182
+ try:
183
+ validation_result = self._api_key_provider.validate_api_key()
184
+ except ValueError as e:
185
+ self._set_status_results(was_successful=False, result_details=str(e))
186
+ self._handle_failure_exception(e)
187
+ return
188
+
189
+ # Build headers: always use proxy API key, optionally add user API key
190
+ headers = {
191
+ "Authorization": f"Bearer {validation_result.proxy_api_key}",
192
+ "Content-Type": "application/json",
193
+ }
194
+ if validation_result.user_api_key:
195
+ headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
196
+
197
+ # Always use proxy API - user's key is forwarded via header if provided
198
+ input_text = self.get_parameter_value("input_text")
199
+ result = self._call_proxy_api(headers, input_text)
200
+
201
+ # Process result...
202
+ self._set_status_results(was_successful=True, result_details="Success")
203
+
204
+ def _call_proxy_api(self, headers: dict[str, str], input_text: str) -> dict:
205
+ # Your proxy API call logic here - always uses proxy endpoint
206
+ # User's API key is automatically forwarded if provided
207
+ pass
208
+ ```
209
+
210
+ ## Component API Reference
211
+
212
+ ### Initialization Parameters
213
+
214
+ When creating an `ApiKeyProviderParameter` instance, you must provide:
215
+
216
+ - **`node`** (BaseNode): The node instance to add parameters to
217
+ - **`api_key_name`** (str): The name of the user's API key secret (e.g., `"BFL_API_KEY"`)
218
+ - **`provider_name`** (str): The display name of the API provider (e.g., `"BlackForest Labs"`)
219
+ - **`api_key_url`** (str): The URL where users can obtain their API key
220
+
221
+ Optional parameters:
222
+
223
+ - **`parameter_name`** (str, default: `"api_key_provider"`): Name for the toggle parameter
224
+ - **`proxy_api_key_name`** (str, default: `"GT_CLOUD_API_KEY"`): Name of the proxy API key secret
225
+ - **`on_label`** (str, default: `"Customer"`): Label when user API is enabled
226
+ - **`off_label`** (str, default: `"Griptape"`): Label when proxy API is enabled
227
+
228
+ ### Available Methods
229
+
230
+ #### `add_parameters()`
231
+
232
+ Adds the toggle parameter and message to the node. Call this once in `__init__`.
233
+
234
+ #### `after_value_set(parameter: Parameter, value: Any)`
235
+
236
+ Handles visibility updates when the toggle changes. Call this from your node's `after_value_set` method.
237
+
238
+ #### `validate_api_key() -> ApiKeyValidationResult`
239
+
240
+ Validates and returns API keys for proxy API usage. Returns a `ApiKeyValidationResult` named tuple with:
241
+
242
+ - `proxy_api_key` (str): The Griptape Cloud API key for Authorization header (always required)
243
+ - `user_api_key` (str | None): Optional user-provided API key for X-GTC-PROXY-AUTH-API-KEY header (None if not enabled)
244
+
245
+ **Raises:** `ValueError` if the proxy API key is not set, or if user API is enabled but user key is not set.
246
+
247
+ #### `is_user_api_enabled() -> bool`
248
+
249
+ Checks if user API is currently enabled.
250
+
251
+ #### `get_api_key(use_user_api: bool) -> str`
252
+
253
+ Gets the API key for the specified mode.
254
+
255
+ **Raises:** `ValueError` if the API key is not set.
256
+
257
+ #### `check_api_key_set(api_key: str) -> bool`
258
+
259
+ Checks if an API key exists and is not empty.
260
+
261
+ ## Migration Guide: Converting Existing Nodes
262
+
263
+ If you have an existing node that manually handles API key switching, here's how to migrate:
264
+
265
+ ### Before (Manual Implementation)
266
+
267
+ ```python
268
+ def __init__(self, **kwargs: Any) -> None:
269
+ super().__init__(**kwargs)
270
+
271
+ # Manual toggle parameter
272
+ self.add_parameter(
273
+ ParameterBool(
274
+ name="api_key_provider",
275
+ default_value=False,
276
+ # ... lots of configuration ...
277
+ )
278
+ )
279
+
280
+ # Manual message
281
+ self.add_node_element(
282
+ ParameterMessage(
283
+ name="set_api_key",
284
+ # ... lots of configuration ...
285
+ )
286
+ )
287
+
288
+ def after_value_set(self, parameter: Parameter, value: Any) -> None:
289
+ if parameter.name == "api_key_provider":
290
+ # Manual visibility logic
291
+ if value:
292
+ if not self.check_api_key_set(self.USER_API_KEY_NAME):
293
+ self.show_message_by_name("set_api_key")
294
+ else:
295
+ self.hide_message_by_name("set_api_key")
296
+ return super().after_value_set(parameter, value)
297
+
298
+ def _validate_api_key(self) -> tuple[str, bool]:
299
+ use_user_api = self.get_parameter_value("api_key_provider") or False
300
+ api_key_name = "USER_KEY" if use_user_api else "GT_CLOUD_API_KEY"
301
+ api_key = GriptapeNodes.SecretsManager().get_secret(api_key_name)
302
+ # ... validation logic ...
303
+ return api_key, use_user_api
304
+ ```
305
+
306
+ ### After (Using Component)
307
+
308
+ ```python
309
+ from griptape_nodes.exe_types.param_components.api_key_provider_parameter import (
310
+ ApiKeyProviderParameter,
311
+ ApiKeyValidationResult,
312
+ )
313
+
314
+ def __init__(self, **kwargs: Any) -> None:
315
+ super().__init__(**kwargs)
316
+
317
+ # Component handles everything
318
+ self._api_key_provider = ApiKeyProviderParameter(
319
+ node=self,
320
+ api_key_name=self.USER_API_KEY_NAME,
321
+ provider_name=self.USER_API_KEY_PROVIDER_NAME,
322
+ api_key_url=self.USER_API_KEY_URL,
323
+ )
324
+ self._api_key_provider.add_parameters()
325
+
326
+ def after_value_set(self, parameter: Parameter, value: Any) -> None:
327
+ self._api_key_provider.after_value_set(parameter, value)
328
+ return super().after_value_set(parameter, value)
329
+
330
+ def _validate_api_key(self) -> ApiKeyValidationResult:
331
+ return self._api_key_provider.validate_api_key()
332
+
333
+ def _process(self) -> None:
334
+ validation_result = self._validate_api_key()
335
+
336
+ # Build headers - always use proxy API
337
+ headers = {
338
+ "Authorization": f"Bearer {validation_result.proxy_api_key}",
339
+ "Content-Type": "application/json",
340
+ }
341
+ if validation_result.user_api_key:
342
+ headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
343
+
344
+ # Always use proxy endpoint
345
+ # ... make API call with headers ...
346
+ ```
347
+
348
+ ## Common Patterns
349
+
350
+ ### Pattern 1: Simple Proxy API Usage
351
+
352
+ All requests go through the proxy API. User's API key is automatically forwarded if provided:
353
+
354
+ ```python
355
+ validation_result = self._api_key_provider.validate_api_key()
356
+
357
+ headers = {
358
+ "Authorization": f"Bearer {validation_result.proxy_api_key}",
359
+ "Content-Type": "application/json",
360
+ }
361
+ if validation_result.user_api_key:
362
+ headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
363
+
364
+ # Always use proxy endpoint
365
+ url = urljoin(self._proxy_base, "models/your-model")
366
+ response = await client.post(url, json=payload, headers=headers)
367
+ ```
368
+
369
+ ### Pattern 2: Logging User API Key Usage
370
+
371
+ You can log when a user-provided API key is being used:
372
+
373
+ ```python
374
+ validation_result = self._api_key_provider.validate_api_key()
375
+
376
+ headers = {
377
+ "Authorization": f"Bearer {validation_result.proxy_api_key}",
378
+ "Content-Type": "application/json",
379
+ }
380
+ if validation_result.user_api_key:
381
+ headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
382
+ self._log("Using user-provided API key via proxy")
383
+ else:
384
+ self._log("Using default proxy API key")
385
+ ```
386
+
387
+ ## Troubleshooting
388
+
389
+ ### Message Not Showing/Hiding
390
+
391
+ **Problem:** The message doesn't appear when the toggle is switched.
392
+
393
+ **Solution:** Make sure you're calling `self._api_key_provider.after_value_set(parameter, value)` in your node's `after_value_set` method and that it's called before `super().after_value_set()`.
394
+
395
+ ### API Key Not Found Error
396
+
397
+ **Problem:** Getting `ValueError` about missing API key even though it's set.
398
+
399
+ **Solution:**
400
+
401
+ 1. Verify the API key name matches exactly (case-sensitive)
402
+ 1. Check that the secret is set in Settings → Secrets
403
+ 1. Ensure you're using the correct `api_key_name` when initializing the component
404
+
405
+ ### Toggle Not Appearing
406
+
407
+ **Problem:** The API key provider toggle doesn't appear in the UI.
408
+
409
+ **Solution:** Make sure you're calling `self._api_key_provider.add_parameters()` in your `__init__` method.
410
+
411
+ ## See Also
412
+
413
+ - [Flux Image Generation Node](../../libraries/griptape_nodes_library/griptape_nodes_library/image/flux_image_generation.py) - Complete working example
414
+ - [ApiKeyProviderParameter Component](../../src/griptape_nodes/exe_types/param_components/api_key_provider_parameter.py) - Component source code
@@ -0,0 +1,200 @@
1
+ """API Key Provider parameter component for reusable API key switching functionality."""
2
+
3
+ from typing import Any, NamedTuple
4
+
5
+ from griptape_nodes.exe_types.core_types import Parameter, ParameterMessage
6
+ from griptape_nodes.exe_types.node_types import BaseNode
7
+ from griptape_nodes.exe_types.param_types.parameter_bool import ParameterBool
8
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
9
+ from griptape_nodes.traits.button import Button
10
+
11
+
12
+ class ApiKeyValidationResult(NamedTuple):
13
+ """Result from API key validation.
14
+
15
+ Attributes:
16
+ proxy_api_key: The Griptape Cloud API key for Authorization header (always required)
17
+ user_api_key: Optional user-provided API key for X-GTC-PROXY-AUTH-API-KEY header (None if not enabled)
18
+ """
19
+
20
+ proxy_api_key: str
21
+ user_api_key: str | None
22
+
23
+
24
+ class ApiKeyProviderParameter:
25
+ """Reusable component for adding API key provider switching functionality to nodes.
26
+
27
+ This component adds:
28
+ - A toggle parameter to enable user-provided API key (always uses proxy API)
29
+ - A button on the toggle to open secrets settings
30
+ - A message that shows/hides based on whether the user API key is set
31
+ - Helper methods to validate and retrieve API keys
32
+
33
+ Example usage:
34
+ ```python
35
+ api_key_provider = ApiKeyProviderParameter(
36
+ node=self,
37
+ api_key_name="BFL_API_KEY",
38
+ provider_name="BlackForest Labs",
39
+ api_key_url="https://dashboard.bfl.ai/api/keys",
40
+ )
41
+ api_key_provider.add_parameters()
42
+
43
+ # In your node's _process method:
44
+ validation_result = api_key_provider.validate_api_key()
45
+ headers = {
46
+ "Authorization": f"Bearer {validation_result.proxy_api_key}",
47
+ "Content-Type": "application/json",
48
+ }
49
+ if validation_result.user_api_key:
50
+ headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
51
+ ```
52
+
53
+ Args:
54
+ node: The BaseNode instance to add parameters to
55
+ api_key_name: The name of the user's API key secret (e.g., "BFL_API_KEY")
56
+ provider_name: The display name of the API provider (e.g., "BlackForest Labs")
57
+ api_key_url: The URL where users can obtain their API key
58
+ parameter_name: Optional name for the toggle parameter (default: "api_key_provider")
59
+ proxy_api_key_name: Optional name for the proxy API key (default: "GT_CLOUD_API_KEY")
60
+ on_label: Label when user API is enabled (default: "Customer")
61
+ off_label: Label when proxy API is enabled (default: "Griptape")
62
+ """
63
+
64
+ def __init__( # noqa: PLR0913
65
+ self,
66
+ node: BaseNode,
67
+ api_key_name: str,
68
+ provider_name: str,
69
+ api_key_url: str,
70
+ *,
71
+ parameter_name: str = "api_key_provider",
72
+ proxy_api_key_name: str = "GT_CLOUD_API_KEY",
73
+ on_label: str = "Customer",
74
+ off_label: str = "Griptape",
75
+ ) -> None:
76
+ self._node = node
77
+ self.api_key_name = api_key_name
78
+ self.provider_name = provider_name
79
+ self.api_key_url = api_key_url
80
+ self.parameter_name = parameter_name
81
+ self.proxy_api_key_name = proxy_api_key_name
82
+ self.on_label = on_label
83
+ self.off_label = off_label
84
+ self.message_name = f"{parameter_name}_message"
85
+
86
+ def add_parameters(self) -> None:
87
+ """Add the API key provider toggle and message to the node."""
88
+ self._node.add_parameter(
89
+ ParameterBool(
90
+ name=self.parameter_name,
91
+ default_value=False,
92
+ tooltip="Use customer API key instead of the default one",
93
+ allow_input=False,
94
+ allow_output=False,
95
+ on_label=self.on_label,
96
+ off_label=self.off_label,
97
+ ui_options={"display_name": "API Key Provider"},
98
+ traits={
99
+ Button(
100
+ icon="key",
101
+ tooltip="Open secrets settings",
102
+ button_link=f"#settings-secrets?filter={self.api_key_name}",
103
+ )
104
+ },
105
+ )
106
+ )
107
+ self._node.add_node_element(
108
+ ParameterMessage(
109
+ name=self.message_name,
110
+ variant="info",
111
+ title=f"{self.provider_name} API key",
112
+ value=(
113
+ f"To use your own {self.provider_name} API key, visit:\n{self.api_key_url}\n"
114
+ f"to obtain a valid key.\n\n"
115
+ f"Then set {self.api_key_name} in "
116
+ f"[Settings → Secrets](#settings-secrets?filter={self.api_key_name})."
117
+ ),
118
+ button_link=f"#settings-secrets?filter={self.api_key_name}",
119
+ button_text="Open Secrets",
120
+ button_icon="key",
121
+ markdown=True,
122
+ hide=True,
123
+ )
124
+ )
125
+
126
+ def after_value_set(self, parameter: Parameter, value: Any) -> None:
127
+ """Handle visibility of the API key message when the provider toggle changes."""
128
+ if parameter.name != self.parameter_name:
129
+ return
130
+
131
+ if value:
132
+ api_key_value = self.check_api_key_set(self.api_key_name)
133
+ if not api_key_value:
134
+ self._node.show_message_by_name(self.message_name)
135
+ else:
136
+ self._node.hide_message_by_name(self.message_name)
137
+
138
+ def is_user_api_enabled(self) -> bool:
139
+ """Check if user API key is currently enabled.
140
+
141
+ Returns:
142
+ bool: True if user API key is enabled, False otherwise
143
+ """
144
+ return bool(self._node.get_parameter_value(self.parameter_name) or False)
145
+
146
+ def check_api_key_set(self, api_key: str) -> bool:
147
+ """Check if an API key exists and is not empty.
148
+
149
+ Args:
150
+ api_key: The name of the API key secret to check
151
+
152
+ Returns:
153
+ bool: True if the API key exists and is not empty, False otherwise
154
+ """
155
+ api_key_value = GriptapeNodes.SecretsManager().get_secret(api_key)
156
+ if api_key_value is None:
157
+ return False
158
+ if isinstance(api_key_value, str):
159
+ return bool(api_key_value.strip())
160
+ return bool(api_key_value)
161
+
162
+ def get_api_key(self, *, use_user_api: bool) -> str:
163
+ """Get the API key for the specified mode.
164
+
165
+ Args:
166
+ use_user_api: True to get user API key, False to get proxy API key
167
+
168
+ Returns:
169
+ str: The API key value
170
+
171
+ Raises:
172
+ ValueError: If the API key is not set
173
+ """
174
+ api_key_name = self.api_key_name if use_user_api else self.proxy_api_key_name
175
+ api_key = GriptapeNodes.SecretsManager().get_secret(api_key_name)
176
+ if not api_key:
177
+ msg = f"{self._node.name} is missing {api_key_name}. Ensure it's set in the environment/config."
178
+ raise ValueError(msg)
179
+ return api_key
180
+
181
+ def validate_api_key(self) -> ApiKeyValidationResult:
182
+ """Validate and return API keys for proxy API usage.
183
+
184
+ Always returns proxy API key. Optionally returns user API key if enabled.
185
+
186
+ Returns:
187
+ ApiKeyValidationResult: Named tuple containing proxy_api_key and optional user_api_key
188
+
189
+ Raises:
190
+ ValueError: If the proxy API key is not set, or if user API is enabled but user key is not set
191
+ """
192
+ # Always need proxy API key
193
+ proxy_api_key = self.get_api_key(use_user_api=False)
194
+
195
+ # Optionally get user API key if enabled
196
+ user_api_key = None
197
+ if self.is_user_api_enabled():
198
+ user_api_key = self.get_api_key(use_user_api=True)
199
+
200
+ return ApiKeyValidationResult(proxy_api_key=proxy_api_key, user_api_key=user_api_key)
@@ -41,6 +41,8 @@ class HuggingFaceModelParameter(ABC):
41
41
  return
42
42
 
43
43
  choices = self.get_choices()
44
+ if not choices:
45
+ return
44
46
 
45
47
  current_value = self._node.get_parameter_value(self._parameter_name)
46
48
  if current_value in choices: