npcpy 1.0.26__py3-none-any.whl → 1.2.32__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 (148) hide show
  1. npcpy/__init__.py +0 -7
  2. npcpy/data/audio.py +16 -99
  3. npcpy/data/image.py +43 -42
  4. npcpy/data/load.py +83 -124
  5. npcpy/data/text.py +28 -28
  6. npcpy/data/video.py +8 -32
  7. npcpy/data/web.py +51 -23
  8. npcpy/ft/diff.py +110 -0
  9. npcpy/ft/ge.py +115 -0
  10. npcpy/ft/memory_trainer.py +171 -0
  11. npcpy/ft/model_ensembler.py +357 -0
  12. npcpy/ft/rl.py +360 -0
  13. npcpy/ft/sft.py +248 -0
  14. npcpy/ft/usft.py +128 -0
  15. npcpy/gen/audio_gen.py +24 -0
  16. npcpy/gen/embeddings.py +13 -13
  17. npcpy/gen/image_gen.py +262 -117
  18. npcpy/gen/response.py +615 -415
  19. npcpy/gen/video_gen.py +53 -7
  20. npcpy/llm_funcs.py +1869 -437
  21. npcpy/main.py +1 -1
  22. npcpy/memory/command_history.py +844 -510
  23. npcpy/memory/kg_vis.py +833 -0
  24. npcpy/memory/knowledge_graph.py +892 -1845
  25. npcpy/memory/memory_processor.py +81 -0
  26. npcpy/memory/search.py +188 -90
  27. npcpy/mix/debate.py +192 -3
  28. npcpy/npc_compiler.py +1672 -801
  29. npcpy/npc_sysenv.py +593 -1266
  30. npcpy/serve.py +3120 -0
  31. npcpy/sql/ai_function_tools.py +257 -0
  32. npcpy/sql/database_ai_adapters.py +186 -0
  33. npcpy/sql/database_ai_functions.py +163 -0
  34. npcpy/sql/model_runner.py +19 -19
  35. npcpy/sql/npcsql.py +706 -507
  36. npcpy/sql/sql_model_compiler.py +156 -0
  37. npcpy/tools.py +183 -0
  38. npcpy/work/plan.py +13 -279
  39. npcpy/work/trigger.py +3 -3
  40. npcpy-1.2.32.dist-info/METADATA +803 -0
  41. npcpy-1.2.32.dist-info/RECORD +54 -0
  42. npcpy/data/dataframes.py +0 -171
  43. npcpy/memory/deep_research.py +0 -125
  44. npcpy/memory/sleep.py +0 -557
  45. npcpy/modes/_state.py +0 -78
  46. npcpy/modes/alicanto.py +0 -1075
  47. npcpy/modes/guac.py +0 -785
  48. npcpy/modes/mcp_npcsh.py +0 -822
  49. npcpy/modes/npc.py +0 -213
  50. npcpy/modes/npcsh.py +0 -1158
  51. npcpy/modes/plonk.py +0 -409
  52. npcpy/modes/pti.py +0 -234
  53. npcpy/modes/serve.py +0 -1637
  54. npcpy/modes/spool.py +0 -312
  55. npcpy/modes/wander.py +0 -549
  56. npcpy/modes/yap.py +0 -572
  57. npcpy/npc_team/alicanto.npc +0 -2
  58. npcpy/npc_team/alicanto.png +0 -0
  59. npcpy/npc_team/assembly_lines/test_pipeline.py +0 -181
  60. npcpy/npc_team/corca.npc +0 -13
  61. npcpy/npc_team/foreman.npc +0 -7
  62. npcpy/npc_team/frederic.npc +0 -6
  63. npcpy/npc_team/frederic4.png +0 -0
  64. npcpy/npc_team/guac.png +0 -0
  65. npcpy/npc_team/jinxs/automator.jinx +0 -18
  66. npcpy/npc_team/jinxs/bash_executer.jinx +0 -31
  67. npcpy/npc_team/jinxs/calculator.jinx +0 -11
  68. npcpy/npc_team/jinxs/edit_file.jinx +0 -96
  69. npcpy/npc_team/jinxs/file_chat.jinx +0 -14
  70. npcpy/npc_team/jinxs/gui_controller.jinx +0 -28
  71. npcpy/npc_team/jinxs/image_generation.jinx +0 -29
  72. npcpy/npc_team/jinxs/internet_search.jinx +0 -30
  73. npcpy/npc_team/jinxs/local_search.jinx +0 -152
  74. npcpy/npc_team/jinxs/npcsh_executor.jinx +0 -31
  75. npcpy/npc_team/jinxs/python_executor.jinx +0 -8
  76. npcpy/npc_team/jinxs/screen_cap.jinx +0 -25
  77. npcpy/npc_team/jinxs/sql_executor.jinx +0 -33
  78. npcpy/npc_team/kadiefa.npc +0 -3
  79. npcpy/npc_team/kadiefa.png +0 -0
  80. npcpy/npc_team/npcsh.ctx +0 -9
  81. npcpy/npc_team/npcsh_sibiji.png +0 -0
  82. npcpy/npc_team/plonk.npc +0 -2
  83. npcpy/npc_team/plonk.png +0 -0
  84. npcpy/npc_team/plonkjr.npc +0 -2
  85. npcpy/npc_team/plonkjr.png +0 -0
  86. npcpy/npc_team/sibiji.npc +0 -5
  87. npcpy/npc_team/sibiji.png +0 -0
  88. npcpy/npc_team/spool.png +0 -0
  89. npcpy/npc_team/templates/analytics/celona.npc +0 -0
  90. npcpy/npc_team/templates/hr_support/raone.npc +0 -0
  91. npcpy/npc_team/templates/humanities/eriane.npc +0 -4
  92. npcpy/npc_team/templates/it_support/lineru.npc +0 -0
  93. npcpy/npc_team/templates/marketing/slean.npc +0 -4
  94. npcpy/npc_team/templates/philosophy/maurawa.npc +0 -0
  95. npcpy/npc_team/templates/sales/turnic.npc +0 -4
  96. npcpy/npc_team/templates/software/welxor.npc +0 -0
  97. npcpy/npc_team/yap.png +0 -0
  98. npcpy/routes.py +0 -958
  99. npcpy/work/mcp_helpers.py +0 -357
  100. npcpy/work/mcp_server.py +0 -194
  101. npcpy-1.0.26.data/data/npcpy/npc_team/alicanto.npc +0 -2
  102. npcpy-1.0.26.data/data/npcpy/npc_team/alicanto.png +0 -0
  103. npcpy-1.0.26.data/data/npcpy/npc_team/automator.jinx +0 -18
  104. npcpy-1.0.26.data/data/npcpy/npc_team/bash_executer.jinx +0 -31
  105. npcpy-1.0.26.data/data/npcpy/npc_team/calculator.jinx +0 -11
  106. npcpy-1.0.26.data/data/npcpy/npc_team/celona.npc +0 -0
  107. npcpy-1.0.26.data/data/npcpy/npc_team/corca.npc +0 -13
  108. npcpy-1.0.26.data/data/npcpy/npc_team/edit_file.jinx +0 -96
  109. npcpy-1.0.26.data/data/npcpy/npc_team/eriane.npc +0 -4
  110. npcpy-1.0.26.data/data/npcpy/npc_team/file_chat.jinx +0 -14
  111. npcpy-1.0.26.data/data/npcpy/npc_team/foreman.npc +0 -7
  112. npcpy-1.0.26.data/data/npcpy/npc_team/frederic.npc +0 -6
  113. npcpy-1.0.26.data/data/npcpy/npc_team/frederic4.png +0 -0
  114. npcpy-1.0.26.data/data/npcpy/npc_team/guac.png +0 -0
  115. npcpy-1.0.26.data/data/npcpy/npc_team/gui_controller.jinx +0 -28
  116. npcpy-1.0.26.data/data/npcpy/npc_team/image_generation.jinx +0 -29
  117. npcpy-1.0.26.data/data/npcpy/npc_team/internet_search.jinx +0 -30
  118. npcpy-1.0.26.data/data/npcpy/npc_team/kadiefa.npc +0 -3
  119. npcpy-1.0.26.data/data/npcpy/npc_team/kadiefa.png +0 -0
  120. npcpy-1.0.26.data/data/npcpy/npc_team/lineru.npc +0 -0
  121. npcpy-1.0.26.data/data/npcpy/npc_team/local_search.jinx +0 -152
  122. npcpy-1.0.26.data/data/npcpy/npc_team/maurawa.npc +0 -0
  123. npcpy-1.0.26.data/data/npcpy/npc_team/npcsh.ctx +0 -9
  124. npcpy-1.0.26.data/data/npcpy/npc_team/npcsh_executor.jinx +0 -31
  125. npcpy-1.0.26.data/data/npcpy/npc_team/npcsh_sibiji.png +0 -0
  126. npcpy-1.0.26.data/data/npcpy/npc_team/plonk.npc +0 -2
  127. npcpy-1.0.26.data/data/npcpy/npc_team/plonk.png +0 -0
  128. npcpy-1.0.26.data/data/npcpy/npc_team/plonkjr.npc +0 -2
  129. npcpy-1.0.26.data/data/npcpy/npc_team/plonkjr.png +0 -0
  130. npcpy-1.0.26.data/data/npcpy/npc_team/python_executor.jinx +0 -8
  131. npcpy-1.0.26.data/data/npcpy/npc_team/raone.npc +0 -0
  132. npcpy-1.0.26.data/data/npcpy/npc_team/screen_cap.jinx +0 -25
  133. npcpy-1.0.26.data/data/npcpy/npc_team/sibiji.npc +0 -5
  134. npcpy-1.0.26.data/data/npcpy/npc_team/sibiji.png +0 -0
  135. npcpy-1.0.26.data/data/npcpy/npc_team/slean.npc +0 -4
  136. npcpy-1.0.26.data/data/npcpy/npc_team/spool.png +0 -0
  137. npcpy-1.0.26.data/data/npcpy/npc_team/sql_executor.jinx +0 -33
  138. npcpy-1.0.26.data/data/npcpy/npc_team/test_pipeline.py +0 -181
  139. npcpy-1.0.26.data/data/npcpy/npc_team/turnic.npc +0 -4
  140. npcpy-1.0.26.data/data/npcpy/npc_team/welxor.npc +0 -0
  141. npcpy-1.0.26.data/data/npcpy/npc_team/yap.png +0 -0
  142. npcpy-1.0.26.dist-info/METADATA +0 -827
  143. npcpy-1.0.26.dist-info/RECORD +0 -139
  144. npcpy-1.0.26.dist-info/entry_points.txt +0 -11
  145. /npcpy/{modes → ft}/__init__.py +0 -0
  146. {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/WHEEL +0 -0
  147. {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/licenses/LICENSE +0 -0
  148. {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/top_level.txt +0 -0
npcpy/routes.py DELETED
@@ -1,958 +0,0 @@
1
- # --- START OF FILE routes.py ---
2
-
3
- from typing import Callable, Dict, Any, List, Optional, Union
4
- import functools
5
- import os
6
- import traceback
7
- import shlex
8
- import time
9
- from datetime import datetime
10
- from sqlalchemy import create_engine
11
- import logging
12
-
13
- from npcpy.npc_sysenv import (
14
- render_code_block, render_markdown,
15
- NPCSH_VISION_MODEL, NPCSH_VISION_PROVIDER, NPCSH_API_URL,
16
- NPCSH_CHAT_MODEL, NPCSH_CHAT_PROVIDER, NPCSH_STREAM_OUTPUT,
17
- NPCSH_IMAGE_GEN_MODEL, NPCSH_IMAGE_GEN_PROVIDER,
18
- NPCSH_EMBEDDING_MODEL, NPCSH_EMBEDDING_PROVIDER,
19
- NPCSH_REASONING_MODEL, NPCSH_REASONING_PROVIDER,
20
- NPCSH_SEARCH_PROVIDER,
21
- )
22
- from npcpy.data.load import load_file_contents
23
-
24
- from npcpy.llm_funcs import (
25
- get_llm_response,
26
- gen_image,
27
- gen_video,
28
- )
29
- from npcpy.npc_compiler import NPC, Team, Jinx
30
- from npcpy.npc_compiler import initialize_npc_project
31
-
32
-
33
- from npcpy.work.plan import execute_plan_command
34
- from npcpy.work.trigger import execute_trigger_command
35
- from npcpy.work.desktop import perform_action
36
-
37
-
38
- from npcpy.memory.search import execute_rag_command, execute_search_command, execute_brainblast_command
39
- from npcpy.memory.command_history import CommandHistory
40
-
41
-
42
- from npcpy.memory.knowledge_graph import breathe
43
- from npcpy.memory.sleep import sleep, forget
44
-
45
- from npcpy.modes.guac import enter_guac_mode
46
- from npcpy.modes.plonk import execute_plonk_command
47
- from npcpy.modes.serve import start_flask_server
48
- from npcpy.modes.alicanto import alicanto
49
- from npcpy.modes.spool import enter_spool_mode
50
- from npcpy.modes.wander import enter_wander_mode
51
- from npcpy.modes.yap import enter_yap_mode
52
-
53
-
54
-
55
- from npcpy.mix.debate import run_debate
56
- from npcpy.data.image import capture_screenshot
57
- from npcpy.npc_compiler import NPC, Team, Jinx
58
- from npcpy.npc_compiler import initialize_npc_project
59
- from npcpy.data.web import search_web
60
-
61
- class CommandRouter:
62
- def __init__(self):
63
- self.routes = {}
64
- self.help_info = {}
65
- self.shell_only = {}
66
-
67
- def route(self, command: str, help_text: str = "", shell_only: bool = False) -> Callable:
68
- def wrapper(func):
69
- self.routes[command] = func
70
- self.help_info[command] = help_text
71
- self.shell_only[command] = shell_only
72
-
73
- @functools.wraps(func)
74
- def wrapped_func(*args, **kwargs):
75
- return func(*args, **kwargs)
76
-
77
- return wrapped_func
78
- return wrapper
79
-
80
- def get_route(self, command: str) -> Optional[Callable]:
81
- return self.routes.get(command)
82
-
83
- def execute(self, command_str: str, **kwargs) -> Any:
84
- command_name = command_str.split()[0].lstrip('/')
85
- route_func = self.get_route(command_name)
86
- if route_func:
87
- return route_func(command=command_str, **kwargs)
88
- return None
89
-
90
- def get_commands(self) -> List[str]:
91
- return list(self.routes.keys())
92
-
93
- def get_help(self, command: str = None) -> Dict[str, str]:
94
- if command:
95
- if command in self.help_info:
96
- return {command: self.help_info[command]}
97
- return {}
98
- return self.help_info
99
-
100
- router = CommandRouter()
101
-
102
- def get_help_text():
103
- commands = router.get_commands()
104
- help_info = router.help_info
105
- shell_only = router.shell_only
106
- commands.sort()
107
- output = "# Available Commands\n\n"
108
- for cmd in commands:
109
- help_text = help_info.get(cmd, "")
110
- shell_only_text = " (Shell only)" if shell_only.get(cmd, False) else ""
111
- output += f"/{cmd}{shell_only_text} - {help_text}\n\n"
112
- output += """
113
- # Note
114
- - Bash commands and programs can be executed directly (try bash first, then LLM).
115
- - Use '/exit' or '/quit' to exit the current NPC mode or the npcsh shell.
116
- - Jinxs defined for the current NPC or Team can also be used like commands (e.g., /screenshot).
117
- """
118
- return output
119
-
120
- def safe_get(kwargs, key, default=None):
121
- return kwargs.get(key, default)
122
-
123
- @router.route("breathe", "Condense context on a regular cadence", shell_only=True)
124
- def breathe_handler(command: str, **kwargs):
125
- messages = safe_get(kwargs, "messages", [])
126
- npc = safe_get(kwargs, "npc")
127
- try:
128
- result = run_breathe_cycle(messages=messages, npc=npc, **kwargs)
129
- if isinstance(result, dict): return result
130
- return {"output": str(result), "messages": messages}
131
- except NameError:
132
- return {"output": "Breathe function (run_breathe_cycle) not available.", "messages": messages}
133
- except Exception as e:
134
- traceback.print_exc()
135
- return {"output": f"Error during breathe: {e}", "messages": messages}
136
-
137
- @router.route("compile", "Compile NPC profiles")
138
- def compile_handler(command: str, **kwargs):
139
- messages = safe_get(kwargs, "messages", [])
140
- npc_team_dir = safe_get(kwargs, 'current_path', './npc_team')
141
- parts = command.split()
142
- npc_file_path_arg = parts[1] if len(parts) > 1 else None
143
- output = ""
144
- try:
145
- if npc_file_path_arg:
146
- npc_full_path = os.path.abspath(npc_file_path_arg)
147
- if os.path.exists(npc_full_path):
148
- npc = NPC(npc_full_path)
149
- output = f"Compiled NPC: {npc_full_path}"
150
- else:
151
- output = f"Error: NPC file not found: {npc_full_path}"
152
- else:
153
- npc = NPC(npc_full_path)
154
-
155
- output = f"Compiled all NPCs in directory: {npc_team_dir}"
156
- except NameError:
157
- output = "Compile functions (compile_npc_file, compile_team_npcs) not available."
158
- except Exception as e:
159
- traceback.print_exc()
160
- output = f"Error compiling: {e}"
161
- return {"output": output, "messages": messages, "npc": npc}
162
-
163
-
164
-
165
- @router.route("flush", "Flush the last N messages", shell_only=True)
166
- def flush_handler(command: str, **kwargs):
167
- messages = safe_get(kwargs, "messages", [])
168
- try:
169
- parts = command.split()
170
- n = int(parts[1]) if len(parts) > 1 else 1
171
- except (ValueError, IndexError):
172
- return {"output": "Usage: /flush [number_of_messages_to_flush]", "messages": messages}
173
-
174
- if n <= 0:
175
- return {"output": "Error: Number of messages must be positive.", "messages": messages}
176
-
177
- new_messages = list(messages)
178
- original_len = len(new_messages)
179
- removed_count = 0
180
-
181
- if new_messages and new_messages[0].get("role") == "system":
182
- system_message = new_messages[0]
183
- working_messages = new_messages[1:]
184
- num_to_remove = min(n, len(working_messages))
185
- if num_to_remove > 0:
186
- final_messages = [system_message] + working_messages[:-num_to_remove]
187
- removed_count = num_to_remove
188
- else:
189
- final_messages = [system_message]
190
- else:
191
- num_to_remove = min(n, original_len)
192
- if num_to_remove > 0:
193
- final_messages = new_messages[:-num_to_remove]
194
- removed_count = num_to_remove
195
- else:
196
- final_messages = []
197
-
198
- output = f"Flushed {removed_count} message(s). Context is now {len(final_messages)} messages."
199
- return {"output": output, "messages": final_messages}
200
-
201
- @router.route("guac", "Enter guac mode")
202
- def guac_handler(command, **kwargs):
203
- '''
204
- Guac ignores input npc and npc_team dirs and manually sets them to be at ~/.npcsh/guac/
205
-
206
- '''
207
- config_dir = safe_get(kwargs, 'config_dir', None)
208
- plots_dir = safe_get(kwargs, 'plots_dir', None)
209
- refresh_period = safe_get(kwargs, 'refresh_period', 100)
210
- lang = safe_get(kwargs, 'lang', None)
211
- messages = safe_get(kwargs, "messages", [])
212
- db_conn = safe_get(kwargs, 'db_conn', create_engine('sqlite:///'+os.path.expanduser('~/npcsh_history.db')))
213
-
214
- npc_file = '~/.npcsh/guac/npc_team/guac.npc'
215
- npc_team_dir = os.path.expanduser('~/.npcsh/guac/npc_team/')
216
-
217
- npc = NPC(file=npc_file, db_conn=db_conn)
218
-
219
- team = Team(npc_team_dir, db_conn=db_conn)
220
-
221
-
222
- enter_guac_mode(npc=npc,
223
- team=team,
224
- config_dir=config_dir,
225
- plots_dir=plots_dir,
226
- npc_team_dir=npc_team_dir,
227
- refresh_period=refresh_period, lang=lang)
228
-
229
- return {"output": 'Exiting Guac Mode', "messages": safe_get(kwargs, "messages", [])}
230
-
231
-
232
- @router.route("help", "Show help information")
233
- def help_handler(command, **kwargs):
234
- return {"output": get_help_text(), "messages": safe_get(kwargs, "messages", [])}
235
-
236
- @router.route("init", "Initialize NPC project")
237
- def init_handler(command: str, **kwargs):
238
- messages = safe_get(kwargs, "messages", [])
239
- try:
240
- parts = shlex.split(command)
241
- directory = "."
242
- templates = None
243
- context = None
244
- # Basic parsing example (needs improvement for robust flag handling)
245
- if len(parts) > 1 and not parts[1].startswith("-"):
246
- directory = parts[1]
247
- # Add logic here to parse -t, -ctx flags if needed
248
-
249
- initialize_npc_project(
250
- directory=directory,
251
- templates=templates,
252
- context=context,
253
- model=safe_get(kwargs, 'model'),
254
- provider=safe_get(kwargs, 'provider')
255
- )
256
- output = f"NPC project initialized in {os.path.abspath(directory)}."
257
- except NameError:
258
- output = "Init function (initialize_npc_project) not available."
259
- except Exception as e:
260
- traceback.print_exc()
261
- output = f"Error initializing project: {e}"
262
- return {"output": output, "messages": messages}
263
-
264
-
265
- @router.route("ots", "Take screenshot and optionally analyze with vision model")
266
- def ots_handler(command: str, **kwargs):
267
- command_parts = command.split()
268
- image_paths = []
269
- npc = safe_get(kwargs, 'npc')
270
- vision_model = safe_get(kwargs, 'model', NPCSH_VISION_MODEL)
271
- vision_provider = safe_get(kwargs, 'provider', NPCSH_VISION_PROVIDER)
272
- if vision_model == NPCSH_CHAT_MODEL: vision_model = NPCSH_VISION_MODEL
273
- if vision_provider == NPCSH_CHAT_PROVIDER: vision_provider = NPCSH_VISION_PROVIDER
274
-
275
- messages = safe_get(kwargs, 'messages', [])
276
- stream = safe_get(kwargs, 'stream', NPCSH_STREAM_OUTPUT)
277
-
278
- try:
279
- if len(command_parts) > 1:
280
- for img_path_arg in command_parts[1:]:
281
- full_path = os.path.abspath(img_path_arg)
282
- if os.path.exists(full_path):
283
- image_paths.append(full_path)
284
- else:
285
- return {"output": f"Error: Image file not found at {full_path}", "messages": messages}
286
- else:
287
- screenshot_info = capture_screenshot(npc=npc)
288
- if screenshot_info and "file_path" in screenshot_info:
289
- image_paths.append(screenshot_info["file_path"])
290
- print(f"Screenshot captured: {screenshot_info.get('filename', os.path.basename(screenshot_info['file_path']))}")
291
- else:
292
- return {"output": "Error: Failed to capture screenshot.", "messages": messages}
293
-
294
- if not image_paths:
295
- return {"output": "No valid images found or captured.", "messages": messages}
296
-
297
- user_prompt = safe_get(kwargs, 'stdin_input')
298
- if user_prompt is None:
299
- try:
300
- user_prompt = input(
301
- "Enter a prompt for the LLM about these images (or press Enter to skip): "
302
- )
303
- except EOFError:
304
- user_prompt = "Describe the image(s)."
305
-
306
- if not user_prompt or not user_prompt.strip():
307
- user_prompt = "Describe the image(s)."
308
-
309
- response_data = get_llm_response(
310
- prompt=user_prompt,
311
- model=vision_model,
312
- provider=vision_provider,
313
- messages=messages,
314
- images=image_paths,
315
- stream=stream,
316
- npc=npc,
317
- api_url=safe_get(kwargs, 'api_url'),
318
- api_key=safe_get(kwargs, 'api_key')
319
- )
320
- return {"output": response_data.get('response'), "messages": response_data.get('messages')}
321
-
322
- except Exception as e:
323
- traceback.print_exc()
324
- return {"output": f"Error during /ots command: {e}", "messages": messages}
325
-
326
-
327
- @router.route("plan", "Execute a plan command")
328
- def plan_handler(command: str, **kwargs):
329
- messages = safe_get(kwargs, "messages", [])
330
- user_command = " ".join(command.split()[1:])
331
- if not user_command:
332
- return {"output": "Usage: /plan <description_of_plan>", "messages": messages}
333
- try:
334
- return execute_plan_command(command=user_command, **kwargs)
335
- except NameError:
336
- return {"output": "Plan function (execute_plan_command) not available.", "messages": messages}
337
- except Exception as e:
338
- traceback.print_exc()
339
- return {"output": f"Error executing plan: {e}", "messages": messages}
340
-
341
- @router.route("pti", "Use pardon-the-interruption mode to interact with the LLM")
342
- def plonk_handler(command: str, **kwargs):
343
- return
344
-
345
- @router.route("plonk", "Use vision model to interact with GUI")
346
- def plonk_handler(command: str, **kwargs):
347
- messages = safe_get(kwargs, "messages", [])
348
- request_str = " ".join(command.split()[1:])
349
- if not request_str:
350
- return {"output": "Usage: /plonk <task_description>", "messages": messages}
351
-
352
- action_space = {
353
- "click": {"x": "int (0-100)", "y": "int (0-100)"},
354
- "type": {"text": "string"},
355
- "scroll": {"direction": "up/down/left/right", "amount": "int"},
356
- "bash": {"command": "string"},
357
- "wait": {"duration": "int (seconds)"}
358
- }
359
- try:
360
- result = execute_plonk_command(
361
- request=request_str,
362
- action_space=action_space,
363
- model=safe_get(kwargs, 'model', NPCSH_VISION_MODEL),
364
- provider=safe_get(kwargs, 'provider', NPCSH_VISION_PROVIDER),
365
- npc=safe_get(kwargs, 'npc')
366
- )
367
- if isinstance(result, dict) and "output" in result:
368
- result_messages = result.get("messages", messages)
369
- return {"output": result["output"], "messages": result_messages}
370
- else:
371
- return {"output": str(result), "messages": messages}
372
- except NameError:
373
- return {"output": "Plonk function (execute_plonk_command) not available.", "messages": messages}
374
- except Exception as e:
375
- traceback.print_exc()
376
- return {"output": f"Error executing plonk command: {e}", "messages": messages}
377
- @router.route("brainblast", "Execute an advanced chunked search on command history")
378
- def brainblast_handler(command: str, **kwargs):
379
- messages = safe_get(kwargs, "messages", [])
380
-
381
- # Parse command to get the search query
382
- parts = shlex.split(command)
383
- search_query = " ".join(parts[1:]) if len(parts) > 1 else ""
384
-
385
-
386
- if not search_query:
387
- return {"output": "Usage: /brainblast <search_terms>", "messages": messages}
388
-
389
- # Get the command history instance
390
- command_history = kwargs.get('command_history')
391
- if not command_history:
392
- # Create a new one if not provided
393
- db_path = safe_get(kwargs, "history_db_path", os.path.expanduser('~/npcsh_history.db'))
394
- try:
395
- command_history = CommandHistory(db_path)
396
- except Exception as e:
397
- return {"output": f"Error connecting to command history: {e}", "messages": messages}
398
-
399
- try:
400
- # Remove messages from kwargs to avoid duplicate argument error
401
- if 'messages' in kwargs:
402
- del kwargs['messages']
403
-
404
- # Execute the brainblast command
405
- return execute_brainblast_command(
406
- command=search_query,
407
- command_history=command_history,
408
- messages=messages,
409
- top_k=safe_get(kwargs, 'top_k', 5),
410
- **kwargs
411
- )
412
-
413
- except Exception as e:
414
- traceback.print_exc()
415
- return {"output": f"Error executing brainblast command: {e}", "messages": messages}
416
-
417
- @router.route("rag", "Execute a RAG command using ChromaDB embeddings with optional file input (-f/--file)")
418
- def rag_handler(command: str, **kwargs):
419
- messages = safe_get(kwargs, "messages", [])
420
-
421
- # Parse command with shlex to properly handle quoted strings
422
- parts = shlex.split(command)
423
- user_command = []
424
- file_paths = []
425
-
426
- # Process arguments
427
- i = 1 # Skip the first element which is "rag"
428
- while i < len(parts):
429
- if parts[i] == "-f" or parts[i] == "--file":
430
- # We found a file flag, get the file path
431
- if i + 1 < len(parts):
432
- file_paths.append(parts[i + 1])
433
- i += 2 # Skip both the flag and the path
434
- else:
435
- return {"output": "Error: -f/--file flag needs a file path", "messages": messages}
436
- else:
437
- # This is part of the user query
438
- user_command.append(parts[i])
439
- i += 1
440
-
441
- user_command = " ".join(user_command)
442
-
443
- vector_db_path = safe_get(kwargs, "vector_db_path", os.path.expanduser('~/npcsh_chroma.db'))
444
- embedding_model = safe_get(kwargs, "embedding_model", NPCSH_EMBEDDING_MODEL)
445
- embedding_provider = safe_get(kwargs, "embedding_provider", NPCSH_EMBEDDING_PROVIDER)
446
-
447
- if not user_command and not file_paths:
448
- return {"output": "Usage: /rag [-f file_path] <query>", "messages": messages}
449
-
450
- try:
451
- # Process files if provided
452
- file_contents = []
453
- for file_path in file_paths:
454
- try:
455
- chunks = load_file_contents(file_path)
456
- file_name = os.path.basename(file_path)
457
- file_contents.extend([f"[{file_name}] {chunk}" for chunk in chunks])
458
- except Exception as file_err:
459
- file_contents.append(f"Error processing file {file_path}: {str(file_err)}")
460
-
461
- # Execute the RAG command
462
- return execute_rag_command(
463
- command=user_command,
464
- vector_db_path=vector_db_path,
465
- embedding_model=embedding_model,
466
- embedding_provider=embedding_provider,
467
- file_contents=file_contents if file_paths else None,
468
- **kwargs
469
- )
470
-
471
- except Exception as e:
472
- traceback.print_exc()
473
- return {"output": f"Error executing RAG command: {e}", "messages": messages}
474
- @router.route("roll", "generate a video")
475
- def roll_handler(command: str, **kwargs):
476
- messages = safe_get(kwargs, "messages", [])
477
- prompt = " ".join(command.split()[1:])
478
- num_frames = safe_get(kwargs, 'num_frames', 125)
479
- width = safe_get(kwargs, 'width', 256)
480
- height = safe_get(kwargs, 'height', 256)
481
- output_path = safe_get(kwargs, 'output_path', "output.mp4")
482
- if not prompt:
483
- return {"output": "Usage: /roll <your prompt>", "messages": messages}
484
- try:
485
- result = gen_video(
486
- prompt=prompt,
487
- model=safe_get(kwargs, 'model', NPCSH_VISION_MODEL),
488
- provider=safe_get(kwargs, 'provider', NPCSH_VISION_PROVIDER),
489
- npc=safe_get(kwargs, 'npc'),
490
- num_frames = num_frames,
491
- width = width,
492
- height = height,
493
- output_path=output_path,
494
-
495
- **safe_get(kwargs, 'api_kwargs', {})
496
- )
497
- return result
498
- except Exception as e:
499
- traceback.print_exc()
500
- return {"output": f"Error generating video: {e}", "messages": messages}
501
-
502
-
503
- @router.route("sample", "Send a prompt directly to the LLM")
504
- def sample_handler(command: str, **kwargs):
505
- messages = safe_get(kwargs, "messages", [])
506
- prompt = " ".join(command.split()[1:])
507
- if not prompt:
508
- return {"output": "Usage: /sample <your prompt>", "messages": messages}
509
-
510
- try:
511
- result = get_llm_response(
512
- prompt=prompt,
513
- provider=safe_get(kwargs, 'provider'),
514
- model=safe_get(kwargs, 'model'),
515
- images=safe_get(kwargs, 'attachments'),
516
- npc=safe_get(kwargs, 'npc'),
517
- team=safe_get(kwargs, 'team'),
518
- messages=messages,
519
- api_url=safe_get(kwargs, 'api_url'),
520
- api_key=safe_get(kwargs, 'api_key'),
521
- context=safe_get(kwargs, 'context'),
522
- stream=safe_get(kwargs, 'stream')
523
- )
524
- return result
525
- except Exception as e:
526
- traceback.print_exc()
527
- return {"output": f"Error sampling LLM: {e}", "messages": messages}
528
-
529
- @router.route("search", "Execute a web search command")
530
- def search_handler(command: str, **kwargs):
531
- """
532
- Executes a search command.
533
- # search commands will bel ike :
534
- # '/search -p default = google "search term" '
535
- # '/search -p perplexity ..
536
- # '/search -p google ..
537
- # extract provider if its there
538
- # check for either -p or --p
539
- """
540
- messages = safe_get(kwargs, "messages", [])
541
- query = " ".join(command.split()[1:])
542
-
543
- if not query:
544
- return {"output": "Usage: /search <query>", "messages": messages}
545
- search_provider = safe_get(kwargs, 'search_provider', NPCSH_SEARCH_PROVIDER)
546
- try:
547
- search_results = search_web(query, provider=search_provider)
548
- output = "\n".join([f"- {res}" for res in search_results]) if search_results else "No results found."
549
- except Exception as e:
550
- traceback.print_exc()
551
- output = f"Error during web search: {e}"
552
- return {"output": output, "messages": messages}
553
-
554
-
555
-
556
- @router.route("serve", "Set configuration values")
557
- def serve_handler(command: str, **kwargs):
558
- #print('calling serve handler')
559
- #print(kwargs)
560
-
561
- port = safe_get(kwargs, "port", 5337)
562
- #print(port, type(port))
563
- messages = safe_get(kwargs, "messages", [])
564
- cors = safe_get(kwargs, "cors", None)
565
- if cors:
566
- cors_origins = [origin.strip() for origin in cors.split(",")]
567
- else:
568
- cors_origins = None
569
-
570
- start_flask_server(
571
- port=port,
572
- cors_origins=cors_origins,
573
- )
574
-
575
-
576
- return {"output": None, "messages": messages}
577
-
578
- @router.route("set", "Set configuration values")
579
- def set_handler(command: str, **kwargs):
580
- messages = safe_get(kwargs, "messages", [])
581
- parts = command.split(maxsplit=1)
582
- if len(parts) < 2 or '=' not in parts[1]:
583
- return {"output": "Usage: /set <key>=<value>", "messages": messages}
584
-
585
- key_value = parts[1]
586
- key, value = key_value.split('=', 1)
587
- key = key.strip()
588
- value = value.strip().strip('"\'')
589
-
590
- try:
591
- set_npcsh_config_value(key, value)
592
- output = f"Configuration value '{key}' set."
593
- except NameError:
594
- output = "Set function (set_npcsh_config_value) not available."
595
- except Exception as e:
596
- traceback.print_exc()
597
- output = f"Error setting configuration '{key}': {e}"
598
- return {"output": output, "messages": messages}
599
-
600
- @router.route("sleep", "Pause execution for N seconds")
601
- def sleep_handler(command: str, **kwargs):
602
- messages = safe_get(kwargs, "messages", [])
603
- parts = command.split()
604
- try:
605
- seconds = float(parts[1]) if len(parts) > 1 else 1.0
606
- if seconds < 0: raise ValueError("Duration must be non-negative")
607
- time.sleep(seconds)
608
- output = f"Slept for {seconds} seconds."
609
- except (ValueError, IndexError):
610
- output = "Usage: /sleep <seconds>"
611
- except Exception as e:
612
- traceback.print_exc()
613
- output = f"Error during sleep: {e}"
614
- return {"output": output, "messages": messages}
615
-
616
- @router.route("spool", "Enter interactive chat (spool) mode")
617
- def spool_handler(command: str, **kwargs):
618
- try:
619
- return enter_spool_mode(
620
- model=safe_get(kwargs, 'model', NPCSH_CHAT_MODEL),
621
- provider=safe_get(kwargs, 'provider', NPCSH_CHAT_PROVIDER),
622
- npc=safe_get(kwargs, 'npc'),
623
- messages=safe_get(kwargs, 'messages'),
624
- conversation_id=safe_get(kwargs, 'conversation_id'),
625
- stream=safe_get(kwargs, 'stream', NPCSH_STREAM_OUTPUT),
626
- files=safe_get(kwargs, 'files'),
627
- )
628
- except Exception as e:
629
- traceback.print_exc()
630
- return {"output": f"Error entering spool mode: {e}", "messages": safe_get(kwargs, "messages", [])}
631
-
632
-
633
- @router.route("jinxs", "Show available jinxs for the current NPC/Team")
634
- def jinxs_handler(command: str, **kwargs):
635
- npc = safe_get(kwargs, 'npc')
636
- team = safe_get(kwargs, 'team')
637
- output = "Available Jinxs:\n"
638
- jinxs_listed = set()
639
-
640
- def format_jinx(name, jinx_obj):
641
- desc = getattr(jinx_obj, 'description', 'No description available.')
642
- return f"- /{name}: {desc}\n"
643
-
644
- if npc and isinstance(npc, NPC) and hasattr(npc, 'jinxs_dict') and npc.jinxs_dict:
645
- output += f"\n--- Jinxs for NPC: {npc.name} ---\n"
646
- for name, jinx in sorted(npc.jinxs_dict.items()):
647
- output += format_jinx(name, jinx)
648
- jinxs_listed.add(name)
649
-
650
- if team and hasattr(team, 'jinxs_dict') and team.jinxs_dict:
651
- team_has_jinxs = False
652
- team_output = ""
653
- for name, jinx in sorted(team.jinxs_dict.items()):
654
- if name not in jinxs_listed:
655
- team_output += format_jinx(name, jinx)
656
- team_has_jinxs = True
657
- if team_has_jinxs:
658
- output += f"\n--- Jinxs for Team: {getattr(team, 'name', 'Unnamed Team')} ---\n"
659
- output += team_output
660
-
661
- if not jinxs_listed and not (team and hasattr(team, 'jinxs_dict') and team.jinxs_dict):
662
- output = "No jinxs available for the current context."
663
-
664
- return {"output": output.strip(), "messages": safe_get(kwargs, "messages", [])}
665
-
666
- @router.route("trigger", "Execute a trigger command")
667
- def trigger_handler(command: str, **kwargs):
668
- messages = safe_get(kwargs, "messages", [])
669
- user_command = " ".join(command.split()[1:])
670
- if not user_command:
671
- return {"output": "Usage: /trigger <trigger_description>", "messages": messages}
672
- try:
673
- return execute_trigger_command(command=user_command, **kwargs)
674
- except NameError:
675
- return {"output": "Trigger function (execute_trigger_command) not available.", "messages": messages}
676
- except Exception as e:
677
- traceback.print_exc()
678
- return {"output": f"Error executing trigger: {e}", "messages": messages}
679
- @router.route("vixynt", "Generate images from text descriptions")
680
- def vixynt_handler(command: str, **kwargs):
681
- npc = safe_get(kwargs, 'npc')
682
- model = safe_get(kwargs, 'model', NPCSH_IMAGE_GEN_MODEL)
683
- provider = safe_get(kwargs, 'provider', NPCSH_IMAGE_GEN_PROVIDER)
684
- height = safe_get(kwargs, 'height', 1024)
685
- width = safe_get(kwargs, 'width', 1024)
686
- filename = safe_get(kwargs, 'output_filename', None)
687
- attachments = None
688
- if model == NPCSH_CHAT_MODEL: model = NPCSH_IMAGE_GEN_MODEL
689
- if provider == NPCSH_CHAT_PROVIDER: provider = NPCSH_IMAGE_GEN_PROVIDER
690
-
691
- messages = safe_get(kwargs, 'messages', [])
692
-
693
- filename = None
694
-
695
- prompt_parts = []
696
- try:
697
- parts = shlex.split(command)
698
- for part in parts[1:]:
699
- if part.startswith("filename="):
700
- filename = part.split("=", 1)[1]
701
- elif part.startswith("height="):
702
- try:
703
- height = int(part.split("=", 1)[1])
704
- except ValueError:
705
- pass
706
- elif part.startswith("width="):
707
- try:
708
- width = int(part.split("=", 1)[1])
709
- except ValueError:
710
- pass
711
- elif part.startswith("attachments="): # New parameter for image editing
712
- # split at comma
713
- attachments = part.split("=", 1)[1].split(",")
714
-
715
- else:
716
- prompt_parts.append(part)
717
- except Exception as parse_err:
718
- return {"output": f"Error parsing arguments: {parse_err}. Usage: /vixynt <prompt> [filename=...] [height=...] [width=...] [input=...for editing]", "messages": messages}
719
- user_prompt = " ".join(prompt_parts)
720
- if not user_prompt:
721
- return {"output": "Usage: /vixynt <prompt> [filename=...] [height=...] [width=...] [attachments=... for editing]", "messages": messages}
722
-
723
- try:
724
- image = gen_image(
725
- prompt=user_prompt,
726
- model=model,
727
- provider=provider,
728
- npc=npc,
729
- height=height,
730
- width=width,
731
- input_images=attachments
732
- )
733
- if filename is None:
734
- # Generate a filename based on the prompt and the date time
735
- os.makedirs(os.path.expanduser("~/.npcsh/images/"), exist_ok=True)
736
- filename = (
737
- os.path.expanduser("~/.npcsh/images/")
738
- + f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
739
- )
740
- image.save(filename)
741
- image.show()
742
-
743
- if attachments:
744
- output = f"Image edited and saved to: {filename}"
745
- else:
746
- output = f"Image generated and saved to: {filename}"
747
- except Exception as e:
748
- traceback.print_exc()
749
- output = f"Error {'editing' if attachments else 'generating'} image: {e}"
750
-
751
- return {"output": output, "messages": messages}
752
- # --- THIS IS THE FINAL, CORRECTED wander_handler in routes.py ---
753
- @router.route("wander", "Enter wander mode (experimental)")
754
- def wander_handler(command: str, **kwargs):
755
- messages = safe_get(kwargs, "messages", [])
756
-
757
- # General parser for key=value arguments
758
- try:
759
- parts = shlex.split(command)
760
- problem_parts = []
761
- wander_params = {}
762
-
763
- i = 1 # Start after the 'wander' command name
764
- while i < len(parts):
765
- part = parts[i]
766
-
767
- if '=' in part:
768
- # This is the start of a key=value pair
769
- key, initial_value = part.split('=', 1)
770
-
771
- # Consume all subsequent parts that do NOT contain '=' as part of this value
772
- value_parts = [initial_value]
773
- j = i + 1
774
- while j < len(parts) and '=' not in parts[j]:
775
- value_parts.append(parts[j])
776
- j += 1
777
-
778
- # Join the reconstructed value and store it
779
- wander_params[key] = " ".join(value_parts)
780
- # Advance the main loop index past the consumed parts
781
- i = j
782
- else:
783
- # This part belongs to the problem string
784
- problem_parts.append(part)
785
- i += 1
786
-
787
- problem = " ".join(problem_parts)
788
- except Exception as e:
789
- return {"output": f"Error parsing arguments: {e}", "messages": messages}
790
-
791
- if not problem:
792
- return {"output": "Usage: /wander <problem> [key=value...]", "messages": messages}
793
-
794
- try:
795
- # Build the argument list for enter_wander_mode
796
- mode_args = {
797
- 'problem': problem,
798
- 'npc': safe_get(kwargs, 'npc'),
799
- 'model': safe_get(kwargs, 'model'),
800
- 'provider': safe_get(kwargs, 'provider'),
801
- # Use parsed params with defaults
802
- 'environment': wander_params.get('environment'),
803
- 'low_temp': float(wander_params.get('low-temp', 0.5)),
804
- 'high_temp': float(wander_params.get('high-temp', 1.9)),
805
- 'interruption_likelihood': float(wander_params.get('interruption-likelihood', 1)),
806
- 'sample_rate': float(wander_params.get('sample-rate', 0.4)),
807
- 'n_high_temp_streams': int(wander_params.get('n-high-temp-streams', 5)),
808
- 'include_events': bool(wander_params.get('include-events', False)),
809
- 'num_events': int(wander_params.get('num-events', 3))
810
- }
811
-
812
- result = enter_wander_mode(**mode_args)
813
-
814
- if isinstance(result, list) and result:
815
- output = result[-1].get("insight", "Wander mode session complete.")
816
- else:
817
- output = str(result) if result else "Wander mode session complete."
818
-
819
- messages.append({"role": "assistant", "content": output})
820
- return {"output": output, "messages": messages}
821
-
822
- except Exception as e:
823
- traceback.print_exc()
824
- return {"output": f"Error during wander mode: {e}", "messages": messages}
825
-
826
- @router.route("yap", "Enter voice chat (yap) mode", shell_only=True)
827
- def whisper_handler(command: str, **kwargs):
828
- try:
829
- return enter_yap_mode(
830
- messages=safe_get(kwargs, 'messages'),
831
- npc=safe_get(kwargs, 'npc'),
832
- model=safe_get(kwargs, 'model', NPCSH_CHAT_MODEL),
833
- provider=safe_get(kwargs, 'provider', NPCSH_CHAT_PROVIDER),
834
- team=safe_get(kwargs, 'team'),
835
- stream=safe_get(kwargs, 'stream', NPCSH_STREAM_OUTPUT),
836
- conversation_id=safe_get(kwargs, 'conversation_id')
837
- )
838
- except Exception as e:
839
- traceback.print_exc()
840
- return {"output": f"Error entering yap mode: {e}", "messages": safe_get(kwargs, "messages", [])}
841
-
842
- @router.route("alicanto", "Conduct deep research with multiple perspectives, identifying gold insights and cliff warnings")
843
- def alicanto_handler(command: str, **kwargs):
844
- messages = safe_get(kwargs, "messages", [])
845
-
846
- # Parse command with shlex to properly handle quoted strings
847
- parts = shlex.split(command)
848
-
849
- # Process arguments
850
- query = ""
851
- num_npcs = safe_get(kwargs, 'num_npcs', 5)
852
- depth = safe_get(kwargs, 'depth', 3)
853
- exploration_factor = safe_get(kwargs, 'exploration', 0.3)
854
- creativity_factor = safe_get(kwargs, 'creativity', 0.5)
855
- output_format = safe_get(kwargs, 'format', 'report')
856
-
857
- # Parse command-line arguments
858
- i = 1 # Skip "alicanto" command
859
- while i < len(parts):
860
- if parts[i].startswith('--'):
861
- option = parts[i][2:] # Remove '--'
862
- if option in ['num-npcs', 'npcs']:
863
- if i + 1 < len(parts) and parts[i + 1].isdigit():
864
- num_npcs = int(parts[i + 1])
865
- i += 2
866
- else:
867
- i += 1
868
- elif option in ['depth', 'd']:
869
- if i + 1 < len(parts) and parts[i + 1].isdigit():
870
- depth = int(parts[i + 1])
871
- i += 2
872
- else:
873
- i += 1
874
- elif option in ['exploration', 'e']:
875
- if i + 1 < len(parts) and parts[i + 1].replace('.', '', 1).isdigit():
876
- exploration_factor = float(parts[i + 1])
877
- i += 2
878
- else:
879
- i += 1
880
- elif option in ['creativity', 'c']:
881
- if i + 1 < len(parts) and parts[i + 1].replace('.', '', 1).isdigit():
882
- creativity_factor = float(parts[i + 1])
883
- i += 2
884
- else:
885
- i += 1
886
- elif option in ['format', 'f']:
887
- if i + 1 < len(parts):
888
- output_format = parts[i + 1]
889
- i += 2
890
- else:
891
- i += 1
892
- else:
893
- # Skip unknown option
894
- i += 1
895
- else:
896
- # This is part of the request
897
- query += parts[i] + " "
898
- i += 1
899
-
900
- query = query.strip()
901
-
902
- # Also apply any kwargs that were passed directly (these override command line args)
903
- if 'num_npcs' in kwargs:
904
- try:
905
- num_npcs = int(kwargs['num_npcs'])
906
- except ValueError:
907
- return {"output": "Error: num_npcs must be an integer", "messages": messages}
908
-
909
- if 'depth' in kwargs:
910
- try:
911
- depth = int(kwargs['depth'])
912
- except ValueError:
913
- return {"output": "Error: depth must be an integer", "messages": messages}
914
-
915
- if 'exploration' in kwargs:
916
- try:
917
- exploration_factor = float(kwargs['exploration'])
918
- except ValueError:
919
- return {"output": "Error: exploration must be a float", "messages": messages}
920
-
921
- if 'creativity' in kwargs:
922
- try:
923
- creativity_factor = float(kwargs['creativity'])
924
- except ValueError:
925
- return {"output": "Error: creativity must be a float", "messages": messages}
926
-
927
- if not query:
928
- return {"output": "Usage: /alicanto <research query> [--num-npcs N] [--depth N] [--exploration 0.3] [--creativity 0.5] [--format report|summary|full]", "messages": messages}
929
-
930
- try:
931
- logging.info(f"Starting Alicanto research on: {query}")
932
- result = alicanto(
933
- request=query,
934
- num_npcs=num_npcs,
935
- depth=depth,
936
- memory=3,
937
- context=None,
938
- model=safe_get(kwargs, 'model', NPCSH_CHAT_MODEL),
939
- provider=safe_get(kwargs, 'provider', NPCSH_CHAT_PROVIDER),
940
- exploration_factor=exploration_factor,
941
- creativity_factor=creativity_factor,
942
- output_format=output_format
943
- )
944
-
945
- # Format the output based on the result type
946
- if isinstance(result, dict):
947
- if "integration" in result:
948
- output = result["integration"]
949
- else:
950
- output = "Alicanto research completed. Full results available in returned data."
951
- else:
952
- output = result
953
-
954
- return {"output": output, "messages": messages, "alicanto_result": result}
955
- except Exception as e:
956
- traceback.print_exc()
957
- logging.error(f"Error during Alicanto research: {e}")
958
- return {"output": f"Error during Alicanto research: {e}", "messages": messages}