pdd-cli 0.0.90__py3-none-any.whl → 0.0.118__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.
- pdd/__init__.py +38 -6
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +497 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +526 -0
- pdd/agentic_common.py +521 -786
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +426 -0
- pdd/agentic_fix.py +118 -3
- pdd/agentic_update.py +25 -8
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +63 -53
- pdd/auto_include.py +185 -3
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +195 -23
- pdd/cmd_test_main.py +345 -197
- pdd/code_generator.py +4 -2
- pdd/code_generator_main.py +118 -32
- pdd/commands/__init__.py +6 -0
- pdd/commands/analysis.py +87 -29
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +290 -0
- pdd/commands/fix.py +136 -113
- pdd/commands/maintenance.py +3 -2
- pdd/commands/misc.py +8 -0
- pdd/commands/modify.py +190 -164
- pdd/commands/sessions.py +284 -0
- pdd/construct_paths.py +334 -32
- pdd/context_generator_main.py +167 -170
- pdd/continue_generation.py +6 -3
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +27 -3
- pdd/core/cloud.py +237 -0
- pdd/core/errors.py +4 -0
- pdd/core/remote_session.py +61 -0
- pdd/crash_main.py +219 -23
- pdd/data/llm_model.csv +4 -4
- pdd/docs/prompting_guide.md +864 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
- pdd/fix_code_loop.py +208 -34
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +291 -38
- pdd/fix_main.py +204 -4
- pdd/fix_verification_errors_loop.py +235 -26
- pdd/fix_verification_main.py +269 -83
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +46 -5
- pdd/generate_test.py +212 -151
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +309 -20
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +7 -5
- pdd/insert_includes.py +2 -1
- pdd/llm_invoke.py +459 -95
- pdd/load_prompt_template.py +15 -34
- pdd/path_resolution.py +140 -0
- pdd/postprocess.py +4 -1
- pdd/preprocess.py +68 -12
- pdd/preprocess_main.py +33 -1
- pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
- pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
- pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
- pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
- pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
- pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
- pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
- pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
- pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
- pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
- pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
- pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +131 -0
- pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
- pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
- pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
- pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
- pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
- pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
- pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
- pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
- pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
- pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
- pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
- pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
- pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
- pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
- pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +2 -2
- pdd/prompts/agentic_update_LLM.prompt +192 -338
- pdd/prompts/auto_include_LLM.prompt +22 -0
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +571 -14
- pdd/prompts/fix_code_module_errors_LLM.prompt +8 -0
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +1 -0
- pdd/prompts/generate_test_LLM.prompt +20 -1
- pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
- pdd/prompts/insert_includes_LLM.prompt +262 -252
- pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/remote_session.py +876 -0
- pdd/server/__init__.py +52 -0
- pdd/server/app.py +335 -0
- pdd/server/click_executor.py +587 -0
- pdd/server/executor.py +338 -0
- pdd/server/jobs.py +661 -0
- pdd/server/models.py +241 -0
- pdd/server/routes/__init__.py +31 -0
- pdd/server/routes/architecture.py +451 -0
- pdd/server/routes/auth.py +364 -0
- pdd/server/routes/commands.py +929 -0
- pdd/server/routes/config.py +42 -0
- pdd/server/routes/files.py +603 -0
- pdd/server/routes/prompts.py +1322 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +209 -0
- pdd/server/token_counter.py +222 -0
- pdd/summarize_directory.py +236 -237
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +329 -47
- pdd/sync_main.py +272 -28
- pdd/sync_orchestration.py +136 -75
- pdd/template_expander.py +161 -0
- pdd/templates/architecture/architecture_json.prompt +41 -46
- pdd/trace.py +1 -1
- pdd/track_cost.py +0 -13
- pdd/unfinished_prompt.py +2 -1
- pdd/update_main.py +23 -5
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +15 -10
- pdd_cli-0.0.118.dist-info/RECORD +227 -0
- pdd_cli-0.0.90.dist-info/RECORD +0 -153
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
pdd/core/cloud.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centralized cloud configuration for PDD CLI commands.
|
|
3
|
+
|
|
4
|
+
Provides consistent cloud URL configuration and JWT token handling
|
|
5
|
+
across all cloud-enabled commands (generate, fix, test, sync, etc.).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import os
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
|
|
16
|
+
from ..get_jwt_token import (
|
|
17
|
+
AuthError,
|
|
18
|
+
NetworkError,
|
|
19
|
+
RateLimitError,
|
|
20
|
+
TokenError,
|
|
21
|
+
UserCancelledError,
|
|
22
|
+
get_jwt_token as device_flow_get_token,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
console = Console()
|
|
26
|
+
|
|
27
|
+
# Environment variable names
|
|
28
|
+
FIREBASE_API_KEY_ENV = "NEXT_PUBLIC_FIREBASE_API_KEY"
|
|
29
|
+
GITHUB_CLIENT_ID_ENV = "GITHUB_CLIENT_ID"
|
|
30
|
+
PDD_CLOUD_URL_ENV = "PDD_CLOUD_URL"
|
|
31
|
+
PDD_JWT_TOKEN_ENV = "PDD_JWT_TOKEN"
|
|
32
|
+
|
|
33
|
+
# Default cloud endpoints
|
|
34
|
+
DEFAULT_BASE_URL = "https://us-central1-prompt-driven-development.cloudfunctions.net"
|
|
35
|
+
|
|
36
|
+
# Endpoint paths (can be extended as more endpoints are added)
|
|
37
|
+
CLOUD_ENDPOINTS = {
|
|
38
|
+
"generateCode": "/generateCode",
|
|
39
|
+
"generateExample": "/generateExample",
|
|
40
|
+
"generateTest": "/generateTest",
|
|
41
|
+
"generateBugTest": "/generateBugTest",
|
|
42
|
+
"fixCode": "/fixCode",
|
|
43
|
+
"crashCode": "/crashCode",
|
|
44
|
+
"verifyCode": "/verifyCode",
|
|
45
|
+
"syncState": "/syncState",
|
|
46
|
+
"trackUsage": "/trackUsage",
|
|
47
|
+
"getCreditBalance": "/getCreditBalance",
|
|
48
|
+
"llmInvoke": "/llmInvoke",
|
|
49
|
+
# Remote session endpoints
|
|
50
|
+
"registerSession": "/registerSession",
|
|
51
|
+
"listSessions": "/listSessions",
|
|
52
|
+
"heartbeatSession": "/heartbeatSession",
|
|
53
|
+
"deregisterSession": "/deregisterSession",
|
|
54
|
+
# Command relay endpoints (Firestore message bus)
|
|
55
|
+
"getCommands": "/getCommands",
|
|
56
|
+
"getCommandStatus": "/getCommandStatus",
|
|
57
|
+
"updateCommand": "/updateCommand",
|
|
58
|
+
"cancelCommand": "/cancelCommand",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class CloudConfig:
|
|
63
|
+
"""Centralized cloud configuration for all PDD commands."""
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def _ensure_default_env() -> None:
|
|
67
|
+
"""Default PDD_ENV for CLI usage when unset."""
|
|
68
|
+
if os.environ.get("PDD_ENV"):
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
# Local/emulator signals should keep PDD_ENV local.
|
|
72
|
+
if (os.environ.get("FUNCTIONS_EMULATOR") or
|
|
73
|
+
os.environ.get("FIREBASE_AUTH_EMULATOR_HOST") or
|
|
74
|
+
os.environ.get("FIREBASE_EMULATOR_HUB")):
|
|
75
|
+
os.environ["PDD_ENV"] = "local"
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
cloud_url = (os.environ.get(PDD_CLOUD_URL_ENV) or "").lower()
|
|
79
|
+
if cloud_url:
|
|
80
|
+
if any(host in cloud_url for host in ("localhost", "127.0.0.1", "0.0.0.0")):
|
|
81
|
+
os.environ["PDD_ENV"] = "local"
|
|
82
|
+
return
|
|
83
|
+
if "prompt-driven-development-stg" in cloud_url or "staging" in cloud_url:
|
|
84
|
+
os.environ["PDD_ENV"] = "staging"
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# Default to production for typical CLI usage.
|
|
88
|
+
os.environ["PDD_ENV"] = "prod"
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def get_base_url() -> str:
|
|
92
|
+
"""Get cloud base URL, allowing override via PDD_CLOUD_URL.
|
|
93
|
+
|
|
94
|
+
For testing against different environments:
|
|
95
|
+
- Local emulator: http://127.0.0.1:5555/prompt-driven-development/us-central1
|
|
96
|
+
- Staging: https://us-central1-prompt-driven-development-stg.cloudfunctions.net
|
|
97
|
+
- Production: (default) https://us-central1-prompt-driven-development.cloudfunctions.net
|
|
98
|
+
"""
|
|
99
|
+
custom_url = os.environ.get(PDD_CLOUD_URL_ENV)
|
|
100
|
+
if custom_url:
|
|
101
|
+
# If full URL provided (with endpoint), extract base
|
|
102
|
+
# If base URL provided, use as-is
|
|
103
|
+
return custom_url.rstrip("/")
|
|
104
|
+
return DEFAULT_BASE_URL
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def get_endpoint_url(endpoint_name: str) -> str:
|
|
108
|
+
"""Get full URL for a specific cloud endpoint.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
endpoint_name: Name of endpoint (e.g., 'generateCode', 'syncState')
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Full URL for the endpoint
|
|
115
|
+
"""
|
|
116
|
+
base = CloudConfig.get_base_url()
|
|
117
|
+
|
|
118
|
+
# Check if PDD_CLOUD_URL already includes the endpoint
|
|
119
|
+
custom_url = os.environ.get(PDD_CLOUD_URL_ENV, "")
|
|
120
|
+
if endpoint_name in custom_url:
|
|
121
|
+
return custom_url
|
|
122
|
+
|
|
123
|
+
path = CLOUD_ENDPOINTS.get(endpoint_name, f"/{endpoint_name}")
|
|
124
|
+
return f"{base}{path}"
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def get_jwt_token(
|
|
128
|
+
verbose: bool = False,
|
|
129
|
+
app_name: str = "PDD Code Generator"
|
|
130
|
+
) -> Optional[str]:
|
|
131
|
+
"""Get JWT token for cloud authentication.
|
|
132
|
+
|
|
133
|
+
Checks PDD_JWT_TOKEN environment variable first (for testing/CI),
|
|
134
|
+
then falls back to interactive device flow authentication.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
verbose: Whether to print status messages
|
|
138
|
+
app_name: Application name for device flow
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
JWT token string, or None if authentication failed
|
|
142
|
+
|
|
143
|
+
Note:
|
|
144
|
+
Callers should handle None return by falling back to local execution.
|
|
145
|
+
"""
|
|
146
|
+
# Default env to prod for typical CLI usage (unless emulator/custom URL says otherwise).
|
|
147
|
+
CloudConfig._ensure_default_env()
|
|
148
|
+
|
|
149
|
+
# Check for pre-injected token (testing/CI)
|
|
150
|
+
injected_token = os.environ.get(PDD_JWT_TOKEN_ENV)
|
|
151
|
+
if injected_token:
|
|
152
|
+
if verbose:
|
|
153
|
+
console.print(f"[info]Using injected JWT token from {PDD_JWT_TOKEN_ENV}[/info]")
|
|
154
|
+
return injected_token
|
|
155
|
+
|
|
156
|
+
# Standard device flow authentication
|
|
157
|
+
try:
|
|
158
|
+
firebase_api_key = os.environ.get(FIREBASE_API_KEY_ENV)
|
|
159
|
+
github_client_id = os.environ.get(GITHUB_CLIENT_ID_ENV)
|
|
160
|
+
|
|
161
|
+
if not firebase_api_key:
|
|
162
|
+
raise AuthError(f"{FIREBASE_API_KEY_ENV} not set.")
|
|
163
|
+
if not github_client_id:
|
|
164
|
+
raise AuthError(f"{GITHUB_CLIENT_ID_ENV} not set.")
|
|
165
|
+
|
|
166
|
+
return asyncio.run(device_flow_get_token(
|
|
167
|
+
firebase_api_key=firebase_api_key,
|
|
168
|
+
github_client_id=github_client_id,
|
|
169
|
+
app_name=app_name
|
|
170
|
+
))
|
|
171
|
+
except (AuthError, NetworkError, TokenError, UserCancelledError, RateLimitError) as e:
|
|
172
|
+
# Always display auth errors (both these expected ones and the unexpected ones handled below) - critical for debugging auth issues
|
|
173
|
+
console.print(f"[yellow]Cloud authentication error: {e}[/yellow]")
|
|
174
|
+
return None
|
|
175
|
+
except Exception as e:
|
|
176
|
+
# Always display unexpected errors too
|
|
177
|
+
console.print(f"[yellow]Unexpected auth error: {e}[/yellow]")
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def is_running_in_cloud() -> bool:
|
|
182
|
+
"""Check if we're running inside a cloud environment.
|
|
183
|
+
|
|
184
|
+
Detects Google Cloud Functions/Cloud Run via K_SERVICE env var,
|
|
185
|
+
or local emulator via FUNCTIONS_EMULATOR. This prevents infinite
|
|
186
|
+
loops when cloud endpoints call the CLI internally.
|
|
187
|
+
"""
|
|
188
|
+
return bool(
|
|
189
|
+
os.environ.get("K_SERVICE") or
|
|
190
|
+
os.environ.get("FUNCTIONS_EMULATOR")
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def is_cloud_enabled() -> bool:
|
|
195
|
+
"""Check if cloud features are available.
|
|
196
|
+
|
|
197
|
+
Cloud is enabled if:
|
|
198
|
+
1. PDD_FORCE_LOCAL is NOT set (respects --local flag), AND
|
|
199
|
+
2. NOT already running inside a cloud environment (prevents infinite loops), AND
|
|
200
|
+
3. Either:
|
|
201
|
+
a. PDD_JWT_TOKEN is set (injected token for testing/CI), OR
|
|
202
|
+
b. Both FIREBASE_API_KEY and GITHUB_CLIENT_ID are set (for device flow auth)
|
|
203
|
+
"""
|
|
204
|
+
# Respect --local flag (sets PDD_FORCE_LOCAL=1)
|
|
205
|
+
if os.environ.get("PDD_FORCE_LOCAL"):
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
# CRITICAL: Never enable cloud mode when already running in cloud
|
|
209
|
+
# This prevents infinite loops when cloud endpoints call CLI internally
|
|
210
|
+
if CloudConfig.is_running_in_cloud():
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
# Check for injected token first (testing/CI scenario)
|
|
214
|
+
if os.environ.get(PDD_JWT_TOKEN_ENV):
|
|
215
|
+
return True
|
|
216
|
+
# Check for device flow auth credentials
|
|
217
|
+
return bool(
|
|
218
|
+
os.environ.get(FIREBASE_API_KEY_ENV) and
|
|
219
|
+
os.environ.get(GITHUB_CLIENT_ID_ENV)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# Re-export exception classes for convenience
|
|
224
|
+
__all__ = [
|
|
225
|
+
'CloudConfig',
|
|
226
|
+
'AuthError',
|
|
227
|
+
'NetworkError',
|
|
228
|
+
'TokenError',
|
|
229
|
+
'UserCancelledError',
|
|
230
|
+
'RateLimitError',
|
|
231
|
+
'FIREBASE_API_KEY_ENV',
|
|
232
|
+
'GITHUB_CLIENT_ID_ENV',
|
|
233
|
+
'PDD_CLOUD_URL_ENV',
|
|
234
|
+
'PDD_JWT_TOKEN_ENV',
|
|
235
|
+
'DEFAULT_BASE_URL',
|
|
236
|
+
'CLOUD_ENDPOINTS',
|
|
237
|
+
]
|
pdd/core/errors.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Error handling logic for PDD CLI.
|
|
3
3
|
"""
|
|
4
|
+
import os
|
|
4
5
|
import traceback
|
|
5
6
|
from typing import Any, Dict, List
|
|
6
7
|
import click
|
|
@@ -60,4 +61,7 @@ def handle_error(exception: Exception, command_name: str, quiet: bool):
|
|
|
60
61
|
console.print(escape(str(exception)))
|
|
61
62
|
else:
|
|
62
63
|
console.print(f" [error]An unexpected error occurred:[/error] {exception}", style="error")
|
|
64
|
+
strict_exit = os.environ.get("PDD_STRICT_EXIT", "").strip().lower() in {"1", "true", "yes", "on"}
|
|
65
|
+
if strict_exit:
|
|
66
|
+
raise SystemExit(1)
|
|
63
67
|
# Do NOT re-raise e here. Let the command function return None.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility to detect if running in a remote/SSH session or headless environment.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Optional, Tuple
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def is_remote_session() -> Tuple[bool, str]:
|
|
10
|
+
"""
|
|
11
|
+
Detect if the current session is remote (SSH) or headless.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Tuple[bool, str]: (is_remote, reason) where reason explains why
|
|
15
|
+
|
|
16
|
+
Detection criteria:
|
|
17
|
+
1. SSH environment variables (SSH_CONNECTION, SSH_CLIENT, SSH_TTY)
|
|
18
|
+
2. DISPLAY not set (headless Linux/Unix)
|
|
19
|
+
3. WSL without WSLg (Windows Subsystem for Linux)
|
|
20
|
+
"""
|
|
21
|
+
# Check SSH environment variables
|
|
22
|
+
if os.environ.get("SSH_CONNECTION"):
|
|
23
|
+
return True, "SSH_CONNECTION environment variable detected"
|
|
24
|
+
if os.environ.get("SSH_CLIENT"):
|
|
25
|
+
return True, "SSH_CLIENT environment variable detected"
|
|
26
|
+
if os.environ.get("SSH_TTY"):
|
|
27
|
+
return True, "SSH_TTY environment variable detected"
|
|
28
|
+
|
|
29
|
+
# Check for headless environment (no DISPLAY on Unix/Linux)
|
|
30
|
+
if sys.platform in ("linux", "linux2") or sys.platform == "darwin":
|
|
31
|
+
if not os.environ.get("DISPLAY"):
|
|
32
|
+
return True, "No DISPLAY environment variable (headless)"
|
|
33
|
+
|
|
34
|
+
# Check for WSL without WSLg (older WSL versions)
|
|
35
|
+
if os.environ.get("WSL_DISTRO_NAME") and not os.environ.get("WAYLAND_DISPLAY"):
|
|
36
|
+
return True, "WSL environment without display server"
|
|
37
|
+
|
|
38
|
+
return False, "Local session with display capability"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def should_skip_browser(explicit_flag: Optional[bool] = None) -> Tuple[bool, str]:
|
|
42
|
+
"""
|
|
43
|
+
Determine if browser opening should be skipped.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
explicit_flag: True to force browser, False to force no-browser, None to auto-detect
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Tuple[bool, str]: (skip_browser, reason)
|
|
50
|
+
"""
|
|
51
|
+
if explicit_flag is True:
|
|
52
|
+
return False, "User explicitly requested browser opening (--browser flag)"
|
|
53
|
+
if explicit_flag is False:
|
|
54
|
+
return True, "User explicitly requested no browser (--no-browser flag)"
|
|
55
|
+
|
|
56
|
+
# Auto-detect
|
|
57
|
+
is_remote, reason = is_remote_session()
|
|
58
|
+
if is_remote:
|
|
59
|
+
return True, f"Auto-detected remote session: {reason}"
|
|
60
|
+
|
|
61
|
+
return False, "Local session detected, will attempt browser opening"
|
pdd/crash_main.py
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from typing import Tuple, Optional, Dict, Any
|
|
3
|
+
import json
|
|
3
4
|
import click
|
|
4
5
|
from rich import print as rprint
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
5
8
|
from pathlib import Path
|
|
6
9
|
|
|
10
|
+
import requests
|
|
11
|
+
import os
|
|
12
|
+
|
|
7
13
|
from .config_resolution import resolve_effective_config
|
|
8
14
|
from .construct_paths import construct_paths
|
|
9
15
|
from .fix_code_loop import fix_code_loop
|
|
16
|
+
from .core.cloud import CloudConfig
|
|
17
|
+
from .get_language import get_language
|
|
18
|
+
|
|
10
19
|
# Import fix_code_module_errors conditionally or ensure it's always available
|
|
11
20
|
try:
|
|
12
21
|
from .fix_code_module_errors import fix_code_module_errors
|
|
@@ -14,6 +23,19 @@ except ImportError:
|
|
|
14
23
|
# Handle case where fix_code_module_errors might not be available if not needed
|
|
15
24
|
fix_code_module_errors = None
|
|
16
25
|
|
|
26
|
+
# Cloud request timeout
|
|
27
|
+
CLOUD_REQUEST_TIMEOUT = 400 # seconds
|
|
28
|
+
|
|
29
|
+
console = Console()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _env_flag_enabled(name: str) -> bool:
|
|
33
|
+
"""Return True when an env var is set to a truthy value."""
|
|
34
|
+
value = os.environ.get(name)
|
|
35
|
+
if value is None:
|
|
36
|
+
return False
|
|
37
|
+
return str(value).strip().lower() in {"1", "true", "yes", "on"}
|
|
38
|
+
|
|
17
39
|
def crash_main(
|
|
18
40
|
ctx: click.Context,
|
|
19
41
|
prompt_file: str,
|
|
@@ -107,11 +129,33 @@ def crash_main(
|
|
|
107
129
|
code_updated: bool = False
|
|
108
130
|
program_updated: bool = False
|
|
109
131
|
|
|
132
|
+
# Determine cloud vs local execution preference
|
|
133
|
+
is_local_execution_preferred = ctx.obj.get('local', False)
|
|
134
|
+
cloud_only = _env_flag_enabled("PDD_CLOUD_ONLY") or _env_flag_enabled("PDD_NO_LOCAL_FALLBACK")
|
|
135
|
+
current_execution_is_local = is_local_execution_preferred and not cloud_only
|
|
136
|
+
|
|
137
|
+
# Cloud execution tracking
|
|
138
|
+
cloud_execution_attempted = False
|
|
139
|
+
cloud_execution_succeeded = False
|
|
140
|
+
|
|
110
141
|
if loop:
|
|
142
|
+
# Determine if loop should use cloud for LLM calls (hybrid mode)
|
|
143
|
+
# Local program execution stays local, but LLM fix calls can go to cloud
|
|
144
|
+
use_cloud_for_loop = not is_local_execution_preferred and not cloud_only
|
|
145
|
+
|
|
146
|
+
# If cloud_only is set but we're in loop mode, we still use hybrid approach
|
|
147
|
+
if cloud_only and not is_local_execution_preferred:
|
|
148
|
+
use_cloud_for_loop = True
|
|
149
|
+
|
|
150
|
+
if verbose:
|
|
151
|
+
mode_desc = "hybrid (local execution + cloud LLM)" if use_cloud_for_loop else "local"
|
|
152
|
+
console.print(Panel(f"Performing {mode_desc} crash fix loop...", title="[blue]Mode[/blue]", expand=False))
|
|
153
|
+
|
|
111
154
|
success, final_program, final_code, attempts, cost, model = fix_code_loop(
|
|
112
155
|
code_file, prompt_content, program_file, strength, temperature,
|
|
113
156
|
max_attempts if max_attempts is not None else 3, budget or 5.0, error_file, verbose, time_param,
|
|
114
|
-
prompt_file=prompt_file, agentic_fallback=agentic_fallback
|
|
157
|
+
prompt_file=prompt_file, agentic_fallback=agentic_fallback,
|
|
158
|
+
use_cloud=use_cloud_for_loop
|
|
115
159
|
)
|
|
116
160
|
# Always set final_code/final_program to something non-empty
|
|
117
161
|
if not final_code:
|
|
@@ -121,33 +165,182 @@ def crash_main(
|
|
|
121
165
|
code_updated = final_code != original_code_content
|
|
122
166
|
program_updated = final_program != original_program_content
|
|
123
167
|
else:
|
|
124
|
-
|
|
125
|
-
|
|
168
|
+
# Single-pass mode: attempt cloud first, fallback to local
|
|
169
|
+
if not current_execution_is_local:
|
|
170
|
+
if verbose:
|
|
171
|
+
console.print(Panel("Attempting cloud crash fix execution...", title="[blue]Mode[/blue]", expand=False))
|
|
126
172
|
|
|
127
|
-
|
|
128
|
-
program_content, prompt_content, code_content, error_content,
|
|
129
|
-
strength, temperature, verbose, time_param
|
|
130
|
-
)
|
|
131
|
-
success = True
|
|
132
|
-
attempts = 1
|
|
173
|
+
jwt_token = CloudConfig.get_jwt_token(verbose=verbose)
|
|
133
174
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
175
|
+
if not jwt_token:
|
|
176
|
+
if cloud_only:
|
|
177
|
+
console.print("[red]Cloud authentication failed.[/red]")
|
|
178
|
+
raise click.UsageError("Cloud authentication failed")
|
|
179
|
+
console.print("[yellow]Cloud authentication failed. Falling back to local execution.[/yellow]")
|
|
180
|
+
current_execution_is_local = True
|
|
139
181
|
|
|
140
|
-
|
|
141
|
-
|
|
182
|
+
if jwt_token and not current_execution_is_local:
|
|
183
|
+
cloud_execution_attempted = True
|
|
184
|
+
# Build cloud payload
|
|
185
|
+
payload = {
|
|
186
|
+
"programContent": program_content,
|
|
187
|
+
"promptContent": prompt_content,
|
|
188
|
+
"codeContent": code_content,
|
|
189
|
+
"errorContent": error_content,
|
|
190
|
+
"language": language,
|
|
191
|
+
"strength": strength,
|
|
192
|
+
"temperature": temperature,
|
|
193
|
+
"time": time_param if time_param is not None else 0.25,
|
|
194
|
+
"verbose": verbose,
|
|
195
|
+
"programPath": program_file,
|
|
196
|
+
"codePath": code_file,
|
|
197
|
+
}
|
|
142
198
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
199
|
+
headers = {
|
|
200
|
+
"Authorization": f"Bearer {jwt_token}",
|
|
201
|
+
"Content-Type": "application/json"
|
|
202
|
+
}
|
|
203
|
+
cloud_url = CloudConfig.get_endpoint_url("crashCode")
|
|
148
204
|
|
|
149
|
-
|
|
150
|
-
|
|
205
|
+
try:
|
|
206
|
+
response = requests.post(
|
|
207
|
+
cloud_url,
|
|
208
|
+
json=payload,
|
|
209
|
+
headers=headers,
|
|
210
|
+
timeout=CLOUD_REQUEST_TIMEOUT
|
|
211
|
+
)
|
|
212
|
+
response.raise_for_status()
|
|
213
|
+
|
|
214
|
+
response_data = response.json()
|
|
215
|
+
fixed_code = response_data.get("fixedCode", "")
|
|
216
|
+
fixed_program = response_data.get("fixedProgram", "")
|
|
217
|
+
update_code = response_data.get("updateCode", False)
|
|
218
|
+
update_program = response_data.get("updateProgram", False)
|
|
219
|
+
cost = float(response_data.get("totalCost", 0.0))
|
|
220
|
+
model = response_data.get("modelName", "cloud_model")
|
|
221
|
+
|
|
222
|
+
if not (fixed_code or fixed_program):
|
|
223
|
+
if cloud_only:
|
|
224
|
+
console.print("[red]Cloud execution returned no fixed code.[/red]")
|
|
225
|
+
raise click.UsageError("Cloud execution returned no fixed code")
|
|
226
|
+
console.print("[yellow]Cloud execution returned no fixed code. Falling back to local.[/yellow]")
|
|
227
|
+
current_execution_is_local = True
|
|
228
|
+
else:
|
|
229
|
+
cloud_execution_succeeded = True
|
|
230
|
+
success = True
|
|
231
|
+
attempts = 1
|
|
232
|
+
|
|
233
|
+
# Fallback if fixed_program is empty but update_program is True
|
|
234
|
+
if update_program and not fixed_program.strip():
|
|
235
|
+
fixed_program = program_content
|
|
236
|
+
if update_code and not fixed_code.strip():
|
|
237
|
+
fixed_code = code_content
|
|
238
|
+
|
|
239
|
+
final_code = fixed_code if update_code else code_content
|
|
240
|
+
final_program = fixed_program if update_program else program_content
|
|
241
|
+
|
|
242
|
+
# Always set final_code/final_program to something non-empty
|
|
243
|
+
if not final_code:
|
|
244
|
+
final_code = original_code_content
|
|
245
|
+
if not final_program:
|
|
246
|
+
final_program = original_program_content
|
|
247
|
+
|
|
248
|
+
code_updated = final_code != original_code_content
|
|
249
|
+
program_updated = final_program != original_program_content
|
|
250
|
+
|
|
251
|
+
if verbose:
|
|
252
|
+
console.print(Panel(
|
|
253
|
+
f"Cloud crash fix completed. Model: {model}, Cost: ${cost:.6f}",
|
|
254
|
+
title="[green]Cloud Success[/green]",
|
|
255
|
+
expand=False
|
|
256
|
+
))
|
|
257
|
+
|
|
258
|
+
except requests.exceptions.Timeout:
|
|
259
|
+
if cloud_only:
|
|
260
|
+
console.print(f"[red]Cloud execution timed out ({CLOUD_REQUEST_TIMEOUT}s).[/red]")
|
|
261
|
+
raise click.UsageError("Cloud execution timed out")
|
|
262
|
+
console.print(f"[yellow]Cloud execution timed out ({CLOUD_REQUEST_TIMEOUT}s). Falling back to local.[/yellow]")
|
|
263
|
+
current_execution_is_local = True
|
|
264
|
+
|
|
265
|
+
except requests.exceptions.HTTPError as e:
|
|
266
|
+
status_code = e.response.status_code if e.response else 0
|
|
267
|
+
err_content = e.response.text[:200] if e.response else "No response content"
|
|
268
|
+
|
|
269
|
+
# Non-recoverable errors: do NOT fall back to local
|
|
270
|
+
if status_code == 402: # Insufficient credits
|
|
271
|
+
try:
|
|
272
|
+
error_data = e.response.json()
|
|
273
|
+
current_balance = error_data.get("currentBalance", "unknown")
|
|
274
|
+
estimated_cost = error_data.get("estimatedCost", "unknown")
|
|
275
|
+
console.print(f"[red]Insufficient credits. Current balance: {current_balance}, estimated cost: {estimated_cost}[/red]")
|
|
276
|
+
except Exception:
|
|
277
|
+
console.print(f"[red]Insufficient credits: {err_content}[/red]")
|
|
278
|
+
raise click.UsageError("Insufficient credits for cloud crash fix")
|
|
279
|
+
elif status_code == 401: # Authentication error
|
|
280
|
+
console.print(f"[red]Authentication failed: {err_content}[/red]")
|
|
281
|
+
raise click.UsageError("Cloud authentication failed")
|
|
282
|
+
elif status_code == 403: # Authorization error (not approved)
|
|
283
|
+
console.print(f"[red]Access denied: {err_content}[/red]")
|
|
284
|
+
raise click.UsageError("Access denied - user not approved")
|
|
285
|
+
elif status_code == 400: # Validation error
|
|
286
|
+
console.print(f"[red]Invalid request: {err_content}[/red]")
|
|
287
|
+
raise click.UsageError(f"Invalid request: {err_content}")
|
|
288
|
+
else:
|
|
289
|
+
# Recoverable errors (5xx, unexpected errors): fall back to local
|
|
290
|
+
if cloud_only:
|
|
291
|
+
console.print(f"[red]Cloud HTTP error ({status_code}): {err_content}[/red]")
|
|
292
|
+
raise click.UsageError(f"Cloud HTTP error ({status_code}): {err_content}")
|
|
293
|
+
console.print(f"[yellow]Cloud HTTP error ({status_code}): {err_content}. Falling back to local.[/yellow]")
|
|
294
|
+
current_execution_is_local = True
|
|
295
|
+
|
|
296
|
+
except requests.exceptions.RequestException as e:
|
|
297
|
+
if cloud_only:
|
|
298
|
+
console.print(f"[red]Cloud network error: {e}[/red]")
|
|
299
|
+
raise click.UsageError(f"Cloud network error: {e}")
|
|
300
|
+
console.print(f"[yellow]Cloud network error: {e}. Falling back to local.[/yellow]")
|
|
301
|
+
current_execution_is_local = True
|
|
302
|
+
|
|
303
|
+
except json.JSONDecodeError:
|
|
304
|
+
if cloud_only:
|
|
305
|
+
console.print("[red]Cloud returned invalid JSON.[/red]")
|
|
306
|
+
raise click.UsageError("Cloud returned invalid JSON")
|
|
307
|
+
console.print("[yellow]Cloud returned invalid JSON. Falling back to local.[/yellow]")
|
|
308
|
+
current_execution_is_local = True
|
|
309
|
+
|
|
310
|
+
# Local execution path (when cloud failed/skipped or local preferred)
|
|
311
|
+
if not cloud_execution_succeeded:
|
|
312
|
+
if fix_code_module_errors is None:
|
|
313
|
+
raise ImportError("fix_code_module_errors is required but not available.")
|
|
314
|
+
|
|
315
|
+
if verbose:
|
|
316
|
+
console.print(Panel("Performing local crash fix...", title="[blue]Mode[/blue]", expand=False))
|
|
317
|
+
|
|
318
|
+
update_program, update_code, fixed_program, fixed_code, _, cost, model = fix_code_module_errors(
|
|
319
|
+
program_content, prompt_content, code_content, error_content,
|
|
320
|
+
strength, temperature, time_param, verbose,
|
|
321
|
+
program_path=program_file,
|
|
322
|
+
code_path=code_file,
|
|
323
|
+
)
|
|
324
|
+
success = True
|
|
325
|
+
attempts = 1
|
|
326
|
+
|
|
327
|
+
# Fallback if fixed_program is empty but update_program is True
|
|
328
|
+
if update_program and not fixed_program.strip():
|
|
329
|
+
fixed_program = program_content
|
|
330
|
+
if update_code and not fixed_code.strip():
|
|
331
|
+
fixed_code = code_content
|
|
332
|
+
|
|
333
|
+
final_code = fixed_code if update_code else code_content
|
|
334
|
+
final_program = fixed_program if update_program else program_content
|
|
335
|
+
|
|
336
|
+
# Always set final_code/final_program to something non-empty
|
|
337
|
+
if not final_code:
|
|
338
|
+
final_code = original_code_content
|
|
339
|
+
if not final_program:
|
|
340
|
+
final_program = original_program_content
|
|
341
|
+
|
|
342
|
+
code_updated = final_code != original_code_content
|
|
343
|
+
program_updated = final_program != original_program_content
|
|
151
344
|
|
|
152
345
|
output_code_path_str = output_file_paths.get("output")
|
|
153
346
|
output_program_path_str = output_file_paths.get("output_program")
|
|
@@ -206,6 +399,9 @@ def crash_main(
|
|
|
206
399
|
except click.Abort:
|
|
207
400
|
# User cancelled - re-raise to stop the sync loop
|
|
208
401
|
raise
|
|
402
|
+
except click.UsageError:
|
|
403
|
+
# Re-raise UsageError for proper CLI handling (e.g., cloud auth failures, insufficient credits)
|
|
404
|
+
raise
|
|
209
405
|
except Exception as e:
|
|
210
406
|
if not quiet:
|
|
211
407
|
rprint(f"[bold red]An unexpected error occurred:[/bold red] {str(e)}")
|
pdd/data/llm_model.csv
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
provider,model,input,output,coding_arena_elo,base_url,api_key,max_reasoning_tokens,structured_output,reasoning_type,location
|
|
2
2
|
OpenAI,gpt-5-nano,0.05,0.4,1249,,OPENAI_API_KEY,0,True,none,
|
|
3
|
-
Google,vertex_ai/gemini-3-flash-preview,0.5,3.0,1430,,VERTEX_CREDENTIALS,0,True,effort,
|
|
3
|
+
Google,vertex_ai/gemini-3-flash-preview,0.5,3.0,1430,,VERTEX_CREDENTIALS,0,True,effort,global
|
|
4
4
|
Google,gemini/gemini-3-pro-preview,1.25,10.0,1487,,GEMINI_API_KEY,0,True,effort,
|
|
5
5
|
Google,vertex_ai/claude-sonnet-4-5,3.0,15.0,1370,,VERTEX_CREDENTIALS,128000,True,budget,
|
|
6
6
|
Google,vertex_ai/gemini-3-pro-preview,1.25,10.0,1487,,VERTEX_CREDENTIALS,0,True,effort,
|
|
@@ -8,12 +8,12 @@ OpenAI,gpt-5.1-codex-mini,0.25,2.0,1325,,OPENAI_API_KEY,0,True,effort,
|
|
|
8
8
|
OpenAI,gpt-5.2,1.75,14.0,1486,,OPENAI_API_KEY,0,True,effort,
|
|
9
9
|
OpenAI,gpt-5.1-codex,1.25,10.0,1478,,OPENAI_API_KEY,0,True,effort,
|
|
10
10
|
OpenAI,gpt-5.1-codex-max,1.25,10.0,1480,,OPENAI_API_KEY,0,True,effort,
|
|
11
|
-
Google,vertex_ai/deepseek-ai/deepseek-v3.2-maas,0.28,0.42,1450,,VERTEX_CREDENTIALS,0,
|
|
11
|
+
Google,vertex_ai/deepseek-ai/deepseek-v3.2-maas,0.28,0.42,1450,,VERTEX_CREDENTIALS,0,True,effort,global
|
|
12
12
|
Fireworks,fireworks_ai/accounts/fireworks/models/qwen3-coder-480b-a35b-instruct,0.45,1.80,1363,,FIREWORKS_API_KEY,0,False,none,
|
|
13
|
-
Google,vertex_ai/claude-opus-4-5,5.0,25.0,1465,,VERTEX_CREDENTIALS,128000,True,budget,
|
|
13
|
+
Google,vertex_ai/claude-opus-4-5,5.0,25.0,1465,,VERTEX_CREDENTIALS,128000,True,budget,global
|
|
14
14
|
OpenAI,openai/mlx-community/Qwen3-30B-A3B-4bit,0,0,1040,http://localhost:8080,,0,False,none,
|
|
15
15
|
lm_studio,lm_studio/openai-gpt-oss-120b-mlx-6,0.0001,0,1082,http://localhost:1234/v1,,0,True,effort,
|
|
16
|
-
Fireworks,fireworks_ai/accounts/fireworks/models/glm-
|
|
16
|
+
Fireworks,fireworks_ai/accounts/fireworks/models/glm-4p7,0.60,2.20,1481,,FIREWORKS_API_KEY,0,False,none,
|
|
17
17
|
OpenAI,groq/moonshotai/kimi-k2-instruct-0905,1.0,3.0,1330,,GROQ_API_KEY,0,True,none,
|
|
18
18
|
Anthropic,anthropic/claude-sonnet-4-5-20250929,3.0,15.0,1370,,ANTHROPIC_API_KEY,128000,True,budget,
|
|
19
19
|
Anthropic,anthropic/claude-opus-4-5-20251101,5.0,25.0,1474,,ANTHROPIC_API_KEY,128000,True,budget,
|