Merge pull request #207 from Pythagora-io/feature-902-903-main

Feature 902 903 main
This commit is contained in:
LeonOstrez
2025-06-14 12:32:45 +01:00
committed by GitHub
9 changed files with 418 additions and 51 deletions

View File

@@ -1,3 +1,7 @@
import asyncio
import json
import os
import sys
from urllib.parse import urljoin
import httpx
@@ -8,7 +12,7 @@ from core.agents.git import GitMixin
from core.agents.mixins import FileDiffMixin
from core.agents.response import AgentResponse
from core.cli.helpers import capture_exception
from core.config import FRONTEND_AGENT_NAME, SWAGGER_EMBEDDINGS_API
from core.config import FRONTEND_AGENT_NAME, IMPLEMENT_CHANGES_AGENT_NAME, SWAGGER_EMBEDDINGS_API
from core.config.actions import (
FE_CHANGE_REQ,
FE_CONTINUE,
@@ -17,7 +21,8 @@ from core.config.actions import (
FE_ITERATION_DONE,
FE_START,
)
from core.llm.parser import DescriptiveCodeBlockParser
from core.llm.convo import Convo
from core.llm.parser import DescriptiveCodeBlockParser, OptionalCodeBlockParser
from core.log import get_logger
from core.telemetry import telemetry
from core.ui.base import ProjectStage
@@ -25,12 +30,19 @@ from core.ui.base import ProjectStage
log = get_logger(__name__)
def has_correct_num_of_backticks(response: str) -> bool:
"""
Checks if the response has the correct number of backticks.
"""
return response.count("```") % 2 == 0 and response.count("```") > 0
class Frontend(FileDiffMixin, GitMixin, BaseAgent):
agent_type = "frontend"
display_name = "Frontend"
async def run(self) -> AgentResponse:
if not self.current_state.epics[0]["messages"]:
if not self.current_state.epics[-1]["messages"]:
finished = await self.start_frontend()
elif self.next_state.epics[-1].get("file_paths_to_remove_mock"):
finished = await self.remove_mock()
@@ -61,7 +73,7 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
else self.current_state.specification.template_summary,
description=self.state_manager.template["description"]
if self.state_manager.template is not None
else self.next_state.epics[0]["description"],
else self.next_state.epics[-1]["description"],
user_feedback=None,
first_time_build=True,
)
@@ -95,7 +107,7 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
llm = self.get_llm(FRONTEND_AGENT_NAME)
convo = AgentConvo(self)
convo.messages = self.current_state.epics[0]["messages"]
convo.messages = self.current_state.epics[-1]["messages"]
convo.user(
"Ok, now think carefully about your previous response. If the response ends by mentioning something about continuing with the implementation, continue but don't implement any files that have already been implemented. If your last response doesn't end by mentioning continuing, respond only with `DONE` and with nothing else."
)
@@ -104,12 +116,21 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
response_blocks = response.blocks
convo.assistant(response.original_response)
await self.process_response(response_blocks)
use_relace = self.current_state.epics[-1].get("use_relace", False)
await self.process_response(response_blocks, relace=use_relace)
if self.next_state.epics[-1].get("manual_iteration", False):
self.next_state.epics[-1]["fe_iteration_done"] = (
has_correct_num_of_backticks(response.original_response)
or self.current_state.epics[-1].get("retry_count", 0) >= 2
)
self.next_state.epics[-1]["retry_count"] = self.current_state.epics[-1].get("retry_count", 0) + 1
else:
self.next_state.epics[-1]["fe_iteration_done"] = (
"done" in response.original_response[-20:].lower().strip() or len(convo.messages) > 15
)
self.next_state.epics[-1]["messages"] = convo.messages
self.next_state.epics[-1]["fe_iteration_done"] = (
"done" in response.original_response[-20:].lower().strip() or len(convo.messages) > 15
)
self.next_state.flag_epics_as_modified()
return False
@@ -120,6 +141,10 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
:return: True if the frontend is fully built, False otherwise.
"""
self.next_state.epics[-1]["auto_debug_attempts"] = 0
self.next_state.epics[-1]["retry_count"] = 0
user_input = await self.try_auto_debug()
frontend_only = self.current_state.branch.project.project_type == "swagger"
self.next_state.action = FE_ITERATION
# update the pages in the knowledge base
@@ -127,31 +152,33 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
await self.ui.send_project_stage({"stage": ProjectStage.ITERATE_FRONTEND})
answer = await self.ask_question(
"Do you want to change anything or report a bug?" if frontend_only else FE_CHANGE_REQ,
buttons={"yes": "I'm done building the UI"} if not frontend_only else None,
default="yes",
extra_info="restart_app/collect_logs",
placeholder='For example, "I don\'t see anything when I open http://localhost:5173/" or "Nothing happens when I click on the NEW PROJECT button"',
)
if answer.button == "yes":
if user_input:
await self.send_message("Errors detected, fixing...")
else:
answer = await self.ask_question(
FE_DONE_WITH_UI,
buttons={
"yes": "Yes, let's build the backend",
"no": "No, continue working on the UI",
},
buttons_only=True,
"Do you want to change anything or report a bug?" if frontend_only else FE_CHANGE_REQ,
buttons={"yes": "I'm done building the UI"} if not frontend_only else None,
default="yes",
extra_info="restart_app/collect_logs",
placeholder='For example, "I don\'t see anything when I open http://localhost:5173/" or "Nothing happens when I click on the NEW PROJECT button"',
)
if answer.button == "yes":
return True
else:
return False
answer = await self.ask_question(
FE_DONE_WITH_UI,
buttons={
"yes": "Yes, let's build the backend",
"no": "No, continue working on the UI",
},
buttons_only=True,
default="yes",
)
await self.send_message("Implementing the changes you suggested...")
return answer.button == "yes"
if answer.text:
user_input = answer.text
await self.send_message("Implementing the changes you suggested...")
llm = self.get_llm(FRONTEND_AGENT_NAME)
@@ -160,7 +187,7 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
if frontend_only:
convo = AgentConvo(self).template(
"is_relevant_for_docs_search",
user_feedback=answer.text,
user_feedback=user_input,
)
response = await llm(convo)
@@ -172,7 +199,7 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
async with httpx.AsyncClient(transport=httpx.AsyncHTTPTransport()) as client:
resp = await client.post(
url,
json={"text": answer.text, "project_id": str(self.state_manager.project.id)},
json={"text": user_input, "project_id": str(self.state_manager.project.id)},
headers={"Authorization": f"Bearer {self.state_manager.get_access_token()}"},
)
@@ -202,17 +229,43 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
llm = self.get_llm(FRONTEND_AGENT_NAME, stream_output=True)
# try relace first
convo = AgentConvo(self).template(
"build_frontend",
description=self.current_state.epics[0]["description"],
user_feedback=answer.text,
"iterate_frontend",
description=self.current_state.epics[-1]["description"],
user_feedback=user_input,
relevant_api_documentation=relevant_api_documentation,
first_time_build=False,
)
# replace system prompt because of relace
convo.messages[0]["content"] = AgentConvo(self).render("system_relace")
response = await llm(convo, parser=DescriptiveCodeBlockParser())
await self.process_response(response.blocks)
relace_finished = await self.process_response(response.blocks, relace=True)
if not relace_finished:
log.debug("Relace didn't finish, reverting to build_frontend")
convo = AgentConvo(self).template(
"build_frontend",
description=self.current_state.epics[-1]["description"],
user_feedback=user_input,
relevant_api_documentation=relevant_api_documentation,
first_time_build=False,
)
response = await llm(convo, parser=DescriptiveCodeBlockParser())
await self.process_response(response.blocks)
convo.assistant(response.original_response)
self.next_state.epics[-1]["messages"] = convo.messages
self.next_state.epics[-1]["use_relace"] = relace_finished
self.next_state.epics[-1]["fe_iteration_done"] = has_correct_num_of_backticks(response.original_response)
self.next_state.epics[-1]["manual_iteration"] = True
self.next_state.flag_epics_as_modified()
return False
@@ -231,8 +284,8 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
await telemetry.trace_code_event(
"frontend-finished",
{
"description": self.current_state.epics[0]["description"],
"messages": self.current_state.epics[0]["messages"],
"description": self.current_state.epics[-1]["description"],
"messages": self.current_state.epics[-1]["messages"],
},
)
@@ -252,7 +305,7 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
return AgentResponse.done(self)
async def process_response(self, response_blocks: list, removed_mock: bool = False) -> list[str]:
async def process_response(self, response_blocks: list, removed_mock: bool = False, relace: bool = False) -> bool:
"""
Processes the response blocks from the LLM.
@@ -276,6 +329,21 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
continue
new_content = content
old_content = self.current_state.get_file_content_by_path(file_path)
if relace:
llm = self.get_llm(IMPLEMENT_CHANGES_AGENT_NAME)
convo = Convo().user(
{
"initialCode": old_content,
"editSnippet": new_content,
}
)
new_content = await llm(convo, temperature=0, parser=OptionalCodeBlockParser())
if not new_content or new_content == ("", 0, 0):
return False
n_new_lines, n_del_lines = self.get_line_changes(old_content, new_content)
await self.ui.send_file_status(file_path, "done", source=self.ui_source)
await self.ui.generate_diff(
@@ -306,12 +374,31 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
cmd_part = cmd_part.strip().replace("client/", "")
command = f"{prefix} && {cmd_part}"
# check if cmd_part contains npm run something, if that something is not in scripts, then skip it
if "npm run" in cmd_part:
npm_script = cmd_part.split("npm run")[1].strip()
absolute_path = os.path.join(
self.state_manager.get_full_project_root(),
os.path.join(
"client" if "client" in prefix else "server" if "server" in prefix else "",
"package.json",
),
)
with open(absolute_path, "r") as file:
package_json = json.load(file)
if npm_script not in package_json.get("scripts", {}):
log.warning(
f"Skipping command: {command} as npm script {npm_script} not found, command is {command}"
)
continue
await self.send_message(f"Running command: `{command}`...")
await self.process_manager.run_command(command)
else:
log.info(f"Unknown block description: {description}")
return AgentResponse.done(self)
return True
async def remove_mock(self):
"""
@@ -393,3 +480,65 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
# self.next_state.app_link = app_link
await self.ui.send_run_command(command)
await self.ui.send_app_link(app_link)
async def kill_app(self):
is_win = sys.platform.lower().startswith("win")
# TODO make ports configurable
# kill frontend - both swagger and node
if is_win:
await self.process_manager.run_command(
"""for /f "tokens=5" %a in ('netstat -ano ^| findstr :5173 ^| findstr LISTENING') do taskkill /F /PID %a""",
show_output=False,
)
else:
await self.process_manager.run_command("lsof -ti:5173 | xargs -r kill", show_output=False)
# if node project, kill backend as well
if self.state_manager.project.project_type == "node":
if is_win:
await self.process_manager.run_command(
"""for /f "tokens=5" %a in ('netstat -ano ^| findstr :3000 ^| findstr LISTENING') do taskkill /F /PID %a""",
show_output=False,
)
else:
await self.process_manager.run_command("lsof -ti:3000 | xargs -r kill", show_output=False)
async def try_auto_debug(self) -> str:
count = 3
if self.next_state.epics[-1].get("auto_debug_attempts", 0) >= 3:
return ""
try:
self.next_state.epics[-1]["auto_debug_attempts"] = (
self.current_state.epics[-1].get("auto_debug_attempts", 0) + 1
)
# kill app
await self.kill_app()
npm_proc = await self.process_manager.start_process("npm run start &", show_output=False)
while True:
if count == 3:
await asyncio.sleep(5)
else:
await asyncio.sleep(2)
diff_stdout, diff_stderr = await npm_proc.read_output()
if (diff_stdout == "" and diff_stderr == "") or count <= 0:
break
count -= 1
await self.process_manager.run_command("curl http://localhost:5173", show_output=False)
await asyncio.sleep(1)
diff_stdout, diff_stderr = await npm_proc.read_output()
# kill app again
await self.kill_app()
if diff_stdout or diff_stderr:
log.debug(f"Auto-debugging output:\n{diff_stdout}\n{diff_stderr}")
return f"I got an error. Here are the logs:\n{diff_stdout}\n{diff_stderr}"
except Exception as e:
capture_exception(e)
log.error(f"Error during auto-debugging: {e}", exc_info=True)
return ""

View File

@@ -243,6 +243,7 @@ class DBConfig(_StrictModel):
description="Database connection URL",
)
debug_sql: bool = Field(False, description="Log all SQL queries to the console")
save_llm_requests: bool = Field(False, description="Save LLM requests to db")
@field_validator("url")
@classmethod

View File

@@ -31,7 +31,7 @@ class SessionManager:
self.SessionClass = async_sessionmaker(self.engine, expire_on_commit=False)
self.session = None
self.recursion_depth = 0
self.save_llm_requests = config.save_llm_requests
event.listen(self.engine.sync_engine, "connect", self._on_connect)
def _on_connect(self, dbapi_connection, _):

View File

@@ -17,7 +17,7 @@ class RelaceClient(BaseLLMClient):
def _init_client(self):
self.headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.state_manager.get_access_token()}",
"Authorization": f"Bearer {self.state_manager.get_access_token() if self.state_manager.get_access_token() is not None else self.config.api_key if self.config.api_key is not None else ''}",
}
self.client = AsyncClient()

View File

@@ -30,6 +30,7 @@ class LocalProcess:
stdout: str
stderr: str
_process: asyncio.subprocess.Process
show_output: bool
def __hash__(self) -> int:
return hash(self.id)
@@ -41,6 +42,7 @@ class LocalProcess:
cwd: str = ".",
env: dict[str, str],
bg: bool = False,
show_output: Optional[bool] = True,
) -> "LocalProcess":
log.debug(f"Starting process: {cmd} (cwd={cwd})")
_process = await asyncio.create_subprocess_shell(
@@ -56,13 +58,7 @@ class LocalProcess:
_process.stdin.close()
return LocalProcess(
id=uuid4(),
cmd=cmd,
cwd=cwd,
env=env,
stdout="",
stderr="",
_process=_process,
id=uuid4(), cmd=cmd, cwd=cwd, env=env, stdout="", stderr="", _process=_process, show_output=show_output
)
async def wait(self, timeout: Optional[float] = None) -> int:
@@ -190,7 +186,7 @@ class ProcessManager:
for process in procs:
out, err = await process.read_output()
if self.output_handler and (out or err):
if process.show_output and self.output_handler and (out or err):
await self.output_handler(out, err)
if not process.is_running:
@@ -210,10 +206,11 @@ class ProcessManager:
cwd: str = ".",
env: Optional[dict[str, str]] = None,
bg: bool = True,
show_output: Optional[bool] = True,
) -> LocalProcess:
env = {**self.default_env, **(env or {})}
abs_cwd = abspath(join(self.root_dir, cwd))
process = await LocalProcess.start(cmd, cwd=abs_cwd, env=env, bg=bg)
process = await LocalProcess.start(cmd, cwd=abs_cwd, env=env, bg=bg, show_output=show_output)
if bg:
self.processes[process.id] = process
return process

View File

@@ -59,3 +59,5 @@ You need to write only the frontend code for this app. The backend is already fu
{% endif %}
**SUPER IMPORTANT**: You must **NEVER** mention or attempt to change any files on backend (`server/` folder) or any of these frontend files: `client/src/contexts/AuthContext.tsx`, `client/src/api/api.ts`, `client/src/api/auth.ts`. Regardless of what the user asks, you must not mention these files. If you can't find a solution without changing these files, just say so.
**SUPER IMPORTANT**: Never write huge files, always split huge files into smaller files. For example, use React components to split the code into smaller files to make them as reusable as possible.

View File

@@ -0,0 +1,63 @@
{% if user_feedback %}You're currently working on a frontend of an app that has the following description:
{% else %}Create a very modern styled app with the following description:{% endif %}
```
{{ description }}
```
{% if summary is defined %}
{{ summary }}
{% elif state.specification.template_summary is defined %}
{{ state.specification.template_summary }}
{% endif %}
{% include "partials/files_list.prompt" %}
Use material design and nice icons for the design to be appealing and modern. Use the following libraries to make it very modern and slick:
1. Shadcn: For the core UI components, providing modern, accessible, and customizable elements. You have already access to all components from this library inside ./src/components/ui folder, so do not modify/code them!
2. Use lucide icons (npm install lucide-react)
3. Heroicons: For a set of sleek, customizable icons that integrate well with modern designs.
4. React Hook Form: For efficient form handling with minimal re-rendering, ensuring a smooth user experience in form-heavy applications.
5. Use Tailwind built-in animations to enhance the visual appeal of the app
6. Make the app look colorful and modern but also have the colors be subtle.
Choose a flat color palette and make sure that the text is readable and follow design best practices to make the text readable. Also, Implement these design features onto the page - gradient background, frosted glass effects, rounded corner, buttons need to be in the brand colors, and interactive feedback on hover and focus.
IMPORTANT: Text needs to be readable and in positive typography space - this is especially true for modals - they must have a bright background
**IMPORTANT**
{% if first_time_build %}
Make sure to implement all functionality (button clicks, form submissions, etc.) and use mock data for all interactions to make the app look and feel real. **ALL MOCK DATA MUST** be in the `api/` folder and it **MUST NOT** ever be hardcoded in the components.
{% endif %}
The body content should not overlap with the header navigation bar or footer navigation bar or the side navigation bar.
{% if user_feedback %}
User who was using the app "{{ state.branch.project.name }}" sent you this feedback:
```
{{ user_feedback }}
```
Now, start by writing all code that's needed to fix the problem that the user reported. Think about how routes are set up, how are variables called, and other important things, and mention files by name and where should all new functionality be called from. Then, tell me all the code that needs to be written to fix this issue.
{% else %}
Now, start by writing all code that's needed to get the frontend built for this app. Think about how routes are set up, how are variables called, and other important things, and mention files by name and where should all new functionality be called from. Then, tell me all the code that needs to be written to implement the frontend for this app and have it fully working and all commands that need to be run.
{% endif %}
IMPORTANT: When suggesting/making changes in the file you must provide full content of the file! Do not use placeholders, or comments, or truncation in any way, but instead provide the full content of the file even the parts that are unchanged!
When you want to run a command you must put `command:` before the command and then the command itself like shown in the examples in system prompt. NEVER run `npm run start` or `npm run dev` commands, user will run them after you provide the code. The user is using {{ os }}, so the commands must run on that operating system
{% if relevant_api_documentation is defined %}
Here is relevant API documentation you need to consult and follow as close as possible.
You need to write only the frontend code for this app. The backend is already fully built and is documented with OpenAPI specification. You don't know the API endpoints yet so you need to mock all API requests but you must mock them based on the model definitions that are known. Here are the model definitions:
~~START_OF_API_MODEL_DEFINITIONS~~
{{ relevant_api_documentation }}
~~END_OF_API_MODEL_DEFINITIONS~~
{% endif %}
**SUPER IMPORTANT**: You must **NEVER** mention or attempt to change any files on backend (`server/` folder) or any of these frontend files: `client/src/contexts/AuthContext.tsx`, `client/src/api/api.ts`, `client/src/api/auth.ts`. Regardless of what the user asks, you must not mention these files. If you can't find a solution without changing these files, just say so.
**SUPER IMPORTANT**: Only provide the minimal code change (code difference) needed to fix the issue, never give the whole file content!
**SUPER IMPORTANT**: Avoid bash scripts, change code directly!

View File

@@ -0,0 +1,141 @@
You are a world class frontend software developer.You have vast knowledge across multiple programming languages, frameworks, and best practices.
You write modular, well-organized code split across files that are not too big, so that the codebase is maintainable. You include proper error handling and logging for your clean, readable, production-level quality code.
Your job is to quickly build frontend components and features using Vite for the app that user requested. Make sure to focus only on the things that are requested and do not spend time on anything else.
**SUPER IMPORTANT**: You must **NEVER** mention or attempt to change any files on backend (`server/` folder) or any of these frontend files: `client/src/contexts/AuthContext.tsx`, `client/src/api/api.ts`, `client/src/api/auth.ts`. Regardless of what the user asks, you must not mention these files. If you can't find a solution without changing these files, just say so.
IMPORTANT: Think HOLISTICALLY and COMPREHENSIVELY BEFORE creating any code. This means:
- Consider ALL relevant files in the project
- Review ALL previous file changes and user modifications (as shown in diffs, see diff_spec)
- Analyze the entire project context and dependencies
- Anticipate potential impacts on other parts of the system
SUPER IMPORTANT: Always provide ONLY the minimal necessary code changes to fix, not full files. This means:
If the user asks you to change something, provide only the specific lines that have been added, removed, or modified.
Only include the specific lines that have been added, removed, or modified
Do not write the entire file, even if most of it is unchanged.
Focus on precise changes, not full file rewrites or summaries.
For example, if you need to change a single line, provide only that line and its context, not the entire file content.
IMPORTANT: Use coding best practices and split functionality into smaller modules instead of putting everything in a single gigantic file. Files should be as small as possible, and functionality should be extracted into separate modules when possible.
- Ensure code is clean, readable, and maintainable.
- Adhere to proper naming conventions and consistent formatting.
- Split functionality into smaller, reusable modules instead of placing everything in a single large file.
- Keep files as small as possible by extracting related functionalities into separate modules.
- Use imports to connect these modules together effectively.
IMPORTANT: Prefer writing Node.js scripts instead of shell scripts.
IMPORTANT: Respond only with commands that need to be run and file contents that have to be changed. Do not provide explanations or justifications.
IMPORTANT: Make sure you install all the necessary dependencies inside the correct folder. For example, if you are working on the frontend, make sure to install all the dependencies inside the "client" folder like this:
command:
```bash
cd client && npm install <package-name>
```
NEVER run `npm run start` or `npm run dev` commands, user will run them after you provide the code.
IMPORTANT: The order of the actions is very important. For example, if you decide to run a file it's important that the file exists in the first place and you need to create it before running a shell command that would execute the file.
IMPORTANT: Put full path of file you are editing! Mostly you will work with files inside "client/" folder so don't forget to put it in file path, for example DO `client/src/App.tsx` instead of `src/App.tsx`.
{% include "partials/file_naming.prompt" %}
Here are the examples:
---start_of_examples---
------------------------example_1---------------------------
Prompt:
Enlarge the login button.
Your response:
command:
```
file: App.tsx
```tsx
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-12 px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword(!showPassword)}
disabled={isLoading}
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-gray-500" />
) : (
<Eye className="h-4 w-4 text-gray-500" />
)}
</Button>
```
------------------------example_1_end---------------------------
------------------------example_2---------------------------
Prompt:
Create a new file called `components/MyComponent.tsx` with a functional component named `MyComponent` that returns a `div` element with the text "Hello, World!".
Your response:
command:
```bash
npm init -y
npm install <package-name>
```
file: App.tsx
```tsx
import React from 'react';
export const MyComponent: React.FC = () => {
return <div>Hello, World!</div>;
};
```
------------------------example_2_end---------------------------
------------------------example_3---------------------------
Prompt:
Create snake game.
Your response:
command:
```bash
cd client && npm install shadcn/ui
node scripts/createInitialLeaderboard.js
```
file: client/components/Snake.tsx
```tsx
import React from 'react';
...
```
file: client/components/Food.tsx
```tsx
...
```
file: client/components/Score.tsx
```tsx
...
```
file: client/components/GameOver.tsx
```tsx
...
```
------------------------example_3_end---------------------------
------------------------example_4---------------------------
Prompt:
Create a script that counts to 10.
Your response:
file: countToTen.js
```js
for (let i = 1; i <= 10; i++) {
console.log(i);
}
```
command:
```bash
node countToTen.js
```
------------------------example_4_end---------------------------
---end_of_examples---

View File

@@ -54,6 +54,7 @@ class StateManager:
self.git_available = False
self.git_used = False
self.auto_confirm_breakdown = True
self.save_llm_requests = False
self.options = {}
self.access_token = None
self.async_tasks = None
@@ -346,8 +347,21 @@ class StateManager:
:param request_log: The request log to log.
"""
# removed logging of LLM requests
pass
# Always record telemetry regardless of save_llm_requests setting
try:
telemetry.record_llm_request(
request_log.prompt_tokens + request_log.completion_tokens,
request_log.duration,
request_log.status != LLMRequestStatus.SUCCESS,
)
except Exception as e:
if self.ui:
log.error(f"An error occurred recording telemetry: {e}")
# Only save to database if configured to do so
if not self.session_manager.save_llm_requests:
return
async with self.db_blocker():
try:
telemetry.record_llm_request(