django-agent-studio 0.3.3__tar.gz → 0.3.5__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.
Files changed (52) hide show
  1. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/PKG-INFO +1 -1
  2. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/agents/dynamic.py +164 -4
  3. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/api/serializers.py +27 -8
  4. django_agent_studio-0.3.5/branding.py +103 -0
  5. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/django_agent_studio.egg-info/PKG-INFO +1 -1
  6. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/django_agent_studio.egg-info/SOURCES.txt +2 -0
  7. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/pyproject.toml +1 -1
  8. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/static/agent-frontend/chat-widget.css +52 -0
  9. django_agent_studio-0.3.5/static/agent-frontend/chat-widget.js +607 -0
  10. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/static/django_agent_studio/js/builder.js +58 -58
  11. django_agent_studio-0.3.5/static/django_agent_studio/js/builder.js.map +1 -0
  12. django_agent_studio-0.3.5/static/django_agent_studio/js/style.css +1 -0
  13. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/templates/django_agent_studio/agent_list.html +31 -28
  14. django_agent_studio-0.3.5/templates/django_agent_studio/base.html +238 -0
  15. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/templates/django_agent_studio/builder.html +1 -1
  16. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/templates/django_agent_studio/collaborators.html +83 -95
  17. django_agent_studio-0.3.5/templates/django_agent_studio/home.html +162 -0
  18. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/templates/django_agent_studio/system_create.html +22 -24
  19. django_agent_studio-0.3.5/templates/django_agent_studio/system_list.html +112 -0
  20. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/templates/django_agent_studio/system_test.html +27 -21
  21. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/templates/django_agent_studio/test.html +22 -17
  22. django_agent_studio-0.3.3/static/agent-frontend/chat-widget.js +0 -591
  23. django_agent_studio-0.3.3/static/django_agent_studio/js/builder.js.map +0 -1
  24. django_agent_studio-0.3.3/static/django_agent_studio/js/style.css +0 -1
  25. django_agent_studio-0.3.3/templates/django_agent_studio/base.html +0 -150
  26. django_agent_studio-0.3.3/templates/django_agent_studio/home.html +0 -144
  27. django_agent_studio-0.3.3/templates/django_agent_studio/system_list.html +0 -104
  28. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/LICENSE +0 -0
  29. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/README.md +0 -0
  30. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/__init__.py +0 -0
  31. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/agents/__init__.py +0 -0
  32. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/agents/builder.py +0 -0
  33. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/api/__init__.py +0 -0
  34. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/api/permissions.py +0 -0
  35. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/api/urls.py +0 -0
  36. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/api/views.py +0 -0
  37. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/apps.py +0 -0
  38. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/django_agent_studio.egg-info/dependency_links.txt +0 -0
  39. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/django_agent_studio.egg-info/requires.txt +0 -0
  40. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/django_agent_studio.egg-info/top_level.txt +0 -0
  41. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/management/__init__.py +0 -0
  42. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/management/commands/__init__.py +0 -0
  43. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/migrations/0001_initial.py +0 -0
  44. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/migrations/__init__.py +0 -0
  45. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/models/__init__.py +0 -0
  46. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/models/permissions.py +0 -0
  47. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/services/__init__.py +0 -0
  48. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/services/permissions.py +0 -0
  49. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/setup.cfg +0 -0
  50. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/static/agent-frontend/chat-widget-markdown.js +0 -0
  51. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/urls.py +0 -0
  52. {django_agent_studio-0.3.3 → django_agent_studio-0.3.5}/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-agent-studio
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: Visual agent builder and management studio for Django - build custom GPTs with a two-pane interface
5
5
  Author: Chris Barry
6
6
  License: Business Source License 1.1
@@ -626,8 +626,13 @@ class DynamicAgentRuntime(AgentRuntime):
626
626
  return ""
627
627
 
628
628
  def _build_tool_schemas(self, config: dict) -> list:
629
- """Build OpenAI-format tool schemas from config."""
629
+ """Build OpenAI-format tool schemas from config.
630
+
631
+ Includes both regular tools and sub-agent tools.
632
+ """
630
633
  schemas = []
634
+
635
+ # Add regular tools
631
636
  for tool in config.get("tools", []):
632
637
  # Skip the _meta field when building schema
633
638
  schema = {
@@ -635,22 +640,68 @@ class DynamicAgentRuntime(AgentRuntime):
635
640
  "function": tool.get("function", {}),
636
641
  }
637
642
  schemas.append(schema)
643
+
644
+ # Add sub-agent tools in OpenAI format
645
+ for sub_tool in config.get("sub_agent_tools", []):
646
+ schema = {
647
+ "type": "function",
648
+ "function": {
649
+ "name": sub_tool.get("name"),
650
+ "description": sub_tool.get("description", ""),
651
+ "parameters": {
652
+ "type": "object",
653
+ "properties": {
654
+ "message": {
655
+ "type": "string",
656
+ "description": f"The message or task to send to the {sub_tool.get('name')} agent",
657
+ },
658
+ "context": {
659
+ "type": "string",
660
+ "description": "Optional additional context to include",
661
+ },
662
+ },
663
+ "required": ["message"],
664
+ },
665
+ },
666
+ }
667
+ schemas.append(schema)
668
+ logger.debug(f"Added sub-agent tool schema: {sub_tool.get('name')}")
669
+
638
670
  return schemas
639
671
 
640
672
  def _build_tool_map(self, config: dict) -> dict:
641
- """Build a map of tool name to execution info."""
673
+ """Build a map of tool name to execution info.
674
+
675
+ Includes both regular tools and sub-agent tools.
676
+ """
642
677
  tool_map = {}
678
+
679
+ # Add regular tools
643
680
  for tool in config.get("tools", []):
644
681
  func_info = tool.get("function", {})
645
682
  tool_name = func_info.get("name")
646
683
  if tool_name:
647
684
  # Get execution metadata from _meta field
648
685
  meta = tool.get("_meta", {})
686
+ # Check both function_path (DynamicTool) and builtin_ref (AgentTool)
649
687
  tool_map[tool_name] = {
650
- "function_path": meta.get("function_path"),
688
+ "function_path": meta.get("function_path") or meta.get("builtin_ref"),
651
689
  "tool_id": meta.get("tool_id"),
652
690
  "is_dynamic": meta.get("is_dynamic", False),
691
+ "is_sub_agent": False,
653
692
  }
693
+
694
+ # Add sub-agent tools
695
+ for sub_tool in config.get("sub_agent_tools", []):
696
+ tool_name = sub_tool.get("name")
697
+ if tool_name:
698
+ tool_map[tool_name] = {
699
+ "is_sub_agent": True,
700
+ "agent_slug": sub_tool.get("agent_slug"),
701
+ "context_mode": sub_tool.get("context_mode", "message_only"),
702
+ }
703
+ logger.debug(f"Added sub-agent tool to map: {tool_name} -> {sub_tool.get('agent_slug')}")
704
+
654
705
  return tool_map
655
706
 
656
707
  async def _execute_tool(
@@ -661,8 +712,22 @@ class DynamicAgentRuntime(AgentRuntime):
661
712
  executor: "DynamicToolExecutor",
662
713
  ctx: RunContext,
663
714
  ) -> str:
664
- """Execute a tool and return its result."""
715
+ """Execute a tool and return its result.
716
+
717
+ Handles both regular tools and sub-agent tools.
718
+ """
665
719
  tool_info = tool_map.get(tool_name, {})
720
+
721
+ # Check if this is a sub-agent tool
722
+ if tool_info.get("is_sub_agent"):
723
+ return await self._execute_sub_agent_tool(
724
+ tool_name=tool_name,
725
+ tool_args=tool_args,
726
+ tool_info=tool_info,
727
+ ctx=ctx,
728
+ )
729
+
730
+ # Regular tool execution
666
731
  function_path = tool_info.get("function_path")
667
732
 
668
733
  if not function_path:
@@ -690,3 +755,98 @@ class DynamicAgentRuntime(AgentRuntime):
690
755
  logger.exception(f"Error executing tool {tool_name}")
691
756
  return json.dumps({"error": str(e)})
692
757
 
758
+ async def _execute_sub_agent_tool(
759
+ self,
760
+ tool_name: str,
761
+ tool_args: dict,
762
+ tool_info: dict,
763
+ ctx: RunContext,
764
+ ) -> str:
765
+ """Execute a sub-agent tool by invoking the sub-agent.
766
+
767
+ Args:
768
+ tool_name: Name of the sub-agent tool
769
+ tool_args: Arguments passed to the tool (message, context)
770
+ tool_info: Tool metadata including agent_slug and context_mode
771
+ ctx: Parent agent's run context
772
+
773
+ Returns:
774
+ JSON string with the sub-agent's response
775
+ """
776
+ from agent_runtime_core.multi_agent import (
777
+ AgentTool as AgentToolCore,
778
+ InvocationMode,
779
+ ContextMode,
780
+ invoke_agent,
781
+ )
782
+ from django_agent_runtime.runtime.registry import get_runtime_async
783
+
784
+ sub_agent_slug = tool_info.get("agent_slug")
785
+ context_mode_str = tool_info.get("context_mode", "message_only")
786
+
787
+ # Map context_mode string to enum
788
+ context_mode_map = {
789
+ 'message_only': ContextMode.MESSAGE_ONLY,
790
+ 'summary': ContextMode.SUMMARY,
791
+ 'full': ContextMode.FULL,
792
+ }
793
+ context_mode = context_mode_map.get(context_mode_str, ContextMode.MESSAGE_ONLY)
794
+
795
+ message = tool_args.get("message", "")
796
+ additional_context = tool_args.get("context")
797
+
798
+ if not message:
799
+ return json.dumps({"error": "Missing required parameter: message"})
800
+
801
+ try:
802
+ # Get the sub-agent's runtime
803
+ sub_agent_runtime = await get_runtime_async(sub_agent_slug)
804
+
805
+ # Create an AgentTool wrapper for the sub-agent
806
+ agent_tool = AgentToolCore(
807
+ agent=sub_agent_runtime,
808
+ name=tool_name,
809
+ description=f"Sub-agent: {sub_agent_slug}",
810
+ invocation_mode=InvocationMode.DELEGATE,
811
+ context_mode=context_mode,
812
+ metadata={
813
+ 'sub_agent_slug': sub_agent_slug,
814
+ 'parent_run_id': str(ctx.run_id) if ctx.run_id else None,
815
+ },
816
+ )
817
+
818
+ # Get conversation history from parent context
819
+ conversation_history = list(ctx.input_messages)
820
+
821
+ # Invoke the sub-agent
822
+ logger.info(f"Invoking sub-agent '{sub_agent_slug}' via tool '{tool_name}'")
823
+ result = await invoke_agent(
824
+ agent_tool=agent_tool,
825
+ message=message,
826
+ parent_ctx=ctx,
827
+ conversation_history=conversation_history,
828
+ additional_context=additional_context,
829
+ )
830
+
831
+ logger.info(f"Sub-agent '{sub_agent_slug}' completed for tool '{tool_name}'")
832
+
833
+ return json.dumps({
834
+ "response": result.response,
835
+ "sub_agent": result.sub_agent_key,
836
+ "handoff": result.handoff,
837
+ })
838
+
839
+ except KeyError as e:
840
+ logger.error(f"Sub-agent not found: {sub_agent_slug} - {e}")
841
+ return json.dumps({
842
+ "error": f"Sub-agent not found: {sub_agent_slug}",
843
+ "tool": tool_name,
844
+ })
845
+ except Exception as e:
846
+ logger.exception(f"Error invoking sub-agent '{sub_agent_slug}'")
847
+ return json.dumps({
848
+ "error": str(e),
849
+ "tool": tool_name,
850
+ "sub_agent": sub_agent_slug,
851
+ })
852
+
@@ -503,8 +503,8 @@ class AgentSystemVersionSerializer(serializers.ModelSerializer):
503
503
  class AgentSystemListSerializer(serializers.ModelSerializer):
504
504
  """Serializer for listing AgentSystems."""
505
505
 
506
- entry_agent_name = serializers.CharField(source='entry_agent.name', read_only=True)
507
- entry_agent_slug = serializers.CharField(source='entry_agent.slug', read_only=True)
506
+ entry_agent = serializers.SerializerMethodField()
507
+ members = AgentSystemMemberSerializer(many=True, read_only=True)
508
508
  member_count = serializers.SerializerMethodField()
509
509
  active_version = serializers.SerializerMethodField()
510
510
 
@@ -516,8 +516,7 @@ class AgentSystemListSerializer(serializers.ModelSerializer):
516
516
  "name",
517
517
  "description",
518
518
  "entry_agent",
519
- "entry_agent_name",
520
- "entry_agent_slug",
519
+ "members",
521
520
  "is_active",
522
521
  "member_count",
523
522
  "active_version",
@@ -526,6 +525,16 @@ class AgentSystemListSerializer(serializers.ModelSerializer):
526
525
  ]
527
526
  read_only_fields = ["id", "created_at", "updated_at"]
528
527
 
528
+ def get_entry_agent(self, obj):
529
+ if obj.entry_agent:
530
+ return {
531
+ "id": str(obj.entry_agent.id),
532
+ "name": obj.entry_agent.name,
533
+ "slug": obj.entry_agent.slug,
534
+ "icon": obj.entry_agent.icon,
535
+ }
536
+ return None
537
+
529
538
  def get_member_count(self, obj):
530
539
  return obj.members.count()
531
540
 
@@ -539,8 +548,6 @@ class AgentSystemListSerializer(serializers.ModelSerializer):
539
548
  class AgentSystemDetailSerializer(serializers.ModelSerializer):
540
549
  """Serializer for AgentSystem detail view."""
541
550
 
542
- entry_agent_name = serializers.CharField(source='entry_agent.name', read_only=True)
543
- entry_agent_slug = serializers.CharField(source='entry_agent.slug', read_only=True)
544
551
  members = AgentSystemMemberSerializer(many=True, read_only=True)
545
552
  versions = AgentSystemVersionSerializer(many=True, read_only=True)
546
553
  dependency_graph = serializers.SerializerMethodField()
@@ -553,8 +560,6 @@ class AgentSystemDetailSerializer(serializers.ModelSerializer):
553
560
  "name",
554
561
  "description",
555
562
  "entry_agent",
556
- "entry_agent_name",
557
- "entry_agent_slug",
558
563
  "is_active",
559
564
  "members",
560
565
  "versions",
@@ -567,6 +572,20 @@ class AgentSystemDetailSerializer(serializers.ModelSerializer):
567
572
  def get_dependency_graph(self, obj):
568
573
  return obj.get_dependency_graph()
569
574
 
575
+ def to_representation(self, instance):
576
+ """Return entry_agent as an object for reading."""
577
+ data = super().to_representation(instance)
578
+ if instance.entry_agent:
579
+ data['entry_agent'] = {
580
+ "id": str(instance.entry_agent.id),
581
+ "name": instance.entry_agent.name,
582
+ "slug": instance.entry_agent.slug,
583
+ "icon": instance.entry_agent.icon,
584
+ }
585
+ else:
586
+ data['entry_agent'] = None
587
+ return data
588
+
570
589
 
571
590
  class AgentSystemCreateSerializer(serializers.Serializer):
572
591
  """Serializer for creating an AgentSystem."""
@@ -0,0 +1,103 @@
1
+ """
2
+ Branding configuration for Django Agent Studio.
3
+
4
+ This module provides customizable branding settings that can be overridden
5
+ in Django settings via the AGENT_STUDIO_BRANDING dictionary.
6
+
7
+ Example settings.py configuration:
8
+
9
+ AGENT_STUDIO_BRANDING = {
10
+ 'app_name': 'My Agent Studio',
11
+ 'logo_svg': '<svg>...</svg>', # Custom logo SVG
12
+ 'colors': {
13
+ 'primary': '#00142E',
14
+ 'accent': '#4fc4f7',
15
+ 'secondary': '#253547',
16
+ },
17
+ }
18
+ """
19
+
20
+ from django.conf import settings
21
+
22
+
23
+ # Default branding configuration
24
+ DEFAULT_BRANDING = {
25
+ # Application name
26
+ 'app_name': 'Agent Studio',
27
+
28
+ # Logo - SVG markup (displayed in header)
29
+ # Default is a simple "AS" text logo
30
+ 'logo_svg': '''
31
+ <svg viewBox="0 0 80 32" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-7">
32
+ <text x="0" y="24" fill="currentColor" font-family="system-ui, -apple-system, sans-serif" font-size="24" font-weight="700">AS</text>
33
+ </svg>
34
+ ''',
35
+
36
+ # Whether to show app name next to logo
37
+ 'show_app_name': True,
38
+
39
+ # Colors - these are the defaults (professional blue/cyan theme)
40
+ 'colors': {
41
+ # Primary color - used for header, primary buttons, main text
42
+ 'primary': '#00142E',
43
+ 'primary_light': '#0a2540',
44
+ 'primary_dark': '#000d1f',
45
+
46
+ # Accent color - used for highlights, focus rings, CTAs
47
+ 'accent': '#4fc4f7',
48
+ 'accent_light': '#7dd3fc',
49
+ 'accent_dark': '#3db8eb',
50
+
51
+ # Secondary color - used for secondary backgrounds, gradients
52
+ 'secondary': '#253547',
53
+ 'secondary_light': '#334155',
54
+ 'secondary_dark': '#1e293b',
55
+ },
56
+
57
+ # Chat widget primary color (used by agent-frontend)
58
+ 'chat_primary_color': '#00142E',
59
+
60
+ # Custom CSS to inject (optional)
61
+ 'custom_css': '',
62
+ }
63
+
64
+
65
+ def get_branding():
66
+ """
67
+ Get the merged branding configuration.
68
+
69
+ Merges user settings from AGENT_STUDIO_BRANDING with defaults.
70
+ """
71
+ user_branding = getattr(settings, 'AGENT_STUDIO_BRANDING', {})
72
+
73
+ # Deep merge colors
74
+ merged = DEFAULT_BRANDING.copy()
75
+
76
+ for key, value in user_branding.items():
77
+ if key == 'colors' and isinstance(value, dict):
78
+ # Merge colors dict
79
+ merged['colors'] = {**DEFAULT_BRANDING['colors'], **value}
80
+ else:
81
+ merged[key] = value
82
+
83
+ return merged
84
+
85
+
86
+ def branding_context_processor(request):
87
+ """
88
+ Django context processor that adds branding to all templates.
89
+
90
+ Add to settings.py TEMPLATES['OPTIONS']['context_processors']:
91
+ 'django_agent_studio.branding.branding_context_processor',
92
+ """
93
+ branding = get_branding()
94
+
95
+ return {
96
+ 'studio_branding': branding,
97
+ 'studio_app_name': branding['app_name'],
98
+ 'studio_logo_svg': branding['logo_svg'],
99
+ 'studio_show_app_name': branding['show_app_name'],
100
+ 'studio_colors': branding['colors'],
101
+ 'studio_chat_primary_color': branding['chat_primary_color'],
102
+ 'studio_custom_css': branding['custom_css'],
103
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-agent-studio
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: Visual agent builder and management studio for Django - build custom GPTs with a two-pane interface
5
5
  Author: Chris Barry
6
6
  License: Business Source License 1.1
@@ -2,11 +2,13 @@ LICENSE
2
2
  README.md
3
3
  __init__.py
4
4
  apps.py
5
+ branding.py
5
6
  pyproject.toml
6
7
  urls.py
7
8
  views.py
8
9
  ./__init__.py
9
10
  ./apps.py
11
+ ./branding.py
10
12
  ./urls.py
11
13
  ./views.py
12
14
  ./agents/__init__.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "django-agent-studio"
7
- version = "0.3.3"
7
+ version = "0.3.5"
8
8
  description = "Visual agent builder and management studio for Django - build custom GPTs with a two-pane interface"
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
@@ -89,6 +89,7 @@
89
89
 
90
90
  /* Widget container */
91
91
  .cw-widget {
92
+ position: relative;
92
93
  width: 380px;
93
94
  height: 500px;
94
95
  background: var(--cw-bg);
@@ -1272,6 +1273,57 @@
1272
1273
  margin-top: 2px;
1273
1274
  }
1274
1275
 
1276
+ /* Thinking Toggle */
1277
+ .cw-model-selector {
1278
+ display: flex;
1279
+ align-items: center;
1280
+ gap: 8px;
1281
+ }
1282
+
1283
+ .cw-model-selector > .cw-model-btn {
1284
+ flex: 1;
1285
+ }
1286
+
1287
+ .cw-thinking-toggle {
1288
+ display: flex;
1289
+ align-items: center;
1290
+ justify-content: center;
1291
+ width: 36px;
1292
+ height: 36px;
1293
+ padding: 0;
1294
+ border: 1px solid var(--cw-border);
1295
+ border-radius: var(--cw-radius-sm);
1296
+ background: var(--cw-bg);
1297
+ cursor: pointer;
1298
+ transition: all 0.2s ease;
1299
+ flex-shrink: 0;
1300
+ }
1301
+
1302
+ .cw-thinking-toggle:hover {
1303
+ border-color: var(--cw-primary);
1304
+ background: var(--cw-bg-muted);
1305
+ }
1306
+
1307
+ .cw-thinking-toggle.cw-thinking-enabled {
1308
+ background: rgba(0, 102, 204, 0.15);
1309
+ border-color: var(--cw-primary);
1310
+ }
1311
+
1312
+ .cw-thinking-toggle:disabled {
1313
+ opacity: 0.5;
1314
+ cursor: not-allowed;
1315
+ }
1316
+
1317
+ .cw-thinking-icon {
1318
+ font-size: 18px;
1319
+ }
1320
+
1321
+ .cw-thinking-badge {
1322
+ margin-left: 4px;
1323
+ font-size: 12px;
1324
+ opacity: 0.7;
1325
+ }
1326
+
1275
1327
  /* ============================================================================
1276
1328
  Tool Call Cards (Claude-style)
1277
1329
  ============================================================================ */