pdd-cli 0.0.45__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 +40 -8
- 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 +598 -0
- pdd/agentic_crash.py +534 -0
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +426 -0
- pdd/agentic_fix.py +1294 -0
- pdd/agentic_langtest.py +162 -0
- pdd/agentic_update.py +387 -0
- pdd/agentic_verify.py +183 -0
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +71 -51
- pdd/auto_include.py +245 -5
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +196 -23
- pdd/bug_to_unit_test.py +2 -0
- pdd/change_main.py +11 -4
- pdd/cli.py +22 -1181
- pdd/cmd_test_main.py +350 -150
- pdd/code_generator.py +60 -18
- pdd/code_generator_main.py +790 -57
- pdd/commands/__init__.py +48 -0
- pdd/commands/analysis.py +306 -0
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +290 -0
- pdd/commands/fix.py +163 -0
- pdd/commands/generate.py +257 -0
- pdd/commands/maintenance.py +175 -0
- pdd/commands/misc.py +87 -0
- pdd/commands/modify.py +256 -0
- pdd/commands/report.py +144 -0
- pdd/commands/sessions.py +284 -0
- pdd/commands/templates.py +215 -0
- pdd/commands/utility.py +110 -0
- pdd/config_resolution.py +58 -0
- pdd/conflicts_main.py +8 -3
- pdd/construct_paths.py +589 -111
- pdd/context_generator.py +10 -2
- pdd/context_generator_main.py +175 -76
- pdd/continue_generation.py +53 -10
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +527 -0
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +554 -0
- pdd/core/errors.py +67 -0
- pdd/core/remote_session.py +61 -0
- pdd/core/utils.py +90 -0
- pdd/crash_main.py +262 -33
- pdd/data/language_format.csv +71 -63
- pdd/data/llm_model.csv +20 -18
- pdd/detect_change_main.py +5 -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 +523 -95
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +491 -92
- pdd/fix_errors_from_unit_tests.py +4 -3
- pdd/fix_main.py +278 -21
- pdd/fix_verification_errors.py +12 -100
- pdd/fix_verification_errors_loop.py +529 -286
- pdd/fix_verification_main.py +294 -89
- 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 +139 -15
- pdd/generate_test.py +218 -146
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +318 -22
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +75 -0
- pdd/get_test_command.py +68 -0
- pdd/git_update.py +70 -19
- pdd/incremental_code_generator.py +2 -2
- pdd/insert_includes.py +13 -4
- pdd/llm_invoke.py +1711 -181
- pdd/load_prompt_template.py +19 -12
- pdd/path_resolution.py +140 -0
- pdd/pdd_completion.fish +25 -2
- pdd/pdd_completion.sh +30 -4
- pdd/pdd_completion.zsh +79 -4
- pdd/postprocess.py +14 -4
- pdd/preprocess.py +293 -24
- pdd/preprocess_main.py +41 -6
- 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_crash_explore_LLM.prompt +49 -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_explore_LLM.prompt +45 -0
- pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
- pdd/prompts/agentic_update_LLM.prompt +925 -0
- pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
- pdd/prompts/auto_include_LLM.prompt +122 -905
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +686 -27
- pdd/prompts/example_generator_LLM.prompt +22 -1
- pdd/prompts/extract_code_LLM.prompt +5 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
- pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
- pdd/prompts/extract_promptline_LLM.prompt +17 -11
- pdd/prompts/find_verification_errors_LLM.prompt +6 -0
- pdd/prompts/fix_code_module_errors_LLM.prompt +12 -2
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
- pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
- pdd/prompts/generate_test_LLM.prompt +41 -7
- pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
- pdd/prompts/increase_tests_LLM.prompt +1 -5
- pdd/prompts/insert_includes_LLM.prompt +316 -186
- pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/prompts/trace_LLM.prompt +25 -22
- pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
- pdd/prompts/update_prompt_LLM.prompt +22 -1
- pdd/pytest_output.py +127 -12
- pdd/remote_session.py +876 -0
- pdd/render_mermaid.py +236 -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/setup_tool.py +648 -0
- pdd/simple_math.py +2 -0
- pdd/split_main.py +3 -2
- pdd/summarize_directory.py +237 -195
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +839 -112
- pdd/sync_main.py +351 -57
- pdd/sync_orchestration.py +1400 -756
- pdd/sync_tui.py +848 -0
- pdd/template_expander.py +161 -0
- pdd/template_registry.py +264 -0
- pdd/templates/architecture/architecture_json.prompt +237 -0
- pdd/templates/generic/generate_prompt.prompt +174 -0
- pdd/trace.py +168 -12
- pdd/trace_main.py +4 -3
- pdd/track_cost.py +140 -63
- pdd/unfinished_prompt.py +51 -4
- pdd/update_main.py +567 -67
- pdd/update_model_costs.py +2 -2
- pdd/update_prompt.py +19 -4
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +29 -11
- pdd_cli-0.0.118.dist-info/RECORD +227 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +1 -1
- pdd_cli-0.0.45.dist-info/RECORD +0 -116
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
pdd/context_generator.py
CHANGED
|
@@ -16,6 +16,9 @@ def context_generator(
|
|
|
16
16
|
temperature: float = 0,
|
|
17
17
|
time: Optional[float] = DEFAULT_TIME,
|
|
18
18
|
verbose: bool = False,
|
|
19
|
+
source_file_path: str = None,
|
|
20
|
+
example_file_path: str = None,
|
|
21
|
+
module_name: str = None,
|
|
19
22
|
) -> tuple:
|
|
20
23
|
"""
|
|
21
24
|
Generates a concise example on how to use a given code module properly.
|
|
@@ -79,7 +82,10 @@ def context_generator(
|
|
|
79
82
|
input_json={
|
|
80
83
|
"code_module": code_module,
|
|
81
84
|
"processed_prompt": processed_prompt,
|
|
82
|
-
"language": language
|
|
85
|
+
"language": language,
|
|
86
|
+
"source_file_path": source_file_path or "",
|
|
87
|
+
"example_file_path": example_file_path or "",
|
|
88
|
+
"module_name": module_name or ""
|
|
83
89
|
},
|
|
84
90
|
strength=strength,
|
|
85
91
|
temperature=temperature,
|
|
@@ -95,6 +101,7 @@ def context_generator(
|
|
|
95
101
|
strength=0.5,
|
|
96
102
|
temperature=temperature,
|
|
97
103
|
time=time,
|
|
104
|
+
language=language,
|
|
98
105
|
verbose=verbose
|
|
99
106
|
)
|
|
100
107
|
except Exception as e:
|
|
@@ -112,6 +119,7 @@ def context_generator(
|
|
|
112
119
|
strength=strength,
|
|
113
120
|
temperature=temperature,
|
|
114
121
|
time=time,
|
|
122
|
+
language=language,
|
|
115
123
|
verbose=verbose
|
|
116
124
|
)
|
|
117
125
|
total_cost = llm_response['cost'] + unfinished_cost + continue_cost
|
|
@@ -149,4 +157,4 @@ if __name__ == "__main__":
|
|
|
149
157
|
print("[bold green]Generated Example Code:[/bold green]")
|
|
150
158
|
print(example_code)
|
|
151
159
|
print(f"[bold blue]Total Cost: ${total_cost:.6f}[/bold blue]")
|
|
152
|
-
print(f"[bold blue]Model Name: {model_name}[/bold blue]")
|
|
160
|
+
print(f"[bold blue]Model Name: {model_name}[/bold blue]")
|
pdd/context_generator_main.py
CHANGED
|
@@ -1,91 +1,190 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import ast
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
1
6
|
import sys
|
|
2
|
-
from
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional, Tuple, Dict, Any
|
|
3
9
|
import click
|
|
4
|
-
|
|
5
|
-
|
|
10
|
+
import httpx
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.text import Text
|
|
6
14
|
from .construct_paths import construct_paths
|
|
7
15
|
from .context_generator import context_generator
|
|
16
|
+
from .core.cloud import CloudConfig
|
|
17
|
+
# get_jwt_token imports removed - using CloudConfig.get_jwt_token() instead
|
|
18
|
+
from .preprocess import preprocess
|
|
19
|
+
from . import DEFAULT_STRENGTH, DEFAULT_TEMPERATURE
|
|
8
20
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Main function to generate example code from a prompt file and an existing code file.
|
|
21
|
+
console = Console()
|
|
22
|
+
CLOUD_TIMEOUT_SECONDS = 400.0
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
:param prompt_file: Path to the prompt file that generated the code.
|
|
15
|
-
:param code_file: Path to the existing code file.
|
|
16
|
-
:param output: Optional path to save the generated example code.
|
|
17
|
-
:return: A tuple containing the generated example code, total cost, and model name used.
|
|
18
|
-
"""
|
|
24
|
+
def _validate_and_fix_python_syntax(code: str, quiet: bool) -> str:
|
|
19
25
|
try:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
ast.parse(code)
|
|
27
|
+
return code
|
|
28
|
+
except SyntaxError:
|
|
29
|
+
if not quiet:
|
|
30
|
+
console.print("[yellow]Warning: Generated code has syntax errors. Attempting to fix...[/yellow]")
|
|
31
|
+
lines = code.splitlines()
|
|
32
|
+
json_markers = ['"explanation":', '"focus":', '"description":', '"code":', '"filename":']
|
|
33
|
+
cut_index = -1
|
|
34
|
+
for i in range(len(lines) - 1, -1, -1):
|
|
35
|
+
line = lines[i].strip()
|
|
36
|
+
if any(marker in line for marker in json_markers) or line == "}" or line == "},":
|
|
37
|
+
cut_index = i
|
|
38
|
+
if cut_index != -1:
|
|
39
|
+
candidate = "\n".join(lines[:cut_index])
|
|
40
|
+
try:
|
|
41
|
+
ast.parse(candidate)
|
|
42
|
+
if not quiet:
|
|
43
|
+
console.print("[green]Fix successful: Removed trailing metadata.[/green]")
|
|
44
|
+
return candidate
|
|
45
|
+
except SyntaxError:
|
|
46
|
+
pass
|
|
47
|
+
low = 0
|
|
48
|
+
high = cut_index if cut_index != -1 else len(lines)
|
|
49
|
+
valid_len = 0
|
|
50
|
+
while low < high:
|
|
51
|
+
mid = (low + high + 1) // 2
|
|
52
|
+
candidate = "\n".join(lines[:mid])
|
|
53
|
+
try:
|
|
54
|
+
ast.parse(candidate)
|
|
55
|
+
valid_len = mid
|
|
56
|
+
low = mid
|
|
57
|
+
except SyntaxError:
|
|
58
|
+
high = mid - 1
|
|
59
|
+
for i in range(len(lines), max(0, len(lines) - 50), -1):
|
|
60
|
+
candidate = "\n".join(lines[:i])
|
|
61
|
+
try:
|
|
62
|
+
ast.parse(candidate)
|
|
63
|
+
if not quiet:
|
|
64
|
+
console.print("[green]Fix successful: Truncated invalid tail content.[/green]")
|
|
65
|
+
return candidate
|
|
66
|
+
except SyntaxError:
|
|
67
|
+
continue
|
|
68
|
+
if not quiet:
|
|
69
|
+
console.print("[red]Fix failed: Could not automatically repair syntax.[/red]")
|
|
70
|
+
return code
|
|
35
71
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
code_content = input_strings["code_file"]
|
|
72
|
+
async def _run_cloud_generation(prompt_content: str, code_content: str, language: str, strength: float, temperature: float, verbose: bool, pdd_env: str, token: str) -> Tuple[Optional[str], float, str]:
|
|
73
|
+
"""Run cloud generation with the provided JWT token.
|
|
39
74
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
rprint("[bold red]Warning:[/bold red] Example generation failed, skipping file write")
|
|
66
|
-
|
|
67
|
-
# Provide user feedback
|
|
68
|
-
if not ctx.obj.get('quiet', False):
|
|
69
|
-
if example_code is not None:
|
|
70
|
-
rprint("[bold green]Example code generated successfully.[/bold green]")
|
|
71
|
-
rprint(f"[bold]Model used:[/bold] {model_name}")
|
|
72
|
-
rprint(f"[bold]Total cost:[/bold] ${total_cost:.6f}")
|
|
73
|
-
if final_output_path and example_code is not None:
|
|
74
|
-
rprint(f"[bold]Example code saved to:[/bold] {final_output_path}")
|
|
75
|
-
else:
|
|
76
|
-
rprint("[bold red]Example code generation failed.[/bold red]")
|
|
77
|
-
rprint(f"[bold]Total cost:[/bold] ${total_cost:.6f}")
|
|
75
|
+
Note: JWT token must be obtained BEFORE calling this async function to avoid
|
|
76
|
+
nested asyncio.run() calls (CloudConfig.get_jwt_token() uses asyncio.run internally).
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
processed_prompt = preprocess(prompt_content, recursive=True, double_curly_brackets=False)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
return None, 0.0, f"Preprocessing failed: {e}"
|
|
82
|
+
if verbose:
|
|
83
|
+
console.print(Panel(Text(processed_prompt[:500] + "..." if len(processed_prompt) > 500 else processed_prompt, overflow="fold"), title="[cyan]Preprocessed Prompt for Cloud[/cyan]", expand=False))
|
|
84
|
+
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
|
85
|
+
payload = {"promptContent": processed_prompt, "codeContent": code_content, "language": language, "strength": strength, "temperature": temperature, "verbose": verbose}
|
|
86
|
+
async with httpx.AsyncClient(timeout=CLOUD_TIMEOUT_SECONDS) as client:
|
|
87
|
+
try:
|
|
88
|
+
cloud_url = CloudConfig.get_endpoint_url("generateExample")
|
|
89
|
+
response = await client.post(cloud_url, json=payload, headers=headers)
|
|
90
|
+
response.raise_for_status()
|
|
91
|
+
data = response.json()
|
|
92
|
+
generated_code = data.get("generatedExample", "")
|
|
93
|
+
total_cost = float(data.get("totalCost", 0.0))
|
|
94
|
+
model_name = data.get("modelName", "cloud-model")
|
|
95
|
+
if not generated_code:
|
|
96
|
+
return None, 0.0, "Cloud function returned empty code."
|
|
97
|
+
return generated_code, total_cost, model_name
|
|
98
|
+
except Exception as e:
|
|
99
|
+
return None, 0.0, f"Cloud error: {e}"
|
|
78
100
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str, output: Optional[str]) -> Tuple[str, float, str]:
|
|
102
|
+
try:
|
|
103
|
+
input_file_paths = {"prompt_file": prompt_file, "code_file": code_file}
|
|
104
|
+
command_options = {"output": output}
|
|
105
|
+
resolved_config, input_strings, output_file_paths, language = construct_paths(input_file_paths=input_file_paths, force=ctx.obj.get('force', False), quiet=ctx.obj.get('quiet', False), command="example", command_options=command_options, context_override=ctx.obj.get('context'), confirm_callback=ctx.obj.get('confirm_callback'))
|
|
106
|
+
prompt_content = input_strings.get("prompt_file", "")
|
|
107
|
+
code_content = input_strings.get("code_file", "")
|
|
108
|
+
if output and not output.endswith("/") and not Path(output).is_dir():
|
|
109
|
+
resolved_output = output
|
|
83
110
|
else:
|
|
84
|
-
|
|
111
|
+
resolved_output = output_file_paths.get("output")
|
|
112
|
+
is_local = ctx.obj.get("local", False)
|
|
113
|
+
strength = ctx.obj.get('strength', DEFAULT_STRENGTH)
|
|
114
|
+
temperature = ctx.obj.get('temperature', DEFAULT_TEMPERATURE)
|
|
115
|
+
verbose = ctx.obj.get('verbose', False)
|
|
116
|
+
quiet = ctx.obj.get('quiet', False)
|
|
117
|
+
pdd_env = os.environ.get("PDD_ENV", "local")
|
|
118
|
+
generated_code = None
|
|
119
|
+
total_cost = 0.0
|
|
120
|
+
model_name = ""
|
|
121
|
+
if not is_local:
|
|
122
|
+
if verbose:
|
|
123
|
+
console.print("Attempting cloud example generation...")
|
|
85
124
|
|
|
86
|
-
|
|
125
|
+
# Get JWT token BEFORE entering async context to avoid nested asyncio.run() calls
|
|
126
|
+
# (CloudConfig.get_jwt_token() uses asyncio.run internally for device flow auth)
|
|
127
|
+
jwt_token = CloudConfig.get_jwt_token(verbose=verbose)
|
|
128
|
+
if not jwt_token:
|
|
129
|
+
if not quiet:
|
|
130
|
+
console.print("[yellow]Cloud authentication failed. Falling back to local.[/yellow]")
|
|
131
|
+
is_local = True
|
|
132
|
+
else:
|
|
133
|
+
try:
|
|
134
|
+
generated_code, total_cost, model_name = asyncio.run(_run_cloud_generation(prompt_content, code_content, language, strength, temperature, verbose, pdd_env, jwt_token))
|
|
135
|
+
if generated_code:
|
|
136
|
+
if verbose:
|
|
137
|
+
console.print(Panel(f"Cloud generation successful. Model: {model_name}, Cost: ${total_cost:.6f}", title="[green]Cloud Success[/green]", expand=False))
|
|
138
|
+
except httpx.TimeoutException:
|
|
139
|
+
if not quiet:
|
|
140
|
+
console.print(f"[yellow]Cloud execution timed out ({CLOUD_TIMEOUT_SECONDS}s). Falling back to local.[/yellow]")
|
|
141
|
+
generated_code = None
|
|
142
|
+
except httpx.HTTPStatusError as e:
|
|
143
|
+
status_code = e.response.status_code
|
|
144
|
+
response_text = e.response.text or ""
|
|
145
|
+
err_content = response_text[:200] if response_text else "No response content"
|
|
146
|
+
if status_code == 402:
|
|
147
|
+
console.print(f"[red]Insufficient credits: {err_content}[/red]")
|
|
148
|
+
raise click.UsageError("Insufficient credits for cloud example generation")
|
|
149
|
+
elif status_code == 401:
|
|
150
|
+
console.print(f"[red]Authentication failed: {err_content}[/red]")
|
|
151
|
+
raise click.UsageError("Cloud authentication failed")
|
|
152
|
+
elif status_code == 403:
|
|
153
|
+
console.print(f"[red]Access denied: {err_content}[/red]")
|
|
154
|
+
raise click.UsageError("Access denied - user not approved")
|
|
155
|
+
else:
|
|
156
|
+
if not quiet:
|
|
157
|
+
console.print(f"[yellow]Cloud HTTP error ({status_code}): {err_content}. Falling back to local.[/yellow]")
|
|
158
|
+
generated_code = None
|
|
159
|
+
except Exception as e:
|
|
160
|
+
if verbose:
|
|
161
|
+
console.print(f"[yellow]Cloud error: {e}. Falling back to local.[/yellow]")
|
|
162
|
+
generated_code = None
|
|
87
163
|
|
|
164
|
+
if generated_code is None:
|
|
165
|
+
if not quiet:
|
|
166
|
+
console.print("[yellow]Cloud execution failed. Falling back to local.[/yellow]")
|
|
167
|
+
is_local = True
|
|
168
|
+
if is_local:
|
|
169
|
+
# Compute file path info if not already computed (when --local flag is used from start)
|
|
170
|
+
source_file_path = str(Path(code_file).resolve())
|
|
171
|
+
example_file_path = str(Path(resolved_output).resolve()) if resolved_output else ""
|
|
172
|
+
module_name = Path(code_file).stem
|
|
173
|
+
generated_code, total_cost, model_name = context_generator(code_module=code_content, prompt=prompt_content, language=language, strength=strength, temperature=temperature, verbose=not quiet, source_file_path=source_file_path, example_file_path=example_file_path, module_name=module_name, time=ctx.obj.get('time'))
|
|
174
|
+
if not generated_code:
|
|
175
|
+
raise click.UsageError("Example generation failed, no code produced.")
|
|
176
|
+
if language and language.lower() == "python":
|
|
177
|
+
generated_code = _validate_and_fix_python_syntax(generated_code, quiet)
|
|
178
|
+
if resolved_output:
|
|
179
|
+
out_path = Path(resolved_output)
|
|
180
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
181
|
+
out_path.write_text(generated_code, encoding="utf-8")
|
|
182
|
+
if not quiet:
|
|
183
|
+
console.print("[bold green]Example generation completed successfully.[/bold green]")
|
|
184
|
+
console.print(f"[bold]Model used:[/bold] {model_name}")
|
|
185
|
+
console.print(f"[bold]Total cost:[/bold] ${total_cost:.6f}")
|
|
186
|
+
return generated_code, total_cost, model_name
|
|
88
187
|
except Exception as e:
|
|
89
188
|
if not ctx.obj.get('quiet', False):
|
|
90
|
-
|
|
91
|
-
|
|
189
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}")
|
|
190
|
+
raise e
|
pdd/continue_generation.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from typing import Tuple
|
|
1
|
+
from typing import Tuple, Optional
|
|
2
|
+
import logging
|
|
2
3
|
from rich.console import Console
|
|
3
4
|
from rich.syntax import Syntax
|
|
4
5
|
from pydantic import BaseModel, Field
|
|
@@ -9,6 +10,10 @@ from .unfinished_prompt import unfinished_prompt
|
|
|
9
10
|
from . import EXTRACTION_STRENGTH, DEFAULT_TIME
|
|
10
11
|
|
|
11
12
|
console = Console()
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Maximum number of generation loops to prevent infinite loops
|
|
16
|
+
MAX_GENERATION_LOOPS = 20
|
|
12
17
|
|
|
13
18
|
class TrimResultsStartOutput(BaseModel):
|
|
14
19
|
explanation: str = Field(description="The explanation of how you determined what to cut out")
|
|
@@ -24,6 +29,7 @@ def continue_generation(
|
|
|
24
29
|
strength: float,
|
|
25
30
|
temperature: float,
|
|
26
31
|
time: float = DEFAULT_TIME,
|
|
32
|
+
language: Optional[str] = None,
|
|
27
33
|
verbose: bool = False
|
|
28
34
|
) -> Tuple[str, float, str]:
|
|
29
35
|
"""
|
|
@@ -78,16 +84,23 @@ def continue_generation(
|
|
|
78
84
|
temperature=0,
|
|
79
85
|
time=time,
|
|
80
86
|
output_pydantic=TrimResultsStartOutput,
|
|
81
|
-
verbose=verbose
|
|
87
|
+
verbose=verbose,
|
|
88
|
+
language=language,
|
|
82
89
|
)
|
|
83
90
|
total_cost += trim_start_response['cost']
|
|
84
91
|
code_block = trim_start_response['result'].code_block
|
|
85
92
|
|
|
86
93
|
# Step 4: Continue generation loop
|
|
87
|
-
while
|
|
94
|
+
while loop_count < MAX_GENERATION_LOOPS:
|
|
88
95
|
loop_count += 1
|
|
89
96
|
if verbose:
|
|
90
97
|
console.print(f"[cyan]Generation loop {loop_count}[/cyan]")
|
|
98
|
+
|
|
99
|
+
# Check for maximum loops reached
|
|
100
|
+
if loop_count >= MAX_GENERATION_LOOPS:
|
|
101
|
+
logger.warning(f"Reached maximum generation loops ({MAX_GENERATION_LOOPS}), terminating")
|
|
102
|
+
console.print(f"[yellow]Warning: Reached maximum generation loops ({MAX_GENERATION_LOOPS}), terminating[/yellow]")
|
|
103
|
+
break
|
|
91
104
|
|
|
92
105
|
# Generate continuation
|
|
93
106
|
continue_response = llm_invoke(
|
|
@@ -99,26 +112,55 @@ def continue_generation(
|
|
|
99
112
|
strength=strength,
|
|
100
113
|
temperature=temperature,
|
|
101
114
|
time=time,
|
|
102
|
-
verbose=verbose
|
|
115
|
+
verbose=verbose,
|
|
116
|
+
language=language,
|
|
103
117
|
)
|
|
104
118
|
|
|
105
119
|
total_cost += continue_response['cost']
|
|
106
120
|
model_name = continue_response['model_name']
|
|
107
121
|
continue_result = continue_response['result']
|
|
108
122
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
123
|
+
if verbose:
|
|
124
|
+
try:
|
|
125
|
+
preview = (continue_result[:160] + '...') if isinstance(continue_result, str) and len(continue_result) > 160 else continue_result
|
|
126
|
+
except Exception:
|
|
127
|
+
preview = "<non-str>"
|
|
128
|
+
console.print(f"[blue]Continue model:[/blue] {model_name}")
|
|
129
|
+
console.print(f"[blue]Continue preview:[/blue] {preview!r}")
|
|
130
|
+
|
|
131
|
+
# If the model produced no continuation, avoid an endless loop
|
|
132
|
+
if not isinstance(continue_result, str) or not continue_result.strip():
|
|
133
|
+
logger.warning("Empty continuation received; stopping to avoid loop.")
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
# Build prospective new block and check completeness on the updated tail
|
|
137
|
+
new_code_block = code_block + continue_result
|
|
138
|
+
last_chunk = new_code_block[-600:] if len(new_code_block) > 600 else new_code_block
|
|
139
|
+
reasoning, is_finished, check_cost, check_model = unfinished_prompt(
|
|
112
140
|
prompt_text=last_chunk,
|
|
113
141
|
strength=0.5,
|
|
114
142
|
temperature=0,
|
|
115
143
|
time=time,
|
|
144
|
+
language=language,
|
|
116
145
|
verbose=verbose
|
|
117
146
|
)
|
|
118
147
|
total_cost += check_cost
|
|
119
148
|
|
|
149
|
+
if verbose:
|
|
150
|
+
console.print(f"[magenta]Tail length:[/magenta] {len(last_chunk)}")
|
|
151
|
+
# Show a safe, shortened representation of the tail
|
|
152
|
+
try:
|
|
153
|
+
tail_preview = (last_chunk[-200:] if len(last_chunk) > 200 else last_chunk)
|
|
154
|
+
except Exception:
|
|
155
|
+
tail_preview = "<unprintable tail>"
|
|
156
|
+
console.print(f"[magenta]Tail preview (last 200 chars):[/magenta]\n{tail_preview}")
|
|
157
|
+
console.print(f"[magenta]Unfinished check model:[/magenta] {check_model}")
|
|
158
|
+
console.print(f"[magenta]is_finished:[/magenta] {is_finished}")
|
|
159
|
+
console.print(f"[magenta]Reasoning:[/magenta] {reasoning}")
|
|
160
|
+
|
|
120
161
|
if not is_finished:
|
|
121
|
-
code_block
|
|
162
|
+
code_block = new_code_block
|
|
163
|
+
# Continue to next iteration
|
|
122
164
|
else:
|
|
123
165
|
# Trim and append final continuation
|
|
124
166
|
trim_response = llm_invoke(
|
|
@@ -131,7 +173,8 @@ def continue_generation(
|
|
|
131
173
|
temperature=0,
|
|
132
174
|
time=time,
|
|
133
175
|
output_pydantic=TrimResultsOutput,
|
|
134
|
-
verbose=verbose
|
|
176
|
+
verbose=verbose,
|
|
177
|
+
language=language,
|
|
135
178
|
)
|
|
136
179
|
total_cost += trim_response['cost']
|
|
137
180
|
code_block += trim_response['result'].trimmed_continued_generation
|
|
@@ -146,4 +189,4 @@ def continue_generation(
|
|
|
146
189
|
|
|
147
190
|
except Exception as e:
|
|
148
191
|
console.print(f"[bold red]Error in continue_generation: {str(e)}[/bold red]")
|
|
149
|
-
raise
|
|
192
|
+
raise
|
pdd/core/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core utilities and configuration for PDD CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .cloud import (
|
|
6
|
+
CloudConfig,
|
|
7
|
+
AuthError,
|
|
8
|
+
NetworkError,
|
|
9
|
+
TokenError,
|
|
10
|
+
UserCancelledError,
|
|
11
|
+
RateLimitError,
|
|
12
|
+
FIREBASE_API_KEY_ENV,
|
|
13
|
+
GITHUB_CLIENT_ID_ENV,
|
|
14
|
+
PDD_CLOUD_URL_ENV,
|
|
15
|
+
PDD_JWT_TOKEN_ENV,
|
|
16
|
+
DEFAULT_BASE_URL,
|
|
17
|
+
CLOUD_ENDPOINTS,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
'CloudConfig',
|
|
22
|
+
'AuthError',
|
|
23
|
+
'NetworkError',
|
|
24
|
+
'TokenError',
|
|
25
|
+
'UserCancelledError',
|
|
26
|
+
'RateLimitError',
|
|
27
|
+
'FIREBASE_API_KEY_ENV',
|
|
28
|
+
'GITHUB_CLIENT_ID_ENV',
|
|
29
|
+
'PDD_CLOUD_URL_ENV',
|
|
30
|
+
'PDD_JWT_TOKEN_ENV',
|
|
31
|
+
'DEFAULT_BASE_URL',
|
|
32
|
+
'CLOUD_ENDPOINTS',
|
|
33
|
+
]
|