vss-cli 2025.8.0.dev1__tar.gz → 2025.9.0.dev2__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.
- {vss_cli-2025.8.0.dev1/vss_cli.egg-info → vss_cli-2025.9.0.dev2}/PKG-INFO +1 -1
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/config.py +205 -36
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/const.py +2 -2
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/clib.yaml +1 -1
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/clone.yaml +1 -1
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/shell.yaml +1 -1
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/template.yaml +1 -1
- vss_cli-2025.9.0.dev2/vss_cli/plugins/assist.py +156 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2/vss_cli.egg-info}/PKG-INFO +1 -1
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli.egg-info/top_level.txt +1 -0
- vss_cli-2025.8.0.dev1/vss_cli/plugins/assist.py +0 -76
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/LICENSE +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/README.md +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/docs/conf.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/pyproject.toml +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/scripts/update_homebrew_formula.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/setup.cfg +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/tests/test_vss_cli.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/__init__.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/autocompletion.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/cli.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/config.yaml +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/image.yaml +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data_types.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/exceptions.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/hcio.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/helper.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/__init__.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/account.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/completion.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/__init__.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/callbacks.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/contentlib.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/domain.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/floppy.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/folder.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/helper.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/image.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/inventory.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/iso.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/net.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/os.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/rel_args.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/rel_opts.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/template.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/vm.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/vmdks.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/configure.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/key.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/mcp.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/message.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/misc.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/ovf.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/plugins.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/raw.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/__init__.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/change.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/export.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/folder.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/image.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/inventory.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/new.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/restore.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/retirement.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/snapshot.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/vmdk.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/service.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/shell.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/status.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/stor.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/token.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/upgrade.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/vpn.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/rel_opts.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/sstatus.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/utils/__init__.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/utils/emoji.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/utils/mcp.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/utils/threading.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/validators.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/vss.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/vssconst.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/yaml.py +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli.egg-info/SOURCES.txt +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli.egg-info/dependency_links.txt +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli.egg-info/entry_points.txt +0 -0
- {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli.egg-info/requires.txt +0 -0
|
@@ -1736,8 +1736,42 @@ class Configuration(VssManager):
|
|
|
1736
1736
|
else:
|
|
1737
1737
|
os.system('clear')
|
|
1738
1738
|
|
|
1739
|
-
|
|
1739
|
+
def _get_client_ip(self) -> str:
|
|
1740
|
+
"""Get the client's IP address."""
|
|
1741
|
+
import socket
|
|
1742
|
+
|
|
1743
|
+
try:
|
|
1744
|
+
# Try to get the IP by connecting to a public DNS server
|
|
1745
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
1746
|
+
s.settimeout(1)
|
|
1747
|
+
# Connect to Google's public DNS
|
|
1748
|
+
s.connect(('8.8.8.8', 80))
|
|
1749
|
+
ip = s.getsockname()[0]
|
|
1750
|
+
s.close()
|
|
1751
|
+
return ip
|
|
1752
|
+
except Exception:
|
|
1753
|
+
# Fallback to localhost if unable to determine
|
|
1754
|
+
return '127.0.0.1'
|
|
1755
|
+
|
|
1756
|
+
def _generate_assistant_api_key(self) -> str:
|
|
1757
|
+
"""Generate a new API key for the assistant."""
|
|
1758
|
+
ip_address = self._get_client_ip()
|
|
1759
|
+
client_name = f'{self.user_agent}/{ip_address}'
|
|
1760
|
+
|
|
1761
|
+
auth_endpoint = f'{self.gpt_server}/api/generate-key'
|
|
1762
|
+
payload = {'client_name': client_name}
|
|
1763
|
+
|
|
1764
|
+
with requests.post(auth_endpoint, json=payload) as response:
|
|
1765
|
+
if response.status_code not in [200, 201]:
|
|
1766
|
+
raise VssCliError(
|
|
1767
|
+
f'Failed to generate API key. '
|
|
1768
|
+
f'Status: {response.status_code}'
|
|
1769
|
+
)
|
|
1770
|
+
data = response.json()
|
|
1771
|
+
return data.get('api_key')
|
|
1772
|
+
|
|
1740
1773
|
def get_new_chat_id(
|
|
1774
|
+
self,
|
|
1741
1775
|
chat_endpoint: str,
|
|
1742
1776
|
persona_id: int,
|
|
1743
1777
|
description: str,
|
|
@@ -1751,7 +1785,7 @@ class Configuration(VssManager):
|
|
|
1751
1785
|
if response.status_code in [401, 403, 500, 502, 503, 504]:
|
|
1752
1786
|
raise VssCliError(
|
|
1753
1787
|
'Invalid response from the API. '
|
|
1754
|
-
'
|
|
1788
|
+
'Failed to create chat session.'
|
|
1755
1789
|
)
|
|
1756
1790
|
rv = response.json()
|
|
1757
1791
|
chat_id = rv['chat_session_id']
|
|
@@ -1762,25 +1796,16 @@ class Configuration(VssManager):
|
|
|
1762
1796
|
message: str,
|
|
1763
1797
|
spinner_cls: Optional[Spinner] = None,
|
|
1764
1798
|
final_message: str = None,
|
|
1765
|
-
):
|
|
1799
|
+
) -> Tuple[str, str]:
|
|
1766
1800
|
"""Ask assistant."""
|
|
1801
|
+
# Generate a new API key for this session
|
|
1802
|
+
api_key = self._generate_assistant_api_key()
|
|
1803
|
+
|
|
1767
1804
|
headers = {
|
|
1768
|
-
'
|
|
1805
|
+
'api-key': f'{api_key}',
|
|
1769
1806
|
'Connection': 'keep-alive',
|
|
1770
1807
|
'Content-Type': 'application/json',
|
|
1771
1808
|
}
|
|
1772
|
-
retrieval_options = {
|
|
1773
|
-
"run_search": "auto",
|
|
1774
|
-
"real_time": True,
|
|
1775
|
-
"limit": 5,
|
|
1776
|
-
"filters": {
|
|
1777
|
-
"source_type": None,
|
|
1778
|
-
"document_set": None,
|
|
1779
|
-
"time_cutoff": None,
|
|
1780
|
-
"tags": [],
|
|
1781
|
-
"user_file_ids": None,
|
|
1782
|
-
},
|
|
1783
|
-
}
|
|
1784
1809
|
top_documents = []
|
|
1785
1810
|
chat_id = self.get_new_chat_id(
|
|
1786
1811
|
chat_endpoint=f'{self.gpt_server}/api/chat/create-chat-session',
|
|
@@ -1790,17 +1815,9 @@ class Configuration(VssManager):
|
|
|
1790
1815
|
)
|
|
1791
1816
|
# chat payload
|
|
1792
1817
|
payload = {
|
|
1793
|
-
"alternate_assistant_id": 2,
|
|
1794
1818
|
"chat_session_id": chat_id,
|
|
1795
|
-
"prompt_id": self._gpt_persona,
|
|
1796
|
-
"parent_message_id": None,
|
|
1797
|
-
"regenerate": False,
|
|
1798
|
-
"full_doc": False,
|
|
1799
|
-
"search_doc_ids": [],
|
|
1800
1819
|
"message": message,
|
|
1801
|
-
"
|
|
1802
|
-
"retrieval_options": retrieval_options,
|
|
1803
|
-
"use_agentic_search": False,
|
|
1820
|
+
"parent_message_id": None,
|
|
1804
1821
|
}
|
|
1805
1822
|
_LOGGING.debug(f'User data payload {payload}')
|
|
1806
1823
|
answer_text = ''
|
|
@@ -1812,27 +1829,117 @@ class Configuration(VssManager):
|
|
|
1812
1829
|
) as response:
|
|
1813
1830
|
if spinner_cls is not None:
|
|
1814
1831
|
spinner_cls.stop()
|
|
1832
|
+
reasoning_text = ''
|
|
1833
|
+
message_content = ''
|
|
1834
|
+
internal_search_queries = []
|
|
1835
|
+
internal_search_docs = []
|
|
1836
|
+
final_documents = []
|
|
1837
|
+
citations = []
|
|
1838
|
+
assistant_message_id = None
|
|
1839
|
+
|
|
1815
1840
|
for line in response.iter_lines():
|
|
1841
|
+
_LOGGING.debug(f"{line=}")
|
|
1816
1842
|
if line:
|
|
1817
1843
|
data = json.loads(line)
|
|
1818
|
-
|
|
1844
|
+
|
|
1845
|
+
# Handle initial message IDs
|
|
1846
|
+
if (
|
|
1847
|
+
'user_message_id' in data
|
|
1848
|
+
and 'reserved_assistant_message_id' in data
|
|
1849
|
+
):
|
|
1850
|
+
assistant_message_id = data[
|
|
1851
|
+
'reserved_assistant_message_id'
|
|
1852
|
+
]
|
|
1853
|
+
_LOGGING.debug(
|
|
1854
|
+
f"Message IDs - User: {data['user_message_id']},"
|
|
1855
|
+
f" Assistant: {assistant_message_id}"
|
|
1856
|
+
)
|
|
1857
|
+
continue
|
|
1858
|
+
|
|
1859
|
+
# Handle indexed objects
|
|
1860
|
+
if 'ind' in data and 'obj' in data:
|
|
1861
|
+
obj = data['obj']
|
|
1862
|
+
obj_type = obj.get('type')
|
|
1863
|
+
|
|
1864
|
+
# Handle reasoning streaming
|
|
1865
|
+
if obj_type == 'reasoning_start':
|
|
1866
|
+
_LOGGING.debug("Reasoning started")
|
|
1867
|
+
elif obj_type == 'reasoning_delta':
|
|
1868
|
+
reasoning_chunk = obj.get('reasoning', '')
|
|
1869
|
+
reasoning_text += reasoning_chunk
|
|
1870
|
+
# Optionally display reasoning to user
|
|
1871
|
+
# (you might want to hide this)
|
|
1872
|
+
self.smooth_print(reasoning_chunk)
|
|
1873
|
+
|
|
1874
|
+
# Handle message content streaming
|
|
1875
|
+
elif obj_type == 'message_start':
|
|
1876
|
+
if 'final_documents' in obj:
|
|
1877
|
+
final_documents = obj['final_documents']
|
|
1878
|
+
_LOGGING.debug("Message started")
|
|
1879
|
+
elif obj_type == 'message_delta':
|
|
1880
|
+
content_chunk = obj.get('content', '')
|
|
1881
|
+
message_content += content_chunk
|
|
1882
|
+
answer_text += content_chunk
|
|
1883
|
+
self.smooth_print(content_chunk)
|
|
1884
|
+
|
|
1885
|
+
# Handle internal search tool
|
|
1886
|
+
elif obj_type == 'internal_search_tool_start':
|
|
1887
|
+
_LOGGING.debug(
|
|
1888
|
+
f"Internal search started - Internet: "
|
|
1889
|
+
f"{obj.get('is_internet_search', False)}"
|
|
1890
|
+
)
|
|
1891
|
+
elif obj_type == 'internal_search_tool_delta':
|
|
1892
|
+
if 'queries' in obj and obj['queries']:
|
|
1893
|
+
internal_search_queries.extend(obj['queries'])
|
|
1894
|
+
if 'documents' in obj and obj['documents']:
|
|
1895
|
+
internal_search_docs.extend(obj['documents'])
|
|
1896
|
+
# Update top_documents for backward
|
|
1897
|
+
# compatibility
|
|
1898
|
+
top_documents = obj['documents']
|
|
1899
|
+
|
|
1900
|
+
# Handle citations
|
|
1901
|
+
elif obj_type == 'citation_start':
|
|
1902
|
+
_LOGGING.debug("Citations started")
|
|
1903
|
+
elif obj_type == 'citation_delta':
|
|
1904
|
+
if 'citations' in obj:
|
|
1905
|
+
citations.extend(obj['citations'])
|
|
1906
|
+
|
|
1907
|
+
# Handle section end and stop
|
|
1908
|
+
elif obj_type == 'section_end':
|
|
1909
|
+
_LOGGING.debug(
|
|
1910
|
+
f"Section ended for index {data['ind']}"
|
|
1911
|
+
)
|
|
1912
|
+
elif obj_type == 'stop':
|
|
1913
|
+
_LOGGING.debug(
|
|
1914
|
+
f"Stream stopped for index {data['ind']}"
|
|
1915
|
+
)
|
|
1916
|
+
|
|
1917
|
+
# Handle legacy format (backward compatibility)
|
|
1918
|
+
elif 'top_documents' in data:
|
|
1819
1919
|
top_documents = data['top_documents']
|
|
1820
|
-
answer_piece
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1920
|
+
elif 'answer_piece' in data:
|
|
1921
|
+
answer_piece = data['answer_piece']
|
|
1922
|
+
if answer_piece is not None:
|
|
1923
|
+
answer_text = answer_text + answer_piece
|
|
1924
|
+
self.smooth_print(answer_piece)
|
|
1925
|
+
# Use final_documents if available, otherwise fall
|
|
1926
|
+
# back to top_documents
|
|
1927
|
+
docs_to_display = final_documents if final_documents else top_documents
|
|
1928
|
+
|
|
1824
1929
|
docs = []
|
|
1825
1930
|
n = 1
|
|
1826
|
-
for doc in
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
)
|
|
1931
|
+
for doc in docs_to_display:
|
|
1932
|
+
# Handle both document_id and link fields
|
|
1933
|
+
doc_url = doc.get("link") or doc.get("document_id", "")
|
|
1934
|
+
doc_title = doc.get("semantic_identifier", "Unknown Document")
|
|
1935
|
+
docs.append(f'[{n}] [{doc_title}]({doc_url})')
|
|
1830
1936
|
n += 1
|
|
1831
1937
|
# make docs
|
|
1832
|
-
docs_text = '\n'.join(docs)
|
|
1938
|
+
docs_text = '\n'.join(docs[:5])
|
|
1833
1939
|
answer_text = answer_text + '\n\n' + docs_text
|
|
1834
1940
|
# clear console for formatting
|
|
1835
|
-
self.
|
|
1941
|
+
if not self.debug:
|
|
1942
|
+
self.clear_console()
|
|
1836
1943
|
from rich.console import Console
|
|
1837
1944
|
from rich.markdown import Markdown
|
|
1838
1945
|
|
|
@@ -1841,3 +1948,65 @@ class Configuration(VssManager):
|
|
|
1841
1948
|
console.print(markdown)
|
|
1842
1949
|
console.print()
|
|
1843
1950
|
console.print(Markdown(f'**Note: {final_message}**'))
|
|
1951
|
+
|
|
1952
|
+
# Return the assistant message ID and API key
|
|
1953
|
+
# for potential feedback
|
|
1954
|
+
return assistant_message_id, api_key
|
|
1955
|
+
|
|
1956
|
+
def provide_assistant_feedback(
|
|
1957
|
+
self,
|
|
1958
|
+
chat_message_id: str,
|
|
1959
|
+
api_key: str,
|
|
1960
|
+
is_positive: bool,
|
|
1961
|
+
feedback_text: Optional[str] = None,
|
|
1962
|
+
) -> bool:
|
|
1963
|
+
"""Provide feedback for an assistant response.
|
|
1964
|
+
|
|
1965
|
+
Args:
|
|
1966
|
+
chat_message_id: The ID of the assistant message
|
|
1967
|
+
to provide feedback for
|
|
1968
|
+
api_key: The API key generated for this session
|
|
1969
|
+
is_positive: True for thumbs up, False for thumbs down
|
|
1970
|
+
feedback_text: Optional feedback text (defaults to
|
|
1971
|
+
'Helpful' or 'Not helpful')
|
|
1972
|
+
|
|
1973
|
+
Returns:
|
|
1974
|
+
bool: True if feedback was submitted successfully, False otherwise
|
|
1975
|
+
"""
|
|
1976
|
+
if feedback_text is None:
|
|
1977
|
+
feedback_text = 'Helpful' if is_positive else 'Not helpful'
|
|
1978
|
+
|
|
1979
|
+
headers = {
|
|
1980
|
+
'api-key': f'{api_key}',
|
|
1981
|
+
'Content-Type': 'application/json',
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
payload = {
|
|
1985
|
+
"chat_message_id": chat_message_id,
|
|
1986
|
+
"is_positive": is_positive,
|
|
1987
|
+
"feedback_text": feedback_text,
|
|
1988
|
+
"predefined_feedback": None,
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
feedback_endpoint = f'{self.gpt_server}/api/chat/create-chat-feedback'
|
|
1992
|
+
|
|
1993
|
+
try:
|
|
1994
|
+
with requests.post(
|
|
1995
|
+
feedback_endpoint, headers=headers, json=payload
|
|
1996
|
+
) as response:
|
|
1997
|
+
if response.status_code in [200, 201]:
|
|
1998
|
+
_LOGGING.debug(
|
|
1999
|
+
f'Feedback submitted successfully '
|
|
2000
|
+
f'for message {chat_message_id}'
|
|
2001
|
+
)
|
|
2002
|
+
return True
|
|
2003
|
+
else:
|
|
2004
|
+
_LOGGING.warning(
|
|
2005
|
+
f'Failed to submit feedback. '
|
|
2006
|
+
f'Status: {response.status_code}, '
|
|
2007
|
+
f'Response: {response.text}'
|
|
2008
|
+
)
|
|
2009
|
+
return False
|
|
2010
|
+
except Exception as e:
|
|
2011
|
+
_LOGGING.error(f'Error submitting feedback: {e}')
|
|
2012
|
+
return False
|
|
@@ -8,7 +8,7 @@ except ImportError:
|
|
|
8
8
|
|
|
9
9
|
PACKAGE_NAME = "vss_cli"
|
|
10
10
|
|
|
11
|
-
__version__ = "2025.
|
|
11
|
+
__version__ = "2025.9.0-dev2"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
DEFAULT_TIMEOUT = 30
|
|
@@ -16,7 +16,7 @@ DEFAULT_ENDPOINT = "https://cloud-api.eis.utoronto.ca"
|
|
|
16
16
|
DEFAULT_ENDPOINT_NAME = "cloud-api"
|
|
17
17
|
DEFAULT_S3_SERVER = "https://vskey-stor.eis.utoronto.ca"
|
|
18
18
|
DEFAULT_VPN_SERVER = "https://vskey-vn.eis.utoronto.ca"
|
|
19
|
-
DEFAULT_GPT_SERVER = "https://gpt.eis.utoronto.ca"
|
|
19
|
+
DEFAULT_GPT_SERVER = "https://gpt-wpy.eis.utoronto.ca"
|
|
20
20
|
DEFAULT_GPT_PERSONA = 2
|
|
21
21
|
DEFAULT_GPT_TOKEN = ''
|
|
22
22
|
_LEGACY_CONFIG = ("~", ".vss-cli", "config.json")
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""AI assistant."""
|
|
2
|
+
import logging
|
|
3
|
+
import random
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from vss_cli.cli import pass_context
|
|
9
|
+
from vss_cli.config import Configuration
|
|
10
|
+
from vss_cli.utils.emoji import EMOJI_UNICODE
|
|
11
|
+
|
|
12
|
+
_LOGGING = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
ej_ai = EMOJI_UNICODE.get(':robot_face:')
|
|
15
|
+
ej_rk = EMOJI_UNICODE.get(':rocket:')
|
|
16
|
+
ej_thumbs_up = EMOJI_UNICODE.get(':thumbs_up:')
|
|
17
|
+
ej_thumbs_down = EMOJI_UNICODE.get(':thumbs_down:')
|
|
18
|
+
ej_checkmark = EMOJI_UNICODE.get(':white_heavy_check_mark:')
|
|
19
|
+
ej_star = EMOJI_UNICODE.get(':star:')
|
|
20
|
+
|
|
21
|
+
we_msg = f"""Hi, I’m UTORcloudy {ej_ai}, the ITS Private Cloud virtual agent.
|
|
22
|
+
I can help with account, virtual machine management, billing questions
|
|
23
|
+
and more. {ej_rk}
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
suggestions = [
|
|
27
|
+
"How to deploy an Ubuntu virtual machine?",
|
|
28
|
+
"How do I get started?",
|
|
29
|
+
"What are the VSS Guidelines?",
|
|
30
|
+
"How can I reset my account password?",
|
|
31
|
+
"How to enable Ubuntu Pro on your VM?",
|
|
32
|
+
"How to activate Windows Server in the ITS Private Cloud?",
|
|
33
|
+
"Do you provide public IP addresses?",
|
|
34
|
+
"How Can I change CPU, Memory,Network, HDD specs after a VM is created?",
|
|
35
|
+
"How to request an SSL certificate?",
|
|
36
|
+
"What additional services are included in my ITS Private Cloud bill?",
|
|
37
|
+
"What factors determine the cost of my ITS Private Cloud usage?",
|
|
38
|
+
'How can I install sentinelOne on my VM?',
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
final_msg = """
|
|
42
|
+
Responses are generated by artificial intelligence and may contain errors.
|
|
43
|
+
Always check sources and refer to actual policies for reliable information."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@click.command('assist', short_help='VSS AI Assistant')
|
|
47
|
+
@click.option(
|
|
48
|
+
'--no-load', is_flag=True, default=False, help='do not load config'
|
|
49
|
+
)
|
|
50
|
+
@click.option(
|
|
51
|
+
'--no-feedback', is_flag=True, default=False, help='skip feedback prompt'
|
|
52
|
+
)
|
|
53
|
+
@click.argument(
|
|
54
|
+
"message",
|
|
55
|
+
required=False,
|
|
56
|
+
)
|
|
57
|
+
@pass_context
|
|
58
|
+
def cli(ctx: Configuration, no_load: bool, no_feedback: bool, message: str):
|
|
59
|
+
"""Manage your VSS account."""
|
|
60
|
+
with ctx.spinner(disable=ctx.debug) as spinner_cls:
|
|
61
|
+
if no_load:
|
|
62
|
+
ctx.set_defaults()
|
|
63
|
+
else:
|
|
64
|
+
ctx.load_config(spinner_cls=spinner_cls)
|
|
65
|
+
_LOGGING.debug(
|
|
66
|
+
f'GPT settings: {ctx.gpt_persona=}, '
|
|
67
|
+
f'{ctx.gpt_token=}, {ctx.gpt_server}'
|
|
68
|
+
)
|
|
69
|
+
if not message:
|
|
70
|
+
spinner_cls.stop()
|
|
71
|
+
ctx.secho(we_msg)
|
|
72
|
+
default = random.choice(suggestions)
|
|
73
|
+
message = click.prompt(
|
|
74
|
+
"How may I assist you?",
|
|
75
|
+
type=str,
|
|
76
|
+
prompt_suffix=" ",
|
|
77
|
+
show_default=True,
|
|
78
|
+
default=f"{default}",
|
|
79
|
+
)
|
|
80
|
+
ctx.echo("")
|
|
81
|
+
spinner_cls.start()
|
|
82
|
+
# ask assistant
|
|
83
|
+
assistant_message_id, api_key = ctx.ask_assistant(
|
|
84
|
+
spinner_cls=spinner_cls, message=message, final_message=final_msg
|
|
85
|
+
)
|
|
86
|
+
# Only ask for feedback if enabled and we have a valid message ID
|
|
87
|
+
if not no_feedback and assistant_message_id and api_key:
|
|
88
|
+
# Compact feedback prompt
|
|
89
|
+
# Single line feedback options
|
|
90
|
+
ctx.echo("")
|
|
91
|
+
ctx.secho("Rate this response: ", fg='cyan', nl=False, bold=True)
|
|
92
|
+
ctx.secho(f"{ej_thumbs_up} Helpful (y) ", fg='green', nl=False)
|
|
93
|
+
ctx.secho(
|
|
94
|
+
f"{ej_thumbs_down} Not helpful (n) ", fg='yellow', nl=False
|
|
95
|
+
)
|
|
96
|
+
ctx.secho("Skip (s)", fg='bright_black')
|
|
97
|
+
|
|
98
|
+
# Get user input with compact prompt
|
|
99
|
+
feedback_choice = click.prompt(
|
|
100
|
+
"",
|
|
101
|
+
type=click.Choice(['y', 'n', 's'], case_sensitive=False),
|
|
102
|
+
default='s',
|
|
103
|
+
show_choices=False,
|
|
104
|
+
show_default=False,
|
|
105
|
+
prompt_suffix="Choice: ",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Process feedback based on choice
|
|
109
|
+
if feedback_choice.lower() == 'y':
|
|
110
|
+
success = ctx.provide_assistant_feedback(
|
|
111
|
+
chat_message_id=assistant_message_id,
|
|
112
|
+
is_positive=True,
|
|
113
|
+
api_key=api_key,
|
|
114
|
+
)
|
|
115
|
+
if success:
|
|
116
|
+
ctx.secho(
|
|
117
|
+
f"{ej_checkmark} Thanks for your feedback!", fg='green'
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
_LOGGING.debug("Failed to submit positive feedback")
|
|
121
|
+
|
|
122
|
+
elif feedback_choice.lower() == 'n':
|
|
123
|
+
# Compact negative feedback prompt
|
|
124
|
+
additional_feedback = click.prompt(
|
|
125
|
+
"Details (optional)",
|
|
126
|
+
default="",
|
|
127
|
+
show_default=False,
|
|
128
|
+
prompt_suffix=": ",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
feedback_text = (
|
|
132
|
+
additional_feedback
|
|
133
|
+
if additional_feedback
|
|
134
|
+
else "Not helpful"
|
|
135
|
+
)
|
|
136
|
+
success = ctx.provide_assistant_feedback(
|
|
137
|
+
chat_message_id=assistant_message_id,
|
|
138
|
+
is_positive=False,
|
|
139
|
+
api_key=api_key,
|
|
140
|
+
feedback_text=feedback_text,
|
|
141
|
+
)
|
|
142
|
+
if success:
|
|
143
|
+
ctx.secho(
|
|
144
|
+
f"{ej_checkmark} Thanks! We'll improve.", fg='cyan'
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
_LOGGING.debug("Failed to submit negative feedback")
|
|
148
|
+
|
|
149
|
+
else:
|
|
150
|
+
ctx.secho("Skipped.", fg='bright_black')
|
|
151
|
+
else:
|
|
152
|
+
_LOGGING.debug(
|
|
153
|
+
f"Cannot provide feedback: "
|
|
154
|
+
f"assistant_message_id={assistant_message_id}, "
|
|
155
|
+
f"api_key={'present' if api_key else 'missing'}"
|
|
156
|
+
)
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"""AI assistant."""
|
|
2
|
-
import logging
|
|
3
|
-
import random
|
|
4
|
-
|
|
5
|
-
import click
|
|
6
|
-
|
|
7
|
-
from vss_cli.cli import pass_context
|
|
8
|
-
from vss_cli.config import Configuration
|
|
9
|
-
from vss_cli.utils.emoji import EMOJI_UNICODE
|
|
10
|
-
|
|
11
|
-
_LOGGING = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
ej_ai = EMOJI_UNICODE.get(':robot_face:')
|
|
14
|
-
ej_rk = EMOJI_UNICODE.get(':rocket:')
|
|
15
|
-
|
|
16
|
-
we_msg = f"""Hi, I’m UTORcloudy {ej_ai}, the ITS Private Cloud virtual agent.
|
|
17
|
-
I can help with account, virtual machine management, billing questions
|
|
18
|
-
and more. {ej_rk}
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
suggestions = [
|
|
22
|
-
"How to deploy an Ubuntu virtual machine?",
|
|
23
|
-
"How do I get started?",
|
|
24
|
-
"What are the VSS Guidelines?",
|
|
25
|
-
"How can I reset my account password?",
|
|
26
|
-
"How to enable Ubuntu Pro on your VM?",
|
|
27
|
-
"How to activate Windows Server in the ITS Private Cloud?",
|
|
28
|
-
"Do you provide public IP addresses?",
|
|
29
|
-
"How Can I change CPU, Memory,Network, HDD specs after a VM is created?",
|
|
30
|
-
"How to request an SSL certificate?",
|
|
31
|
-
"What additional services are included in my ITS Private Cloud bill?",
|
|
32
|
-
"What factors determine the cost of my ITS Private Cloud usage?",
|
|
33
|
-
'How can I install sentinelOne on my VM?',
|
|
34
|
-
]
|
|
35
|
-
|
|
36
|
-
final_msg = """
|
|
37
|
-
Responses are generated by artificial intelligence and may contain errors.
|
|
38
|
-
Always check sources and refer to actual policies for reliable information."""
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@click.command('assist', short_help='VSS AI Assistant')
|
|
42
|
-
@click.option(
|
|
43
|
-
'--no-load', is_flag=True, default=False, help='do not load config'
|
|
44
|
-
)
|
|
45
|
-
@click.argument(
|
|
46
|
-
"message",
|
|
47
|
-
required=False,
|
|
48
|
-
)
|
|
49
|
-
@pass_context
|
|
50
|
-
def cli(ctx: Configuration, no_load: bool, message: str):
|
|
51
|
-
"""Manage your VSS account."""
|
|
52
|
-
with ctx.spinner(disable=ctx.debug) as spinner_cls:
|
|
53
|
-
if no_load:
|
|
54
|
-
ctx.set_defaults()
|
|
55
|
-
else:
|
|
56
|
-
ctx.load_config(spinner_cls=spinner_cls)
|
|
57
|
-
_LOGGING.debug(
|
|
58
|
-
f'GPT settings: {ctx.gpt_persona=}, '
|
|
59
|
-
f'{ctx.gpt_token=}, {ctx.gpt_server}'
|
|
60
|
-
)
|
|
61
|
-
if not message:
|
|
62
|
-
spinner_cls.stop()
|
|
63
|
-
ctx.secho(we_msg)
|
|
64
|
-
default = random.choice(suggestions)
|
|
65
|
-
message = click.prompt(
|
|
66
|
-
"How may I assist you?",
|
|
67
|
-
type=str,
|
|
68
|
-
prompt_suffix=" ",
|
|
69
|
-
show_default=True,
|
|
70
|
-
default=f"{default}",
|
|
71
|
-
)
|
|
72
|
-
ctx.echo("")
|
|
73
|
-
spinner_cls.start()
|
|
74
|
-
ctx.ask_assistant(
|
|
75
|
-
spinner_cls=spinner_cls, message=message, final_message=final_msg
|
|
76
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/callbacks.py
RENAMED
|
File without changes
|
{vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/contentlib.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/inventory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/inventory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/retirement.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|