code-puppy 0.0.373__py3-none-any.whl → 0.0.375__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 +60 -4
- code_puppy/agents/base_agent.py +61 -4
- code_puppy/agents/json_agent.py +30 -7
- code_puppy/callbacks.py +125 -0
- 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 +48 -7
- code_puppy/messaging/subagent_console.py +0 -1
- code_puppy/model_factory.py +63 -258
- code_puppy/model_utils.py +33 -1
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +106 -1
- code_puppy/plugins/antigravity_oauth/utils.py +2 -3
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +85 -3
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +88 -0
- code_puppy/plugins/ralph/__init__.py +13 -0
- code_puppy/plugins/ralph/agents.py +433 -0
- code_puppy/plugins/ralph/commands.py +208 -0
- code_puppy/plugins/ralph/loop_controller.py +285 -0
- code_puppy/plugins/ralph/models.py +125 -0
- code_puppy/plugins/ralph/register_callbacks.py +133 -0
- code_puppy/plugins/ralph/state_manager.py +322 -0
- code_puppy/plugins/ralph/tools.py +451 -0
- 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 +169 -1
- code_puppy/tools/agent_tools.py +1 -1
- code_puppy/tools/command_runner.py +23 -9
- code_puppy/tools/universal_constructor.py +889 -0
- {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/METADATA +1 -1
- {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/RECORD +44 -28
- {code_puppy-0.0.373.data → code_puppy-0.0.375.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.373.data → code_puppy-0.0.375.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""Antigravity OAuth Plugin callbacks for Code Puppy CLI.
|
|
1
|
+
"""Antigravity OAuth Plugin callbacks for Code Puppy CLI.
|
|
2
|
+
|
|
3
|
+
Provides OAuth authentication for Antigravity models and registers
|
|
4
|
+
the 'antigravity' model type handler.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
|
@@ -30,6 +34,8 @@ from .oauth import (
|
|
|
30
34
|
prepare_oauth_context,
|
|
31
35
|
)
|
|
32
36
|
from .storage import clear_accounts
|
|
37
|
+
from .token import is_token_expired, refresh_access_token
|
|
38
|
+
from .transport import create_antigravity_client
|
|
33
39
|
from .utils import (
|
|
34
40
|
add_models_to_config,
|
|
35
41
|
load_antigravity_models,
|
|
@@ -408,6 +414,105 @@ def _handle_custom_command(command: str, name: str) -> Optional[bool]:
|
|
|
408
414
|
return None
|
|
409
415
|
|
|
410
416
|
|
|
417
|
+
def _create_antigravity_model(model_name: str, model_config: Dict, config: Dict) -> Any:
|
|
418
|
+
"""Create an Antigravity model instance.
|
|
419
|
+
|
|
420
|
+
This handler is registered via the 'register_model_type' callback to handle
|
|
421
|
+
models with type='antigravity'.
|
|
422
|
+
"""
|
|
423
|
+
from code_puppy.gemini_model import GeminiModel
|
|
424
|
+
from code_puppy.model_factory import get_custom_config
|
|
425
|
+
|
|
426
|
+
# Try to import custom model for thinking signatures
|
|
427
|
+
try:
|
|
428
|
+
from .antigravity_model import AntigravityModel
|
|
429
|
+
except ImportError:
|
|
430
|
+
AntigravityModel = None # type: ignore
|
|
431
|
+
|
|
432
|
+
url, headers, verify, api_key = get_custom_config(model_config)
|
|
433
|
+
if not api_key:
|
|
434
|
+
emit_warning(
|
|
435
|
+
f"API key is not set for Antigravity endpoint; skipping model '{model_config.get('name')}'."
|
|
436
|
+
)
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
# Get fresh access token (refresh if needed)
|
|
440
|
+
tokens = load_stored_tokens()
|
|
441
|
+
if not tokens:
|
|
442
|
+
emit_warning("Antigravity tokens not found; run /antigravity-auth first.")
|
|
443
|
+
return None
|
|
444
|
+
|
|
445
|
+
access_token = tokens.get("access_token", "")
|
|
446
|
+
refresh_token = tokens.get("refresh_token", "")
|
|
447
|
+
expires_at = tokens.get("expires_at")
|
|
448
|
+
|
|
449
|
+
# Refresh if expired or about to expire (initial check)
|
|
450
|
+
if is_token_expired(expires_at):
|
|
451
|
+
new_tokens = refresh_access_token(refresh_token)
|
|
452
|
+
if new_tokens:
|
|
453
|
+
access_token = new_tokens.access_token
|
|
454
|
+
refresh_token = new_tokens.refresh_token
|
|
455
|
+
expires_at = new_tokens.expires_at
|
|
456
|
+
tokens["access_token"] = new_tokens.access_token
|
|
457
|
+
tokens["refresh_token"] = new_tokens.refresh_token
|
|
458
|
+
tokens["expires_at"] = new_tokens.expires_at
|
|
459
|
+
save_tokens(tokens)
|
|
460
|
+
else:
|
|
461
|
+
emit_warning(
|
|
462
|
+
"Failed to refresh Antigravity token; run /antigravity-auth again."
|
|
463
|
+
)
|
|
464
|
+
return None
|
|
465
|
+
|
|
466
|
+
# Callback to persist tokens when proactively refreshed during session
|
|
467
|
+
def on_token_refreshed(new_tokens: Any) -> None:
|
|
468
|
+
"""Persist new tokens when proactively refreshed."""
|
|
469
|
+
try:
|
|
470
|
+
updated_tokens = load_stored_tokens() or {}
|
|
471
|
+
updated_tokens["access_token"] = new_tokens.access_token
|
|
472
|
+
updated_tokens["refresh_token"] = new_tokens.refresh_token
|
|
473
|
+
updated_tokens["expires_at"] = new_tokens.expires_at
|
|
474
|
+
save_tokens(updated_tokens)
|
|
475
|
+
logger.debug("Persisted proactively refreshed Antigravity tokens")
|
|
476
|
+
except Exception as e:
|
|
477
|
+
logger.warning("Failed to persist refreshed tokens: %s", e)
|
|
478
|
+
|
|
479
|
+
project_id = tokens.get("project_id", model_config.get("project_id", ""))
|
|
480
|
+
client = create_antigravity_client(
|
|
481
|
+
access_token=access_token,
|
|
482
|
+
project_id=project_id,
|
|
483
|
+
model_name=model_config["name"],
|
|
484
|
+
base_url=url,
|
|
485
|
+
headers=headers,
|
|
486
|
+
refresh_token=refresh_token,
|
|
487
|
+
expires_at=expires_at,
|
|
488
|
+
on_token_refreshed=on_token_refreshed,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# Use custom model with direct httpx client
|
|
492
|
+
if AntigravityModel:
|
|
493
|
+
model = AntigravityModel(
|
|
494
|
+
model_name=model_config["name"],
|
|
495
|
+
api_key=api_key or "", # Antigravity uses OAuth, key may be empty
|
|
496
|
+
base_url=url,
|
|
497
|
+
http_client=client,
|
|
498
|
+
)
|
|
499
|
+
else:
|
|
500
|
+
model = GeminiModel(
|
|
501
|
+
model_name=model_config["name"],
|
|
502
|
+
api_key=api_key or "",
|
|
503
|
+
base_url=url,
|
|
504
|
+
http_client=client,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
return model
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _register_model_types() -> List[Dict[str, Any]]:
|
|
511
|
+
"""Register the antigravity model type handler."""
|
|
512
|
+
return [{"type": "antigravity", "handler": _create_antigravity_model}]
|
|
513
|
+
|
|
514
|
+
|
|
411
515
|
# Register callbacks
|
|
412
516
|
register_callback("custom_command_help", _custom_help)
|
|
413
517
|
register_callback("custom_command", _handle_custom_command)
|
|
518
|
+
register_callback("register_model_type", _register_model_types)
|
|
@@ -77,9 +77,9 @@ def add_models_to_config(access_token: str, project_id: str = "") -> bool:
|
|
|
77
77
|
# Build custom headers
|
|
78
78
|
headers = dict(ANTIGRAVITY_HEADERS)
|
|
79
79
|
|
|
80
|
-
# Use
|
|
80
|
+
# Use antigravity type - handled by the plugin's register_model_type callback
|
|
81
81
|
models_config[prefixed_name] = {
|
|
82
|
-
"type": "
|
|
82
|
+
"type": "antigravity",
|
|
83
83
|
"name": model_id,
|
|
84
84
|
"custom_endpoint": {
|
|
85
85
|
"url": ANTIGRAVITY_ENDPOINT,
|
|
@@ -90,7 +90,6 @@ def add_models_to_config(access_token: str, project_id: str = "") -> bool:
|
|
|
90
90
|
"context_length": model_info.get("context_length", 200000),
|
|
91
91
|
"family": model_info.get("family", "other"),
|
|
92
92
|
"oauth_source": "antigravity-plugin",
|
|
93
|
-
"antigravity": True, # Flag to use Antigravity transport
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
# Add thinking budget if present
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
"""ChatGPT OAuth plugin callbacks aligned with ChatMock flow.
|
|
1
|
+
"""ChatGPT OAuth plugin callbacks aligned with ChatMock flow.
|
|
2
|
+
|
|
3
|
+
Provides OAuth authentication for ChatGPT models and registers
|
|
4
|
+
the 'chatgpt_oauth' model type handler.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
|
5
9
|
import os
|
|
6
|
-
from typing import List, Optional, Tuple
|
|
10
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
7
11
|
|
|
8
12
|
from code_puppy.callbacks import register_callback
|
|
9
13
|
from code_puppy.messaging import emit_info, emit_success, emit_warning
|
|
@@ -11,7 +15,12 @@ from code_puppy.model_switching import set_model_and_reload_agent
|
|
|
11
15
|
|
|
12
16
|
from .config import CHATGPT_OAUTH_CONFIG, get_token_storage_path
|
|
13
17
|
from .oauth_flow import run_oauth_flow
|
|
14
|
-
from .utils import
|
|
18
|
+
from .utils import (
|
|
19
|
+
get_valid_access_token,
|
|
20
|
+
load_chatgpt_models,
|
|
21
|
+
load_stored_tokens,
|
|
22
|
+
remove_chatgpt_models,
|
|
23
|
+
)
|
|
15
24
|
|
|
16
25
|
|
|
17
26
|
def _custom_help() -> List[Tuple[str, str]]:
|
|
@@ -90,5 +99,78 @@ def _handle_custom_command(command: str, name: str) -> Optional[bool]:
|
|
|
90
99
|
return None
|
|
91
100
|
|
|
92
101
|
|
|
102
|
+
def _create_chatgpt_oauth_model(
|
|
103
|
+
model_name: str, model_config: Dict, config: Dict
|
|
104
|
+
) -> Any:
|
|
105
|
+
"""Create a ChatGPT OAuth model instance.
|
|
106
|
+
|
|
107
|
+
This handler is registered via the 'register_model_type' callback to handle
|
|
108
|
+
models with type='chatgpt_oauth'.
|
|
109
|
+
"""
|
|
110
|
+
from pydantic_ai.models.openai import OpenAIResponsesModel
|
|
111
|
+
from pydantic_ai.providers.openai import OpenAIProvider
|
|
112
|
+
|
|
113
|
+
from code_puppy.chatgpt_codex_client import create_codex_async_client
|
|
114
|
+
from code_puppy.http_utils import get_cert_bundle_path
|
|
115
|
+
|
|
116
|
+
# Get a valid access token (refreshing if needed)
|
|
117
|
+
access_token = get_valid_access_token()
|
|
118
|
+
if not access_token:
|
|
119
|
+
emit_warning(
|
|
120
|
+
f"Failed to get valid ChatGPT OAuth token; skipping model '{model_config.get('name')}'. "
|
|
121
|
+
"Run /chatgpt-auth to authenticate."
|
|
122
|
+
)
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
# Get account_id from stored tokens (required for ChatGPT-Account-Id header)
|
|
126
|
+
tokens = load_stored_tokens()
|
|
127
|
+
account_id = tokens.get("account_id", "") if tokens else ""
|
|
128
|
+
if not account_id:
|
|
129
|
+
emit_warning(
|
|
130
|
+
f"No account_id found in ChatGPT OAuth tokens; skipping model '{model_config.get('name')}'. "
|
|
131
|
+
"Run /chatgpt-auth to re-authenticate."
|
|
132
|
+
)
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
# Build headers for ChatGPT Codex API
|
|
136
|
+
originator = CHATGPT_OAUTH_CONFIG.get("originator", "codex_cli_rs")
|
|
137
|
+
client_version = CHATGPT_OAUTH_CONFIG.get("client_version", "0.72.0")
|
|
138
|
+
|
|
139
|
+
headers = {
|
|
140
|
+
"ChatGPT-Account-Id": account_id,
|
|
141
|
+
"originator": originator,
|
|
142
|
+
"User-Agent": f"{originator}/{client_version}",
|
|
143
|
+
}
|
|
144
|
+
# Merge with any headers from model config
|
|
145
|
+
config_headers = model_config.get("custom_endpoint", {}).get("headers", {})
|
|
146
|
+
headers.update(config_headers)
|
|
147
|
+
|
|
148
|
+
# Get base URL - Codex API uses chatgpt.com, not api.openai.com
|
|
149
|
+
base_url = model_config.get("custom_endpoint", {}).get(
|
|
150
|
+
"url", CHATGPT_OAUTH_CONFIG["api_base_url"]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Create HTTP client with Codex interceptor for store=false injection
|
|
154
|
+
verify = get_cert_bundle_path()
|
|
155
|
+
client = create_codex_async_client(headers=headers, verify=verify)
|
|
156
|
+
|
|
157
|
+
provider = OpenAIProvider(
|
|
158
|
+
api_key=access_token,
|
|
159
|
+
base_url=base_url,
|
|
160
|
+
http_client=client,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# ChatGPT Codex API only supports Responses format
|
|
164
|
+
model = OpenAIResponsesModel(model_name=model_config["name"], provider=provider)
|
|
165
|
+
setattr(model, "provider", provider)
|
|
166
|
+
return model
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _register_model_types() -> List[Dict[str, Any]]:
|
|
170
|
+
"""Register the chatgpt_oauth model type handler."""
|
|
171
|
+
return [{"type": "chatgpt_oauth", "handler": _create_chatgpt_oauth_model}]
|
|
172
|
+
|
|
173
|
+
|
|
93
174
|
register_callback("custom_command_help", _custom_help)
|
|
94
175
|
register_callback("custom_command", _handle_custom_command)
|
|
176
|
+
register_callback("register_model_type", _register_model_types)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Claude Code OAuth Plugin for Code Puppy.
|
|
3
|
+
|
|
4
|
+
Provides OAuth authentication for Claude Code models and registers
|
|
5
|
+
the 'claude_code' model type handler.
|
|
3
6
|
"""
|
|
4
7
|
|
|
5
8
|
from __future__ import annotations
|
|
@@ -24,6 +27,7 @@ from .utils import (
|
|
|
24
27
|
build_authorization_url,
|
|
25
28
|
exchange_code_for_tokens,
|
|
26
29
|
fetch_claude_code_models,
|
|
30
|
+
get_valid_access_token,
|
|
27
31
|
load_claude_models_filtered,
|
|
28
32
|
load_stored_tokens,
|
|
29
33
|
prepare_oauth_context,
|
|
@@ -276,5 +280,89 @@ def _handle_custom_command(command: str, name: str) -> Optional[bool]:
|
|
|
276
280
|
return None
|
|
277
281
|
|
|
278
282
|
|
|
283
|
+
def _create_claude_code_model(model_name: str, model_config: Dict, config: Dict) -> Any:
|
|
284
|
+
"""Create a Claude Code model instance.
|
|
285
|
+
|
|
286
|
+
This handler is registered via the 'register_model_type' callback to handle
|
|
287
|
+
models with type='claude_code'.
|
|
288
|
+
"""
|
|
289
|
+
from anthropic import AsyncAnthropic
|
|
290
|
+
from pydantic_ai.models.anthropic import AnthropicModel
|
|
291
|
+
from pydantic_ai.providers.anthropic import AnthropicProvider
|
|
292
|
+
|
|
293
|
+
from code_puppy.claude_cache_client import (
|
|
294
|
+
ClaudeCacheAsyncClient,
|
|
295
|
+
patch_anthropic_client_messages,
|
|
296
|
+
)
|
|
297
|
+
from code_puppy.config import get_effective_model_settings
|
|
298
|
+
from code_puppy.http_utils import get_cert_bundle_path, get_http2
|
|
299
|
+
from code_puppy.model_factory import get_custom_config
|
|
300
|
+
|
|
301
|
+
url, headers, verify, api_key = get_custom_config(model_config)
|
|
302
|
+
|
|
303
|
+
# Refresh token if this is from the plugin
|
|
304
|
+
if model_config.get("oauth_source") == "claude-code-plugin":
|
|
305
|
+
refreshed_token = get_valid_access_token()
|
|
306
|
+
if refreshed_token:
|
|
307
|
+
api_key = refreshed_token
|
|
308
|
+
custom_endpoint = model_config.get("custom_endpoint")
|
|
309
|
+
if isinstance(custom_endpoint, dict):
|
|
310
|
+
custom_endpoint["api_key"] = refreshed_token
|
|
311
|
+
|
|
312
|
+
if not api_key:
|
|
313
|
+
emit_warning(
|
|
314
|
+
f"API key is not set for Claude Code endpoint; skipping model '{model_config.get('name')}'."
|
|
315
|
+
)
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
# Check if interleaved thinking is enabled (defaults to True for OAuth models)
|
|
319
|
+
effective_settings = get_effective_model_settings(model_name)
|
|
320
|
+
interleaved_thinking = effective_settings.get("interleaved_thinking", True)
|
|
321
|
+
|
|
322
|
+
# Handle anthropic-beta header based on interleaved_thinking setting
|
|
323
|
+
if "anthropic-beta" in headers:
|
|
324
|
+
beta_parts = [p.strip() for p in headers["anthropic-beta"].split(",")]
|
|
325
|
+
if interleaved_thinking:
|
|
326
|
+
if "interleaved-thinking-2025-05-14" not in beta_parts:
|
|
327
|
+
beta_parts.append("interleaved-thinking-2025-05-14")
|
|
328
|
+
else:
|
|
329
|
+
beta_parts = [p for p in beta_parts if "interleaved-thinking" not in p]
|
|
330
|
+
headers["anthropic-beta"] = ",".join(beta_parts) if beta_parts else None
|
|
331
|
+
if headers.get("anthropic-beta") is None:
|
|
332
|
+
del headers["anthropic-beta"]
|
|
333
|
+
elif interleaved_thinking:
|
|
334
|
+
headers["anthropic-beta"] = "interleaved-thinking-2025-05-14"
|
|
335
|
+
|
|
336
|
+
# Use a dedicated client wrapper that injects cache_control on /v1/messages
|
|
337
|
+
if verify is None:
|
|
338
|
+
verify = get_cert_bundle_path()
|
|
339
|
+
|
|
340
|
+
http2_enabled = get_http2()
|
|
341
|
+
|
|
342
|
+
client = ClaudeCacheAsyncClient(
|
|
343
|
+
headers=headers,
|
|
344
|
+
verify=verify,
|
|
345
|
+
timeout=180,
|
|
346
|
+
http2=http2_enabled,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
anthropic_client = AsyncAnthropic(
|
|
350
|
+
base_url=url,
|
|
351
|
+
http_client=client,
|
|
352
|
+
auth_token=api_key,
|
|
353
|
+
)
|
|
354
|
+
patch_anthropic_client_messages(anthropic_client)
|
|
355
|
+
anthropic_client.api_key = None
|
|
356
|
+
anthropic_client.auth_token = api_key
|
|
357
|
+
provider = AnthropicProvider(anthropic_client=anthropic_client)
|
|
358
|
+
return AnthropicModel(model_name=model_config["name"], provider=provider)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _register_model_types() -> List[Dict[str, Any]]:
|
|
362
|
+
"""Register the claude_code model type handler."""
|
|
363
|
+
return [{"type": "claude_code", "handler": _create_claude_code_model}]
|
|
364
|
+
|
|
365
|
+
|
|
279
366
|
register_callback("custom_command_help", _custom_help)
|
|
280
367
|
register_callback("custom_command", _handle_custom_command)
|
|
368
|
+
register_callback("register_model_type", _register_model_types)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Ralph Plugin - Autonomous AI agent loop for completing PRDs.
|
|
2
|
+
|
|
3
|
+
Based on Geoffrey Huntley's Ralph pattern: https://ghuntley.com/ralph/
|
|
4
|
+
|
|
5
|
+
This plugin provides:
|
|
6
|
+
- PRD Generator agent for creating detailed requirements
|
|
7
|
+
- Ralph Converter agent for converting PRDs to JSON format
|
|
8
|
+
- Ralph Orchestrator agent for autonomous execution
|
|
9
|
+
- Tools for managing prd.json and progress.txt
|
|
10
|
+
- /ralph commands for controlling the workflow
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__version__ = "0.1.0"
|