code-puppy 0.0.372__py3-none-any.whl → 0.0.374__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.
- code_puppy/agents/agent_creator_agent.py +49 -1
- code_puppy/agents/agent_helios.py +122 -0
- code_puppy/agents/agent_manager.py +26 -2
- code_puppy/agents/json_agent.py +30 -7
- code_puppy/claude_cache_client.py +9 -9
- code_puppy/command_line/colors_menu.py +2 -0
- code_puppy/command_line/command_handler.py +1 -0
- code_puppy/command_line/config_commands.py +3 -1
- code_puppy/command_line/uc_menu.py +890 -0
- code_puppy/config.py +29 -0
- code_puppy/messaging/messages.py +18 -0
- code_puppy/messaging/rich_renderer.py +35 -0
- code_puppy/messaging/subagent_console.py +0 -1
- code_puppy/plugins/claude_code_oauth/README.md +1 -1
- code_puppy/plugins/claude_code_oauth/SETUP.md +1 -1
- code_puppy/plugins/claude_code_oauth/utils.py +44 -13
- code_puppy/plugins/universal_constructor/__init__.py +13 -0
- code_puppy/plugins/universal_constructor/models.py +138 -0
- code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
- code_puppy/plugins/universal_constructor/registry.py +304 -0
- code_puppy/plugins/universal_constructor/sandbox.py +584 -0
- code_puppy/tools/__init__.py +138 -1
- code_puppy/tools/universal_constructor.py +889 -0
- {code_puppy-0.0.372.dist-info → code_puppy-0.0.374.dist-info}/METADATA +1 -1
- {code_puppy-0.0.372.dist-info → code_puppy-0.0.374.dist-info}/RECORD +30 -22
- {code_puppy-0.0.372.data → code_puppy-0.0.374.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.372.data → code_puppy-0.0.374.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.372.dist-info → code_puppy-0.0.374.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.372.dist-info → code_puppy-0.0.374.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.372.dist-info → code_puppy-0.0.374.dist-info}/licenses/LICENSE +0 -0
code_puppy/config.py
CHANGED
|
@@ -101,6 +101,9 @@ PACK_AGENT_NAMES = frozenset(
|
|
|
101
101
|
]
|
|
102
102
|
)
|
|
103
103
|
|
|
104
|
+
# Agents that require Universal Constructor to be enabled
|
|
105
|
+
UC_AGENT_NAMES = frozenset(["helios"])
|
|
106
|
+
|
|
104
107
|
|
|
105
108
|
def get_pack_agents_enabled() -> bool:
|
|
106
109
|
"""Return True if pack agents are enabled (default False).
|
|
@@ -117,6 +120,30 @@ def get_pack_agents_enabled() -> bool:
|
|
|
117
120
|
return str(cfg_val).strip().lower() in {"1", "true", "yes", "on"}
|
|
118
121
|
|
|
119
122
|
|
|
123
|
+
def get_universal_constructor_enabled() -> bool:
|
|
124
|
+
"""Return True if the Universal Constructor is enabled (default True).
|
|
125
|
+
|
|
126
|
+
The Universal Constructor allows agents to dynamically create, manage,
|
|
127
|
+
and execute custom tools at runtime. When enabled, agents can extend
|
|
128
|
+
their capabilities by writing Python code that becomes callable tools.
|
|
129
|
+
|
|
130
|
+
When False, the universal_constructor tool is not registered with agents.
|
|
131
|
+
"""
|
|
132
|
+
cfg_val = get_value("enable_universal_constructor")
|
|
133
|
+
if cfg_val is None:
|
|
134
|
+
return True # Enabled by default
|
|
135
|
+
return str(cfg_val).strip().lower() in {"1", "true", "yes", "on"}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def set_universal_constructor_enabled(enabled: bool) -> None:
|
|
139
|
+
"""Enable or disable the Universal Constructor.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
enabled: True to enable, False to disable
|
|
143
|
+
"""
|
|
144
|
+
set_value("enable_universal_constructor", "true" if enabled else "false")
|
|
145
|
+
|
|
146
|
+
|
|
120
147
|
DEFAULT_SECTION = "puppy"
|
|
121
148
|
REQUIRED_KEYS = ["puppy_name", "owner_name"]
|
|
122
149
|
|
|
@@ -260,6 +287,8 @@ def get_config_keys():
|
|
|
260
287
|
default_keys.append("enable_dbos")
|
|
261
288
|
# Add pack agents control key
|
|
262
289
|
default_keys.append("enable_pack_agents")
|
|
290
|
+
# Add universal constructor control key
|
|
291
|
+
default_keys.append("enable_universal_constructor")
|
|
263
292
|
# Add cancel agent key configuration
|
|
264
293
|
default_keys.append("cancel_agent_key")
|
|
265
294
|
# Add banner color keys
|
code_puppy/messaging/messages.py
CHANGED
|
@@ -317,6 +317,21 @@ class SubAgentStatusMessage(BaseMessage):
|
|
|
317
317
|
)
|
|
318
318
|
|
|
319
319
|
|
|
320
|
+
class UniversalConstructorMessage(BaseMessage):
|
|
321
|
+
"""Result of a universal_constructor operation."""
|
|
322
|
+
|
|
323
|
+
category: MessageCategory = MessageCategory.TOOL_OUTPUT
|
|
324
|
+
action: str = Field(
|
|
325
|
+
description="The UC action performed (list/call/create/update/info)"
|
|
326
|
+
)
|
|
327
|
+
tool_name: Optional[str] = Field(
|
|
328
|
+
default=None, description="Tool name if applicable"
|
|
329
|
+
)
|
|
330
|
+
success: bool = Field(description="Whether the operation succeeded")
|
|
331
|
+
summary: str = Field(description="Brief summary of the result")
|
|
332
|
+
details: Optional[str] = Field(default=None, description="Additional details")
|
|
333
|
+
|
|
334
|
+
|
|
320
335
|
# =============================================================================
|
|
321
336
|
# User Interaction Messages (Agent → User)
|
|
322
337
|
# =============================================================================
|
|
@@ -443,6 +458,7 @@ AnyMessage = Union[
|
|
|
443
458
|
SubAgentInvocationMessage,
|
|
444
459
|
SubAgentResponseMessage,
|
|
445
460
|
SubAgentStatusMessage,
|
|
461
|
+
UniversalConstructorMessage,
|
|
446
462
|
UserInputRequest,
|
|
447
463
|
ConfirmationRequest,
|
|
448
464
|
SelectionRequest,
|
|
@@ -485,6 +501,8 @@ __all__ = [
|
|
|
485
501
|
"SubAgentInvocationMessage",
|
|
486
502
|
"SubAgentResponseMessage",
|
|
487
503
|
"SubAgentStatusMessage",
|
|
504
|
+
# Universal Constructor
|
|
505
|
+
"UniversalConstructorMessage",
|
|
488
506
|
# User interaction
|
|
489
507
|
"UserInputRequest",
|
|
490
508
|
"ConfirmationRequest",
|
|
@@ -48,6 +48,7 @@ from .messages import (
|
|
|
48
48
|
SubAgentInvocationMessage,
|
|
49
49
|
SubAgentResponseMessage,
|
|
50
50
|
TextMessage,
|
|
51
|
+
UniversalConstructorMessage,
|
|
51
52
|
UserInputRequest,
|
|
52
53
|
VersionCheckMessage,
|
|
53
54
|
)
|
|
@@ -287,6 +288,8 @@ class RichConsoleRenderer:
|
|
|
287
288
|
elif isinstance(message, SubAgentResponseMessage):
|
|
288
289
|
# Skip rendering - we now display sub-agent responses via display_non_streamed_result
|
|
289
290
|
pass
|
|
291
|
+
elif isinstance(message, UniversalConstructorMessage):
|
|
292
|
+
self._render_universal_constructor(message)
|
|
290
293
|
elif isinstance(message, UserInputRequest):
|
|
291
294
|
# Can't handle async user input in sync context - skip
|
|
292
295
|
self._console.print("[dim]User input requested (requires async)[/dim]")
|
|
@@ -775,6 +778,38 @@ class RichConsoleRenderer:
|
|
|
775
778
|
f"({msg.message_count} messages)[/dim]"
|
|
776
779
|
)
|
|
777
780
|
|
|
781
|
+
def _render_universal_constructor(self, msg: UniversalConstructorMessage) -> None:
|
|
782
|
+
"""Render universal_constructor tool output with banner."""
|
|
783
|
+
# Skip for sub-agents unless verbose mode
|
|
784
|
+
if self._should_suppress_subagent_output():
|
|
785
|
+
return
|
|
786
|
+
|
|
787
|
+
# Format banner
|
|
788
|
+
banner = self._format_banner("universal_constructor", "UNIVERSAL CONSTRUCTOR")
|
|
789
|
+
|
|
790
|
+
# Build the header line with action and optional tool name
|
|
791
|
+
# Escape user-controlled strings to prevent Rich markup injection
|
|
792
|
+
header_parts = [f"\n{banner} 🔧 [bold cyan]{msg.action.upper()}[/bold cyan]"]
|
|
793
|
+
if msg.tool_name:
|
|
794
|
+
safe_tool_name = escape_rich_markup(msg.tool_name)
|
|
795
|
+
header_parts.append(f" [dim]tool=[/dim][bold]{safe_tool_name}[/bold]")
|
|
796
|
+
self._console.print("".join(header_parts))
|
|
797
|
+
|
|
798
|
+
# Status indicator
|
|
799
|
+
safe_summary = escape_rich_markup(msg.summary) if msg.summary else ""
|
|
800
|
+
if msg.success:
|
|
801
|
+
self._console.print(f"[green]✓[/green] {safe_summary}")
|
|
802
|
+
else:
|
|
803
|
+
self._console.print(f"[red]✗[/red] {safe_summary}")
|
|
804
|
+
|
|
805
|
+
# Show details if present
|
|
806
|
+
if msg.details:
|
|
807
|
+
safe_details = escape_rich_markup(msg.details)
|
|
808
|
+
self._console.print(f"[dim]{safe_details}[/dim]")
|
|
809
|
+
|
|
810
|
+
# Trailing newline for spinner separation
|
|
811
|
+
self._console.print()
|
|
812
|
+
|
|
778
813
|
# =========================================================================
|
|
779
814
|
# User Interaction
|
|
780
815
|
# =========================================================================
|
|
@@ -24,7 +24,6 @@ from rich.text import Text
|
|
|
24
24
|
|
|
25
25
|
from code_puppy.messaging.messages import SubAgentStatusMessage
|
|
26
26
|
|
|
27
|
-
|
|
28
27
|
# =============================================================================
|
|
29
28
|
# Status Configuration
|
|
30
29
|
# =============================================================================
|
|
@@ -52,7 +52,7 @@ The plugin ships with sensible defaults in `config.py`:
|
|
|
52
52
|
```python
|
|
53
53
|
CLAUDE_CODE_OAUTH_CONFIG = {
|
|
54
54
|
"auth_url": "https://claude.ai/oauth/authorize",
|
|
55
|
-
"token_url": "https://
|
|
55
|
+
"token_url": "https://console.anthropic.com/v1/oauth/token",
|
|
56
56
|
"api_base_url": "https://api.anthropic.com",
|
|
57
57
|
"client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
|
58
58
|
"scope": "org:create_api_key user:profile user:inference",
|
|
@@ -37,7 +37,7 @@ Anthropic exposes a shared **public client** (`claude-cli`) for command-line too
|
|
|
37
37
|
```python
|
|
38
38
|
CLAUDE_CODE_OAUTH_CONFIG = {
|
|
39
39
|
"auth_url": "https://claude.ai/oauth/authorize",
|
|
40
|
-
"token_url": "https://
|
|
40
|
+
"token_url": "https://console.anthropic.com/v1/oauth/token",
|
|
41
41
|
"api_base_url": "https://api.anthropic.com",
|
|
42
42
|
"client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
|
43
43
|
"scope": "org:create_api_key user:profile user:inference",
|
|
@@ -21,10 +21,10 @@ from .config import (
|
|
|
21
21
|
get_token_storage_path,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
# Proactive refresh buffer
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
# Proactive refresh buffer default (seconds). Actual buffer is dynamic
|
|
25
|
+
# based on expires_in to avoid overly aggressive refreshes.
|
|
26
|
+
TOKEN_REFRESH_BUFFER_SECONDS = 300
|
|
27
|
+
MIN_REFRESH_BUFFER_SECONDS = 30
|
|
28
28
|
|
|
29
29
|
logger = logging.getLogger(__name__)
|
|
30
30
|
|
|
@@ -146,15 +146,40 @@ def _calculate_expires_at(expires_in: Optional[float]) -> Optional[float]:
|
|
|
146
146
|
return None
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
def
|
|
149
|
+
def _calculate_refresh_buffer(expires_in: Optional[float]) -> float:
|
|
150
|
+
default_buffer = float(TOKEN_REFRESH_BUFFER_SECONDS)
|
|
151
|
+
if expires_in is None:
|
|
152
|
+
return default_buffer
|
|
153
|
+
try:
|
|
154
|
+
expires_value = float(expires_in)
|
|
155
|
+
except (TypeError, ValueError):
|
|
156
|
+
return default_buffer
|
|
157
|
+
return min(default_buffer, max(MIN_REFRESH_BUFFER_SECONDS, expires_value * 0.1))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _get_expires_at_value(tokens: Dict[str, Any]) -> Optional[float]:
|
|
150
161
|
expires_at = tokens.get("expires_at")
|
|
151
162
|
if expires_at is None:
|
|
152
|
-
return
|
|
163
|
+
return None
|
|
153
164
|
try:
|
|
154
|
-
|
|
165
|
+
return float(expires_at)
|
|
155
166
|
except (TypeError, ValueError):
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _is_token_actually_expired(tokens: Dict[str, Any]) -> bool:
|
|
171
|
+
expires_at_value = _get_expires_at_value(tokens)
|
|
172
|
+
if expires_at_value is None:
|
|
173
|
+
return False
|
|
174
|
+
return time.time() >= expires_at_value
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def is_token_expired(tokens: Dict[str, Any]) -> bool:
|
|
178
|
+
expires_at_value = _get_expires_at_value(tokens)
|
|
179
|
+
if expires_at_value is None:
|
|
156
180
|
return False
|
|
157
|
-
|
|
181
|
+
buffer_seconds = _calculate_refresh_buffer(tokens.get("expires_in"))
|
|
182
|
+
return time.time() >= expires_at_value - buffer_seconds
|
|
158
183
|
|
|
159
184
|
|
|
160
185
|
def update_claude_code_model_tokens(access_token: str) -> bool:
|
|
@@ -216,11 +241,12 @@ def refresh_access_token(force: bool = False) -> Optional[str]:
|
|
|
216
241
|
new_tokens = response.json()
|
|
217
242
|
tokens["access_token"] = new_tokens.get("access_token")
|
|
218
243
|
tokens["refresh_token"] = new_tokens.get("refresh_token", refresh_token)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
244
|
+
expires_in_value = new_tokens.get("expires_in")
|
|
245
|
+
if expires_in_value is None:
|
|
246
|
+
expires_in_value = tokens.get("expires_in")
|
|
247
|
+
if expires_in_value is not None:
|
|
248
|
+
tokens["expires_in"] = expires_in_value
|
|
249
|
+
tokens["expires_at"] = _calculate_expires_at(expires_in_value)
|
|
224
250
|
if save_tokens(tokens):
|
|
225
251
|
update_claude_code_model_tokens(tokens["access_token"])
|
|
226
252
|
return tokens["access_token"]
|
|
@@ -249,6 +275,11 @@ def get_valid_access_token() -> Optional[str]:
|
|
|
249
275
|
refreshed = refresh_access_token()
|
|
250
276
|
if refreshed:
|
|
251
277
|
return refreshed
|
|
278
|
+
if not _is_token_actually_expired(tokens):
|
|
279
|
+
logger.warning(
|
|
280
|
+
"Claude Code token refresh failed; using existing access token until expiry"
|
|
281
|
+
)
|
|
282
|
+
return access_token
|
|
252
283
|
logger.warning("Claude Code token refresh failed")
|
|
253
284
|
return None
|
|
254
285
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Universal Constructor - Dynamic tool creation and management plugin.
|
|
2
|
+
|
|
3
|
+
This plugin enables users to create, manage, and deploy custom tools
|
|
4
|
+
that extend Code Puppy's capabilities. Tools are stored in the user's
|
|
5
|
+
config directory and can be organized into namespaces via subdirectories.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# User tools directory - where user-created UC tools live
|
|
11
|
+
USER_UC_DIR = Path.home() / ".code_puppy" / "plugins" / "universal_constructor"
|
|
12
|
+
|
|
13
|
+
__all__ = ["USER_UC_DIR"]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Pydantic models for Universal Constructor tools and responses.
|
|
2
|
+
|
|
3
|
+
This module defines the data structures used throughout the UC plugin
|
|
4
|
+
for representing tool metadata, tool information, and operation responses.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Optional
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ToolMeta(BaseModel):
|
|
13
|
+
"""Metadata for a UC tool.
|
|
14
|
+
|
|
15
|
+
This is the structure expected in the TOOL_META dictionary
|
|
16
|
+
at the top of each tool file.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name: str = Field(..., description="Human-readable tool name")
|
|
20
|
+
namespace: str = Field(
|
|
21
|
+
default="", description="Namespace for the tool (from subdirectory path)"
|
|
22
|
+
)
|
|
23
|
+
description: str = Field(..., description="What the tool does")
|
|
24
|
+
enabled: bool = Field(default=True, description="Whether the tool is active")
|
|
25
|
+
version: str = Field(default="1.0.0", description="Semantic version of the tool")
|
|
26
|
+
author: str = Field(default="", description="Tool author or creator")
|
|
27
|
+
created_at: Optional[str] = Field(
|
|
28
|
+
default=None, description="When the tool was created (ISO format string)"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
model_config = {"extra": "allow"} # Allow additional metadata fields
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class UCToolInfo(BaseModel):
|
|
35
|
+
"""Full information about a UC tool.
|
|
36
|
+
|
|
37
|
+
Combines metadata with runtime information like function signature
|
|
38
|
+
and source file location.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
meta: ToolMeta = Field(..., description="Tool metadata")
|
|
42
|
+
signature: str = Field(..., description="Function signature string")
|
|
43
|
+
source_path: str = Field(..., description="Path to the tool source file")
|
|
44
|
+
function_name: str = Field(default="", description="Name of the callable function")
|
|
45
|
+
docstring: Optional[str] = Field(default=None, description="Function docstring")
|
|
46
|
+
|
|
47
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def full_name(self) -> str:
|
|
51
|
+
"""Get the fully qualified tool name including namespace."""
|
|
52
|
+
if self.meta.namespace:
|
|
53
|
+
return f"{self.meta.namespace}.{self.meta.name}"
|
|
54
|
+
return self.meta.name
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Response models for UC operations
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class UCListOutput(BaseModel):
|
|
61
|
+
"""Response model for listing UC tools."""
|
|
62
|
+
|
|
63
|
+
tools: List[UCToolInfo] = Field(
|
|
64
|
+
default_factory=list, description="List of available tools"
|
|
65
|
+
)
|
|
66
|
+
total_count: int = Field(default=0, description="Total number of tools")
|
|
67
|
+
enabled_count: int = Field(default=0, description="Number of enabled tools")
|
|
68
|
+
error: Optional[str] = Field(default=None, description="Error message if any")
|
|
69
|
+
|
|
70
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class UCCallOutput(BaseModel):
|
|
74
|
+
"""Response model for calling a UC tool."""
|
|
75
|
+
|
|
76
|
+
success: bool = Field(..., description="Whether the call succeeded")
|
|
77
|
+
tool_name: str = Field(..., description="Name of the tool that was called")
|
|
78
|
+
result: Any = Field(default=None, description="Return value from the tool")
|
|
79
|
+
error: Optional[str] = Field(default=None, description="Error message if failed")
|
|
80
|
+
execution_time: Optional[float] = Field(
|
|
81
|
+
default=None, description="Execution time in seconds"
|
|
82
|
+
)
|
|
83
|
+
source_preview: Optional[str] = Field(
|
|
84
|
+
default=None, description="Preview of the tool's source code that was executed"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class UCCreateOutput(BaseModel):
|
|
89
|
+
"""Response model for creating a UC tool."""
|
|
90
|
+
|
|
91
|
+
success: bool = Field(..., description="Whether creation succeeded")
|
|
92
|
+
tool_name: str = Field(default="", description="Name of the created tool")
|
|
93
|
+
source_path: Optional[str] = Field(
|
|
94
|
+
default=None, description="Path where tool was saved"
|
|
95
|
+
)
|
|
96
|
+
preview: Optional[str] = Field(
|
|
97
|
+
default=None, description="Preview of the first 10 lines of source code"
|
|
98
|
+
)
|
|
99
|
+
error: Optional[str] = Field(default=None, description="Error message if failed")
|
|
100
|
+
validation_warnings: List[str] = Field(
|
|
101
|
+
default_factory=list, description="Non-fatal validation warnings"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class UCUpdateOutput(BaseModel):
|
|
108
|
+
"""Response model for updating a UC tool."""
|
|
109
|
+
|
|
110
|
+
success: bool = Field(..., description="Whether update succeeded")
|
|
111
|
+
tool_name: str = Field(default="", description="Name of the updated tool")
|
|
112
|
+
source_path: Optional[str] = Field(
|
|
113
|
+
default=None, description="Path to the updated tool"
|
|
114
|
+
)
|
|
115
|
+
preview: Optional[str] = Field(
|
|
116
|
+
default=None, description="Preview of the first 10 lines of updated source code"
|
|
117
|
+
)
|
|
118
|
+
error: Optional[str] = Field(default=None, description="Error message if failed")
|
|
119
|
+
changes_applied: List[str] = Field(
|
|
120
|
+
default_factory=list, description="List of changes that were applied"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class UCInfoOutput(BaseModel):
|
|
127
|
+
"""Response model for getting info about a specific UC tool."""
|
|
128
|
+
|
|
129
|
+
success: bool = Field(..., description="Whether lookup succeeded")
|
|
130
|
+
tool: Optional[UCToolInfo] = Field(
|
|
131
|
+
default=None, description="Tool information if found"
|
|
132
|
+
)
|
|
133
|
+
source_code: Optional[str] = Field(
|
|
134
|
+
default=None, description="Source code of the tool"
|
|
135
|
+
)
|
|
136
|
+
error: Optional[str] = Field(default=None, description="Error message if failed")
|
|
137
|
+
|
|
138
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Callback registration for the Universal Constructor plugin.
|
|
2
|
+
|
|
3
|
+
This module registers callbacks to integrate UC with the rest of
|
|
4
|
+
Code Puppy. It ensures the plugin is properly loaded and initialized.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from code_puppy.callbacks import register_callback
|
|
10
|
+
|
|
11
|
+
from . import USER_UC_DIR
|
|
12
|
+
from .registry import get_registry
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _on_startup() -> None:
|
|
18
|
+
"""Initialize UC plugin on application startup."""
|
|
19
|
+
from code_puppy.config import get_universal_constructor_enabled
|
|
20
|
+
|
|
21
|
+
# Skip initialization if UC is disabled
|
|
22
|
+
if not get_universal_constructor_enabled():
|
|
23
|
+
logger.debug("Universal Constructor is disabled, skipping initialization")
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
logger.debug("Universal Constructor plugin initializing...")
|
|
27
|
+
|
|
28
|
+
# Ensure the user tools directory exists
|
|
29
|
+
USER_UC_DIR.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
|
|
31
|
+
# Do an initial scan of tools (lazy - will happen on first access)
|
|
32
|
+
registry = get_registry()
|
|
33
|
+
logger.debug(f"UC registry initialized, tools dir: {registry._tools_dir}")
|
|
34
|
+
|
|
35
|
+
# Log plugin info at startup
|
|
36
|
+
tools = registry.list_tools(include_disabled=True)
|
|
37
|
+
enabled = [t for t in tools if t.meta.enabled]
|
|
38
|
+
logger.debug(
|
|
39
|
+
f"UC plugin loaded: {len(enabled)}/{len(tools)} tools enabled "
|
|
40
|
+
f"from {USER_UC_DIR}"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Register startup callback
|
|
45
|
+
register_callback("startup", _on_startup)
|
|
46
|
+
|
|
47
|
+
logger.debug("Universal Constructor plugin callbacks registered")
|