pygpt-net 2.6.3__py3-none-any.whl → 2.6.6__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 (78) hide show
  1. pygpt_net/CHANGELOG.txt +14 -0
  2. pygpt_net/__init__.py +2 -2
  3. pygpt_net/app.py +6 -1
  4. pygpt_net/config.py +55 -65
  5. pygpt_net/controller/__init__.py +5 -2
  6. pygpt_net/controller/chat/chat.py +38 -35
  7. pygpt_net/controller/chat/render.py +144 -217
  8. pygpt_net/controller/chat/stream.py +52 -25
  9. pygpt_net/controller/config/config.py +39 -42
  10. pygpt_net/controller/config/field/checkbox.py +16 -12
  11. pygpt_net/controller/config/field/checkbox_list.py +36 -31
  12. pygpt_net/controller/config/field/cmd.py +51 -57
  13. pygpt_net/controller/config/field/combo.py +33 -16
  14. pygpt_net/controller/config/field/dictionary.py +48 -55
  15. pygpt_net/controller/config/field/input.py +50 -32
  16. pygpt_net/controller/config/field/slider.py +40 -45
  17. pygpt_net/controller/config/field/textarea.py +20 -6
  18. pygpt_net/controller/config/placeholder.py +110 -231
  19. pygpt_net/controller/ctx/common.py +48 -49
  20. pygpt_net/controller/ctx/ctx.py +24 -4
  21. pygpt_net/controller/lang/mapping.py +57 -95
  22. pygpt_net/controller/lang/plugins.py +64 -55
  23. pygpt_net/controller/lang/settings.py +39 -38
  24. pygpt_net/controller/layout/layout.py +11 -2
  25. pygpt_net/controller/plugins/plugins.py +19 -1
  26. pygpt_net/controller/settings/profile.py +16 -4
  27. pygpt_net/controller/ui/mode.py +107 -125
  28. pygpt_net/core/bridge/bridge.py +5 -5
  29. pygpt_net/core/command/command.py +149 -219
  30. pygpt_net/core/ctx/ctx.py +94 -146
  31. pygpt_net/core/debug/debug.py +48 -58
  32. pygpt_net/core/models/models.py +74 -112
  33. pygpt_net/core/modes/modes.py +13 -21
  34. pygpt_net/core/plugins/plugins.py +154 -177
  35. pygpt_net/core/presets/presets.py +103 -176
  36. pygpt_net/core/render/web/body.py +50 -39
  37. pygpt_net/core/render/web/renderer.py +154 -251
  38. pygpt_net/core/text/utils.py +28 -44
  39. pygpt_net/core/tokens/tokens.py +104 -203
  40. pygpt_net/data/config/config.json +3 -3
  41. pygpt_net/data/config/models.json +3 -3
  42. pygpt_net/item/ctx.py +141 -139
  43. pygpt_net/plugin/agent/plugin.py +2 -1
  44. pygpt_net/plugin/audio_output/plugin.py +5 -2
  45. pygpt_net/plugin/base/plugin.py +77 -93
  46. pygpt_net/plugin/bitbucket/plugin.py +3 -2
  47. pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
  48. pygpt_net/plugin/cmd_custom/plugin.py +3 -2
  49. pygpt_net/plugin/cmd_files/plugin.py +3 -2
  50. pygpt_net/plugin/cmd_history/plugin.py +3 -2
  51. pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
  52. pygpt_net/plugin/cmd_serial/plugin.py +3 -2
  53. pygpt_net/plugin/cmd_system/plugin.py +3 -6
  54. pygpt_net/plugin/cmd_web/plugin.py +3 -2
  55. pygpt_net/plugin/experts/plugin.py +2 -2
  56. pygpt_net/plugin/facebook/plugin.py +3 -4
  57. pygpt_net/plugin/github/plugin.py +4 -2
  58. pygpt_net/plugin/google/plugin.py +3 -3
  59. pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
  60. pygpt_net/plugin/mailer/plugin.py +3 -5
  61. pygpt_net/plugin/openai_vision/plugin.py +3 -2
  62. pygpt_net/plugin/real_time/plugin.py +52 -60
  63. pygpt_net/plugin/slack/plugin.py +3 -4
  64. pygpt_net/plugin/telegram/plugin.py +3 -4
  65. pygpt_net/plugin/twitter/plugin.py +3 -4
  66. pygpt_net/tools/code_interpreter/tool.py +0 -1
  67. pygpt_net/tools/translator/tool.py +1 -1
  68. pygpt_net/ui/layout/ctx/ctx_list.py +10 -6
  69. pygpt_net/ui/main.py +46 -30
  70. pygpt_net/ui/tray.py +61 -60
  71. pygpt_net/ui/widget/lists/context.py +2 -2
  72. pygpt_net/ui/widget/textarea/web.py +161 -48
  73. pygpt_net/utils.py +8 -1
  74. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.6.dist-info}/METADATA +16 -2
  75. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.6.dist-info}/RECORD +78 -78
  76. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.6.dist-info}/LICENSE +0 -0
  77. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.6.dist-info}/WHEEL +0 -0
  78. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.6.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.07.28 00:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -28,6 +28,8 @@ from pygpt_net.item.model import ModelItem
28
28
 
29
29
  class Command:
30
30
  DESC_LIMIT = 1024
31
+ _RE_CMD_PRESENCE = re.compile(r'<tool>\s*{.*}\s*</tool>', re.DOTALL)
32
+ _RE_TOOL_BLOCKS = re.compile(r'<tool>(.*?)</tool>', re.DOTALL)
31
33
 
32
34
  def __init__(self, window=None):
33
35
  """
@@ -52,14 +54,15 @@ class Command:
52
54
  :return: prompt with appended syntax
53
55
  """
54
56
  prompt = data['prompt']
55
- cmd_prompt = self.window.core.prompt.get('cmd')
57
+ core = self.window.core
58
+ cmd_prompt = core.prompt.get('cmd')
56
59
  extra = ""
57
60
  schema = self.extract_syntax(data['cmd'])
58
61
  if schema:
59
- if self.window.core.config.get('mode') == MODE_ASSISTANT:
60
- extra = self.window.core.prompt.get('cmd.extra.assistants') # Assistants API env fix
62
+ if core.config.get('mode') == MODE_ASSISTANT:
63
+ extra = core.prompt.get('cmd.extra.assistants')
61
64
  else:
62
- extra = self.window.core.prompt.get('cmd.extra')
65
+ extra = core.prompt.get('cmd.extra')
63
66
  if prompt.strip() != "":
64
67
  prompt += "\n\n"
65
68
  prompt += cmd_prompt.strip().replace("{extra}", extra).replace("{schema}", schema)
@@ -75,49 +78,39 @@ class Command:
75
78
  :param cmds: commands list
76
79
  :return: JSON string with commands usage syntax
77
80
  """
78
- data = {}
79
- cmds = copy.deepcopy(cmds) # make copy to prevent changes in original data
80
- self.window.core.ctx.current_cmd = copy.deepcopy(cmds) # for debug purposes
81
+ data: Dict[str, Any] = {}
82
+ self.window.core.ctx.current_cmd = copy.deepcopy(cmds)
81
83
 
82
84
  for cmd in cmds:
83
85
  if "cmd" in cmd and "instruction" in cmd:
84
86
  cmd_name = cmd["cmd"]
85
- data[cmd_name] = {
86
- "help": cmd["instruction"],
87
- }
87
+ data_cmd = {"help": cmd["instruction"]}
88
88
  if "params" in cmd and len(cmd["params"]) > 0:
89
- data[cmd_name]["params"] = {}
89
+ params_out: Dict[str, Any] = {}
90
90
  for param in cmd["params"]:
91
91
  try:
92
- if isinstance(param, dict):
93
- if "name" in param:
94
- # assign param
95
- key = param["name"]
96
- del param["name"]
97
- data[cmd_name]["params"][key] = param
98
-
99
- # remove required, leave only optional
100
- if "required" in data[cmd_name]["params"][key]:
101
- if data[cmd_name]["params"][key]["required"] is False:
102
- data[cmd_name]["params"][key]["optional"] = True
103
- del data[cmd_name]["params"][key]["required"]
104
-
105
- # remove type if str (default)
106
- if ("type" in data[cmd_name]["params"][key]
107
- and data[cmd_name]["params"][key]["type"] == "str"):
108
- del data[cmd_name]["params"][key]["type"]
109
-
110
- # remove description and move to help
111
- if "description" in data[cmd_name]["params"][key]:
112
- data[cmd_name]["params"][key]["help"] = data[cmd_name]["params"][key][
113
- "description"]
114
- del data[cmd_name]["params"][key]["description"]
115
-
116
- except Exception as e:
92
+ if isinstance(param, dict) and "name" in param:
93
+ key = param["name"]
94
+ p = dict(param)
95
+ p.pop("name", None)
96
+ if "required" in p:
97
+ if p["required"] is False:
98
+ p["optional"] = True
99
+ p.pop("required", None)
100
+ if p.get("type") == "str":
101
+ p.pop("type", None)
102
+ if "description" in p:
103
+ p["help"] = p["description"]
104
+ p.pop("description", None)
105
+ params_out[key] = p
106
+ except Exception:
117
107
  pass
108
+ if params_out:
109
+ data_cmd["params"] = params_out
110
+ data[cmd_name] = data_cmd
118
111
 
119
- self.window.core.ctx.current_cmd_schema = data # for debug
120
- return json.dumps(data) # pack, return JSON string without indent and formatting
112
+ self.window.core.ctx.current_cmd_schema = data
113
+ return json.dumps(data)
121
114
 
122
115
  def has_cmds(self, text: str) -> bool:
123
116
  """
@@ -128,8 +121,7 @@ class Command:
128
121
  """
129
122
  if text is None:
130
123
  return False
131
- regex_cmd = r'<tool>\s*{.*}\s*</tool>'
132
- return bool(re.search(regex_cmd, text))
124
+ return bool(self._RE_CMD_PRESENCE.search(text))
133
125
 
134
126
  def extract_cmds(self, text: str) -> List[Dict[str, Any]]:
135
127
  """
@@ -138,15 +130,14 @@ class Command:
138
130
  :param text: text to extract commands from
139
131
  :return: list of commands (dict)
140
132
  """
141
- cmds = []
133
+ cmds: List[Dict[str, Any]] = []
142
134
  try:
143
- chunks = re.findall(r'<tool>(.*?)</tool>', text, re.DOTALL)
135
+ chunks = self._RE_TOOL_BLOCKS.findall(text)
144
136
  for chunk in chunks:
145
- cmd = self.extract_cmd(chunk) # extract JSON string to dict
137
+ cmd = self.extract_cmd(chunk)
146
138
  if cmd is not None:
147
- cmds.append(cmd) # cmd = dict
148
- except Exception as e:
149
- # do nothing
139
+ cmds.append(cmd)
140
+ except Exception:
150
141
  pass
151
142
  return cmds
152
143
 
@@ -161,11 +152,7 @@ class Command:
161
152
  chunk = chunk.strip()
162
153
  if chunk and chunk.startswith('{') and chunk.endswith('}'):
163
154
  try:
164
- # syntax1: {"read_file": {"path": ["my_cars.txt"]}}
165
- # syntax2: {"cmd": "read_file", "params": {"path": ["my_cars.txt"]}}
166
155
  cmd = json.loads(chunk)
167
-
168
- # if the first key is not "cmd", then try to convert from incorrect syntax into "cmd" syntax:
169
156
  if "cmd" not in cmd:
170
157
  if len(cmd) == 1:
171
158
  for key in cmd:
@@ -179,8 +166,7 @@ class Command:
179
166
  "cmd": key,
180
167
  "params": cmd[key]
181
168
  }
182
- except json.JSONDecodeError as e:
183
- # do nothing
169
+ except json.JSONDecodeError:
184
170
  pass
185
171
  return cmd
186
172
 
@@ -194,11 +180,7 @@ class Command:
194
180
  :param cmds: commands list
195
181
  :return parsed commands
196
182
  """
197
- commands = []
198
- for cmd in cmds:
199
- if 'cmd' in cmd:
200
- commands.append(cmd)
201
- return commands
183
+ return [cmd for cmd in cmds if 'cmd' in cmd]
202
184
 
203
185
  def unpack_tool_calls(
204
186
  self,
@@ -290,11 +272,9 @@ class Command:
290
272
  ctx.tool_calls = tmp_calls
291
273
 
292
274
  if append_output:
293
- # append tool calls to context output
294
275
  ctx.extra["tool_calls"] = ctx.tool_calls
295
276
  ctx.extra["tool_output"] = []
296
277
 
297
-
298
278
  def unpack_tool_calls_from_llama(
299
279
  self,
300
280
  tool_calls: List
@@ -348,10 +328,7 @@ class Command:
348
328
  :param tool_calls: tool calls
349
329
  :return: commands
350
330
  """
351
- cmds = []
352
- for tool_call in tool_calls:
353
- cmds.append(self.tool_call_to_cmd(tool_call))
354
- return cmds
331
+ return [self.tool_call_to_cmd(tool_call) for tool_call in tool_calls]
355
332
 
356
333
  def pack_cmds(
357
334
  self,
@@ -363,10 +340,10 @@ class Command:
363
340
  :param cmds: commands
364
341
  :return: packed commands string
365
342
  """
366
- packed = ""
367
- for cmd in cmds:
368
- packed += "<tool>" + json.dumps(cmd) + "</tool>"
369
- return packed
343
+ return "".join(
344
+ "<tool>" + json.dumps(cmd, separators=(',', ':')) + "</tool>"
345
+ for cmd in cmds
346
+ )
370
347
 
371
348
  def append_tool_calls(self, ctx: CtxItem):
372
349
  """
@@ -374,8 +351,7 @@ class Command:
374
351
 
375
352
  :param ctx: context item
376
353
  """
377
- cmds = self.tool_calls_to_cmds(ctx.tool_calls)
378
- cmds_str = self.pack_cmds(cmds)
354
+ cmds_str = self.pack_cmds(self.tool_calls_to_cmds(ctx.tool_calls))
379
355
  if ctx.output is None:
380
356
  ctx.output = ""
381
357
  ctx.output += cmds_str
@@ -387,32 +363,22 @@ class Command:
387
363
  :param ctx: context item
388
364
  :return: list of tool calls outputs
389
365
  """
390
- outputs = []
391
- idx_outputs = {}
392
- for tool_call in ctx.tool_calls:
393
- call_id = tool_call["id"]
394
- idx_outputs[call_id] = ""
366
+ idx_outputs = {tool_call["id"]: "" for tool_call in ctx.tool_calls}
395
367
  try:
396
- responses = json.loads(ctx.input) # response is JSON string in input
368
+ responses = json.loads(ctx.input)
369
+ last_by_name: Dict[str, Any] = {}
370
+ for response in responses:
371
+ req = response.get("request")
372
+ if isinstance(req, dict) and "cmd" in req:
373
+ last_by_name[req["cmd"]] = response.get("result")
397
374
  for tool_call in ctx.tool_calls:
398
- call_id = tool_call["id"]
399
- for response in responses:
400
- if "request" in response:
401
- if "cmd" in response["request"]:
402
- func_name = response["request"]["cmd"]
403
- if tool_call["function"]["name"] == func_name:
404
- idx_outputs[call_id] = response["result"]
405
-
375
+ name = tool_call["function"]["name"]
376
+ if name in last_by_name:
377
+ idx_outputs[tool_call["id"]] = last_by_name[name]
406
378
  except Exception as e:
407
379
  self.window.core.debug.log(e)
408
380
 
409
- for id in idx_outputs:
410
- data = {
411
- "tool_call_id": id,
412
- "output": idx_outputs[id],
413
- }
414
- outputs.append(data)
415
-
381
+ outputs = [{"tool_call_id": id_, "output": out} for id_, out in idx_outputs.items()]
416
382
  if not isinstance(ctx.extra, dict):
417
383
  ctx.extra = {}
418
384
  ctx.extra["tool_calls_outputs"] = outputs
@@ -436,7 +402,7 @@ class Command:
436
402
  func = self.as_native_functions(all=False, parent_id=parent_id)
437
403
  if func_user is None:
438
404
  func_user = []
439
- return func + func_user # merge both
405
+ return func + func_user
440
406
 
441
407
  def as_native_functions(
442
408
  self,
@@ -488,14 +454,14 @@ class Command:
488
454
  event = Event(Event.CMD_SYNTAX_INLINE, data)
489
455
  self.window.dispatch(event)
490
456
 
491
- cmds = copy.deepcopy(data['cmd']) # make copy to prevent changes in original plugins cmd
492
- func_plugins = self.cmds_to_functions(cmds) # plugin functions
457
+ cmds = copy.deepcopy(data['cmd'])
458
+ func_plugins = self.cmds_to_functions(cmds)
493
459
  if self.window.controller.agent.legacy.enabled():
494
- func_agent = self.cmds_to_functions(self.window.controller.agent.legacy.get_functions()) # agent functions
460
+ func_agent = self.cmds_to_functions(self.window.controller.agent.legacy.get_functions())
495
461
  if (self.window.controller.agent.experts.enabled()
496
462
  or self.window.controller.agent.legacy.enabled(check_inline=False)):
497
- if parent_id is None: # don't append expert call tool for experts
498
- func_experts = self.cmds_to_functions(self.window.core.experts.get_functions()) # agent functions
463
+ if parent_id is None:
464
+ func_experts = self.cmds_to_functions(self.window.core.experts.get_functions())
499
465
  return func_plugins + func_agent + func_experts
500
466
 
501
467
  def cmds_to_functions(
@@ -509,17 +475,18 @@ class Command:
509
475
  :return: functions list
510
476
  """
511
477
  functions = []
478
+ limit = self.DESC_LIMIT
512
479
  for cmd in cmds:
513
480
  if "cmd" in cmd and "instruction" in cmd:
514
481
  cmd_name = cmd["cmd"]
515
482
  desc = cmd["instruction"]
516
- if len(desc) > self.DESC_LIMIT:
517
- desc = desc[:self.DESC_LIMIT] # limit description to 1024 characters
483
+ if len(desc) > limit:
484
+ desc = desc[:limit]
518
485
  functions.append(
519
486
  {
520
487
  "name": cmd_name,
521
488
  "desc": desc,
522
- "params": json.dumps(self.extract_params(cmd), indent=4),
489
+ "params": json.dumps(self.extract_params(cmd), separators=(',', ':')),
523
490
  }
524
491
  )
525
492
  return functions
@@ -541,84 +508,67 @@ class Command:
541
508
  "required": [],
542
509
  "additionalProperties": False,
543
510
  }
544
- if "params" in cmd and len(cmd["params"]) > 0:
545
- for param in cmd["params"]:
546
- # add required params
547
- if "required" in param and param["required"]:
511
+ params_list = cmd.get("params") or []
512
+ if params_list:
513
+ for param in params_list:
514
+ if isinstance(param, dict) and param.get("required"):
548
515
  required.append(param["name"])
549
516
 
550
- if len(required) > 0:
517
+ if required:
551
518
  params["required"] = required
552
519
 
553
- # extract params and convert to JSON schema format
554
- for param in cmd["params"]:
520
+ type_map = {
521
+ "str": "string",
522
+ "enum": "string",
523
+ "text": "string",
524
+ "int": "integer",
525
+ "bool": "boolean",
526
+ "dict": "object",
527
+ "list": "array",
528
+ }
529
+
530
+ limit = self.DESC_LIMIT
531
+ for param in params_list:
555
532
  try:
556
- if isinstance(param, dict):
557
- if "name" in param:
558
- key = param["name"]
559
- params["properties"][key] = {}
560
- params["properties"][key]["type"] = "string"
561
- params["properties"][key]["description"] = ""
562
-
563
- # add required fields
564
- if "type" in param:
565
- params["properties"][key]["type"] = param["type"]
566
- if "description" in param:
567
- desc = param["description"]
568
- if len(desc) > self.DESC_LIMIT:
569
- desc = desc[:self.DESC_LIMIT] # limit description to 1024 characters
570
- params["properties"][key]["description"] = desc
571
-
572
- # append enum if exists
573
- if "enum" in param:
574
- params["properties"][key]["description"] += ", enum: " + json.dumps(
575
- param["enum"]) + ")"
576
- # get dict keys from param["enum"][key] as list:
577
- values = []
578
- if key in param["enum"]:
579
- if isinstance(param["enum"][key], dict): # check for sub-dicts
580
- values = list(param["enum"][key].keys())
581
- sub_values = []
582
- for k in param["enum"][key]:
583
- if isinstance(param["enum"][key][k], dict):
584
- for v in list(param["enum"][key][k].keys()):
585
- if v not in sub_values:
586
- sub_values.append(v)
587
- elif isinstance(param["enum"][key][k], list):
588
- for v in param["enum"][key][k]:
589
- if v not in sub_values:
590
- sub_values.append(v)
591
- if len(sub_values) > 0:
592
- values = sub_values
593
- elif isinstance(param["enum"][key], list):
594
- values = param["enum"][key]
595
-
596
- if values:
597
- params["properties"][key]["enum"] = values
598
-
599
- # remove defaults and append to description
600
- if "default" in param:
601
- params["properties"][key]["description"] += ", default: " + str(
602
- param["default"]) + ")"
603
-
604
- # convert internal types to supported by JSON schema
605
- if params["properties"][key]["type"] == "str":
606
- params["properties"][key]["type"] = "string"
607
- elif params["properties"][key]["type"] == "enum":
608
- params["properties"][key]["type"] = "string"
609
- elif params["properties"][key]["type"] == "text":
610
- params["properties"][key]["type"] = "string"
611
- elif params["properties"][key]["type"] == "int":
612
- params["properties"][key]["type"] = "integer"
613
- elif params["properties"][key]["type"] == "bool":
614
- params["properties"][key]["type"] = "boolean"
615
- elif params["properties"][key]["type"] == "dict":
616
- params["properties"][key]["type"] = "object"
617
- elif params["properties"][key]["type"] == "list":
618
- params["properties"][key]["type"] = "array"
619
- params["properties"][key]["items"] = {
620
- "type": "string"
621
- }
533
+ if isinstance(param, dict) and "name" in param:
534
+ key = param["name"]
535
+ prop: Dict[str, Any] = {}
536
+ prop["type"] = param.get("type", "string")
537
+ prop["description"] = ""
538
+ if "type" in param and param["type"] in type_map:
539
+ prop["type"] = type_map[param["type"]]
540
+ if "description" in param:
541
+ desc = param["description"]
542
+ if len(desc) > limit:
543
+ desc = desc[:limit]
544
+ prop["description"] = desc
545
+ if "enum" in param:
546
+ prop["description"] += ", enum: " + json.dumps(param["enum"]) + ")"
547
+ values = []
548
+ enum_block = param["enum"].get(key) if isinstance(param["enum"], dict) else None
549
+ if isinstance(enum_block, dict):
550
+ values = list(enum_block.keys())
551
+ sub_values = []
552
+ for k in enum_block:
553
+ if isinstance(enum_block[k], dict):
554
+ for v in list(enum_block[k].keys()):
555
+ if v not in sub_values:
556
+ sub_values.append(v)
557
+ elif isinstance(enum_block[k], list):
558
+ for v in enum_block[k]:
559
+ if v not in sub_values:
560
+ sub_values.append(v)
561
+ if sub_values:
562
+ values = sub_values
563
+ elif isinstance(enum_block, list):
564
+ values = enum_block
565
+ if values:
566
+ prop["enum"] = values
567
+ if "default" in param:
568
+ prop["description"] += ", default: " + str(param["default"]) + ")"
569
+ if prop["type"] == "array":
570
+ prop["items"] = {"type": "string"}
571
+ params["properties"][key] = prop
622
572
  except Exception as e:
623
573
  print(e)
624
574
  pass
@@ -638,12 +588,10 @@ class Command:
638
588
  ]
639
589
  mode = self.window.core.config.get('mode')
640
590
 
641
- # force disabled for specific modes
642
591
  if mode in disabled_modes:
643
592
  return False
644
593
 
645
594
  if not force:
646
- # check model
647
595
  model = self.window.core.config.get('model')
648
596
  if model:
649
597
  model_data = self.window.core.models.get(model)
@@ -651,13 +599,11 @@ class Command:
651
599
  if not self.window.core.models.is_tool_call_allowed(mode, model_data):
652
600
  return False
653
601
 
654
- # agents and experts
655
602
  if self.window.controller.agent.legacy.enabled():
656
603
  return self.window.core.config.get('agent.func_call.native', False)
657
604
  if self.window.controller.agent.experts.enabled():
658
605
  return self.window.core.config.get('experts.func_call.native', False)
659
606
 
660
- # otherwise check config
661
607
  return self.window.core.config.get('func_call.native', False)
662
608
 
663
609
  def is_cmd(self, inline: bool = True) -> bool:
@@ -667,15 +613,10 @@ class Command:
667
613
  :param inline: check if inline plugin is enabled
668
614
  :return: True if command is enabled
669
615
  """
670
- # check if cmd is enabled in config
671
- if self.window.core.config.get('cmd'):
672
- return True
673
-
674
- # check if cmd inline plugin is enabled
675
- if inline and self.window.controller.plugins.is_type_enabled("cmd.inline"):
676
- return True
677
-
678
- return False
616
+ return bool(
617
+ self.window.core.config.get('cmd')
618
+ or (inline and self.window.controller.plugins.is_type_enabled("cmd.inline"))
619
+ )
679
620
 
680
621
  def is_enabled(self, cmd: str) -> bool:
681
622
  """
@@ -684,38 +625,27 @@ class Command:
684
625
  :param cmd: command
685
626
  :return: True if command is enabled
686
627
  """
687
- enabled_cmds = []
688
- data = {
689
- 'prompt': "",
690
- 'silent': True,
691
- 'force': True,
692
- 'syntax': [],
693
- 'cmd': [],
694
- }
695
- event = Event(Event.CMD_SYNTAX, data)
696
- self.window.dispatch(event)
697
- if (event.data and "cmd" in event.data
698
- and isinstance(event.data["cmd"], list)):
699
- for item in event.data["cmd"]:
700
- if "cmd" in item:
701
- enabled_cmds.append(item["cmd"])
702
- data = {
703
- 'prompt': "",
704
- 'silent': True,
705
- 'force': True,
706
- 'syntax': [],
707
- 'cmd': [],
708
- }
709
- event = Event(Event.CMD_SYNTAX_INLINE, data)
710
- self.window.dispatch(event)
711
- if (event.data and "cmd" in event.data
712
- and isinstance(event.data["cmd"], list)):
713
- for item in event.data["cmd"]:
714
- if "cmd" in item:
715
- enabled_cmds.append(item["cmd"])
716
- if cmd in enabled_cmds:
717
- return True
718
- return False
628
+ enabled_cmds = set()
629
+
630
+ def collect(event_type):
631
+ data = {
632
+ 'prompt': "",
633
+ 'silent': True,
634
+ 'force': True,
635
+ 'syntax': [],
636
+ 'cmd': [],
637
+ }
638
+ event = Event(event_type, data)
639
+ self.window.dispatch(event)
640
+ if event.data and "cmd" in event.data and isinstance(event.data["cmd"], list):
641
+ for item in event.data["cmd"]:
642
+ if "cmd" in item:
643
+ enabled_cmds.add(item["cmd"])
644
+
645
+ collect(Event.CMD_SYNTAX)
646
+ collect(Event.CMD_SYNTAX_INLINE)
647
+
648
+ return cmd in enabled_cmds
719
649
 
720
650
  def is_model_supports_tools(
721
651
  self,
@@ -743,4 +673,4 @@ class Command:
743
673
  if (model.get_provider() == "ollama"
744
674
  and model.id.startswith(disabled_model)):
745
675
  return False
746
- return True
676
+ return True