22
22
get_org_id ,
23
23
query_graphql_organizations ,
24
24
)
25
- from src .api .tools .types import Tool
26
- from src .api .tools .types import WorkspaceTarget
25
+ from src .api .tools .types import Tool , WorkspaceTarget
27
26
from src .utils .uuid_validation import validate_workspace_id , validate_uuid_string
27
+ from src .utils .elicitation import try_elicitation , ElicitationError
28
28
from src .logger import get_logger
29
29
30
30
# Set up logger for this module
@@ -712,7 +712,7 @@ def complete_database_migration(
712
712
713
713
Use Cases:
714
714
1. Apply Migration: Set apply_to_production=True to execute the migration on the production database
715
- 2. Discard Migration: Set apply_to_production=False to cleanup without applying changes
715
+ 2. Discard Migration: Set apply_to_production=False to cleanup without applying
716
716
717
717
Important Notes:
718
718
- This tool must be called after 'prepare_database_migration' to properly cleanup the branch database
@@ -1798,7 +1798,7 @@ async def choose_organization(ctx: Context) -> dict:
1798
1798
if len (organizations ) == 1 :
1799
1799
selected_org = organizations [0 ]
1800
1800
else :
1801
-
1801
+ # For multiple organizations, use elicitation to let the user choose
1802
1802
class OrganizationChoice (BaseModel ):
1803
1803
"""Schema for collecting organization selection."""
1804
1804
@@ -1807,56 +1807,54 @@ class OrganizationChoice(BaseModel):
1807
1807
choices = [org ["orgID" ] for org in organizations ],
1808
1808
)
1809
1809
1810
- # For multiple organizations, use elicitation to let the user choose
1811
1810
# Format the organization list for display
1812
1811
org_list = "\n " .join (
1813
1812
[f"- ID: { org ['orgID' ]} ({ org ['name' ]} )" for org in organizations ]
1814
1813
)
1815
1814
1816
- try :
1817
- result = await ctx . elicit (
1818
- message = f"""📋 **Available SingleStore Organizations:**\n \n { org_list } \n \n Please select an organization to use: """ ,
1819
- schema = OrganizationChoice ,
1820
- )
1815
+ elicit_result , error = await try_elicitation (
1816
+ ctx = ctx ,
1817
+ message = f"""**Available SingleStore Organizations:**\n \n { org_list } \n \n Please select the organization ID you want to use. """ ,
1818
+ schema = OrganizationChoice ,
1819
+ )
1821
1820
1822
- if result .action == "accept" and result .data :
1823
- # Parse the selection to get the org ID
1824
- selected = result .data .organizationID
1825
- # Extract orgID from the selection string
1826
- org_id = selected
1827
- # Find the matching organization
1828
- selected_org = next (
1829
- org for org in organizations if org ["orgID" ] == org_id
1830
- )
1831
- else :
1832
- return {
1833
- "status" : "cancelled" ,
1834
- "message" : "Organization selection was cancelled" ,
1835
- "data" : {
1836
- "organizations" : organizations ,
1837
- "count" : len (organizations ),
1838
- },
1839
- }
1840
-
1841
- except Exception as elicit_error :
1842
- logger .error (
1843
- f"Error during organization elicitation: { str (elicit_error )} "
1821
+ if error == ElicitationError .NOT_SUPPORTED :
1822
+ # Client doesn't support elicitation, return list and wait for next prompt
1823
+ await ctx .info (
1824
+ "This client doesn't support interactive organization selection."
1825
+ " Please wait for the next prompt to provide the organization ID and call set_organization tool."
1844
1826
)
1845
1827
return {
1846
- "status" : "error" ,
1847
- "message" : f"Failed to process organization selection: { str (elicit_error )} " ,
1848
- "error_code" : "ELICITATION_FAILED" ,
1849
- "error_details" : {"exception_type" : type (elicit_error ).__name__ },
1828
+ "status" : "pending_selection" ,
1829
+ "message" : "Please provide the organization ID in your next request" ,
1830
+ "data" : {
1831
+ "organizations" : organizations ,
1832
+ "count" : len (organizations ),
1833
+ },
1834
+ }
1835
+
1836
+ if elicit_result .status == "success" and elicit_result .data :
1837
+ # Find the matching organization from the selection
1838
+ selected_org_id = elicit_result .data .organizationID
1839
+ if selected_org_id :
1840
+ for org in organizations :
1841
+ if org ["orgID" ] == selected_org_id :
1842
+ selected_org = org
1843
+ break
1844
+ elif elicit_result .status == "cancelled" :
1845
+ return {
1846
+ "status" : "cancelled" ,
1847
+ "message" : "Organization selection was cancelled" ,
1848
+ "data" : {
1849
+ "organizations" : organizations ,
1850
+ "count" : len (organizations ),
1851
+ },
1850
1852
}
1851
1853
1852
1854
# Set the selected organization in settings
1853
1855
if selected_org :
1854
- if hasattr (settings , "org_id" ):
1855
- settings .org_id = selected_org ["orgID" ]
1856
- else :
1857
- setattr (settings , "org_id" , selected_org ["orgID" ])
1856
+ settings .org_id = selected_org ["orgID" ]
1858
1857
1859
- # Return consistent response regardless of selection method
1860
1858
return {
1861
1859
"status" : "success" ,
1862
1860
"message" : f"Successfully selected organization: { selected_org ['name' ]} (ID: { selected_org ['orgID' ]} )" ,
@@ -1870,6 +1868,15 @@ class OrganizationChoice(BaseModel):
1870
1868
"user_id" : user_id ,
1871
1869
},
1872
1870
}
1871
+ else :
1872
+ return {
1873
+ "status" : "error" ,
1874
+ "message" : "No organization was selected" ,
1875
+ "data" : {
1876
+ "organizations" : organizations ,
1877
+ "count" : len (organizations ),
1878
+ },
1879
+ }
1873
1880
1874
1881
except Exception as e :
1875
1882
logger .error (f"Error retrieving organizations: { str (e )} " )
@@ -2136,6 +2143,91 @@ async def terminate_virtual_workspace(
2136
2143
}
2137
2144
2138
2145
2146
+ async def set_organization (ctx : Context , organization_id : str ) -> dict :
2147
+ """
2148
+ Set the current organization after retrieving the list from choose_organization.
2149
+ This tool should only be used when the client doesn't support elicitation.
2150
+
2151
+ Args:
2152
+ organization_id: The ID of the organization to select, as obtained from the
2153
+ choose_organization tool's response.
2154
+
2155
+ Returns:
2156
+ Dictionary with selected organization details
2157
+
2158
+ Important:
2159
+ - This tool should only be called after choose_organization returns a 'pending_selection' status
2160
+ - The organization_id must be one of the IDs returned by choose_organization
2161
+
2162
+ Example flow:
2163
+ 1. Call choose_organization first
2164
+ 2. If it returns 'pending_selection', get the organization ID from the list
2165
+ 3. Call set_organization with the chosen ID
2166
+ """
2167
+ settings = config .get_settings ()
2168
+ user_id = config .get_user_id ()
2169
+ # Track tool call event
2170
+ settings .analytics_manager .track_event (
2171
+ user_id ,
2172
+ "tool_calling" ,
2173
+ {"name" : "set_organization" , "organization_id" : organization_id },
2174
+ )
2175
+
2176
+ logger .debug (f"Setting organization ID: { organization_id } " )
2177
+
2178
+ try :
2179
+ # Get the list of organizations to validate the selection
2180
+ organizations = query_graphql_organizations ()
2181
+
2182
+ # Find the selected organization
2183
+ selected_org = next (
2184
+ (org for org in organizations if org ["orgID" ] == organization_id ), None
2185
+ )
2186
+
2187
+ if not selected_org :
2188
+ available_orgs = ", " .join (org ["orgID" ] for org in organizations )
2189
+ return {
2190
+ "status" : "error" ,
2191
+ "message" : f"Organization ID '{ organization_id } ' not found. Available IDs: { available_orgs } " ,
2192
+ "error_code" : "INVALID_ORGANIZATION" ,
2193
+ "error_details" : {
2194
+ "provided_id" : organization_id ,
2195
+ "available_ids" : [org ["orgID" ] for org in organizations ],
2196
+ },
2197
+ }
2198
+
2199
+ # Set the selected organization in settings
2200
+ if hasattr (settings , "org_id" ):
2201
+ settings .org_id = selected_org ["orgID" ]
2202
+ else :
2203
+ setattr (settings , "org_id" , selected_org ["orgID" ])
2204
+
2205
+ await ctx .info (
2206
+ f"Organization set to: { selected_org ['name' ]} (ID: { selected_org ['orgID' ]} )"
2207
+ )
2208
+
2209
+ return {
2210
+ "status" : "success" ,
2211
+ "message" : f"Successfully set organization to: { selected_org ['name' ]} (ID: { selected_org ['orgID' ]} )" ,
2212
+ "data" : {
2213
+ "organization" : selected_org ,
2214
+ },
2215
+ "metadata" : {
2216
+ "timestamp" : datetime .now (timezone .utc ).isoformat (),
2217
+ "user_id" : user_id ,
2218
+ },
2219
+ }
2220
+
2221
+ except Exception as e :
2222
+ logger .error (f"Error setting organization: { str (e )} " )
2223
+ return {
2224
+ "status" : "error" ,
2225
+ "message" : f"Failed to set organization: { str (e )} " ,
2226
+ "error_code" : "ORGANIZATION_SET_FAILED" ,
2227
+ "error_details" : {"exception_type" : type (e ).__name__ },
2228
+ }
2229
+
2230
+
2139
2231
tools_definition = [
2140
2232
{"func" : get_user_id },
2141
2233
{"func" : workspace_groups_info },
@@ -2154,6 +2246,7 @@ async def terminate_virtual_workspace(
2154
2246
{"func" : list_job_executions , "internal" : True },
2155
2247
{"func" : get_notebook_path , "internal" : True },
2156
2248
{"func" : choose_organization },
2249
+ {"func" : set_organization },
2157
2250
# These tools are under development and not yet available for public use
2158
2251
{"func" : prepare_database_migration , "internal" : True },
2159
2252
{"func" : complete_database_migration , "internal" : True },
0 commit comments