Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/praisonai/praisonai/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,48 @@ def main_callback(

# If no command provided, start interactive mode
if ctx.invoked_subcommand is None:
# Check for credentials before starting TUI
from ..llm.credentials import is_configured
import sys

if not is_configured(): # Check for any configured credentials
# In non-interactive mode, just show error
if not sys.stdin.isatty() or quiet:
typer.echo(
"Error: No API key configured. Run: praisonai setup",
err=True
)
raise typer.Exit(1)

# In interactive mode, offer to run setup
typer.echo("No API key configured.")
run_setup = typer.confirm("Would you like to run the setup wizard now?")

if run_setup:
# Import and run setup
from .commands.setup import _run_setup
exit_code = _run_setup(
non_interactive=False,
provider=None,
api_key=None,
model=None
)
if exit_code != 0:
typer.echo("Setup failed. Exiting.", err=True)
raise typer.Exit(exit_code)

# Re-check credentials after setup
if not is_configured():
typer.echo("Setup completed but credentials still not detected.", err=True)
raise typer.Exit(1)

# After successful setup, continue to TUI
typer.echo("\nSetup complete! Starting interactive mode...\n")
Comment on lines +668 to +678

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing credential re-check after setup completion.

Unlike the parallel implementation in run.py (lines 137-140), this code path does not re-validate credentials after the setup wizard completes. If the user configures a provider other than OpenAI (which the hardcoded gpt-4o-mini check requires), the TUI will launch and fail at the first LLM call.

Proposed fix to add post-setup verification
                 if exit_code != 0:
                     typer.echo("Setup failed. Exiting.", err=True)
                     raise typer.Exit(exit_code)
                 
-                # After successful setup, continue to TUI
-                typer.echo("\nSetup complete! Starting interactive mode...\n")
+                # Re-check credentials after setup
+                if not is_configured(model="gpt-4o-mini"):
+                    typer.echo("Setup completed but credentials for gpt-4o-mini not detected.", err=True)
+                    raise typer.Exit(1)
+                
+                typer.echo("\nSetup complete! Starting interactive mode...\n")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if exit_code != 0:
typer.echo("Setup failed. Exiting.", err=True)
raise typer.Exit(exit_code)
# After successful setup, continue to TUI
typer.echo("\nSetup complete! Starting interactive mode...\n")
if exit_code != 0:
typer.echo("Setup failed. Exiting.", err=True)
raise typer.Exit(exit_code)
# Re-check credentials after setup
if not is_configured(model="gpt-4o-mini"):
typer.echo("Setup completed but credentials for gpt-4o-mini not detected.", err=True)
raise typer.Exit(1)
typer.echo("\nSetup complete! Starting interactive mode...\n")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai/praisonai/cli/app.py` around lines 668 - 673, After the setup
wizard completes successfully (when exit_code equals 0), add credential
re-validation before continuing to the TUI launch, similar to the implementation
in run.py lines 137-140. This validation should verify that the credentials
configured by the user (including non-OpenAI providers) are functional before
the interactive mode starts. Insert this credential check between the successful
setup confirmation and the echo message that says "Setup complete! Starting
interactive mode...", ensuring the TUI doesn't launch with invalid or
misconfigured credentials.

else:
typer.echo("\nTo configure credentials later, run: praisonai setup")
typer.echo("or set environment variables like OPENAI_API_KEY")
raise typer.Exit(0)

from praisonai.cli.interactive.async_tui import AsyncTUI, AsyncTUIConfig

tui_config = AsyncTUIConfig(
Expand Down
166 changes: 104 additions & 62 deletions src/praisonai/praisonai/cli/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,50 +63,6 @@ def _parse_permissions(allow: Optional[List[str]], deny: Optional[List[str]], pe

return config if config else None


def _check_api_key_available() -> bool:
"""
Check if an API key is available from environment or stored credentials.

Also injects stored credentials into environment if no env key is present.

Returns:
True if an API key is available, False otherwise
"""
import os

# Check all known provider env vars first
known_keys = (
"OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
"GEMINI_API_KEY", "GROQ_API_KEY", "COHERE_API_KEY",
)
if any(os.environ.get(k) for k in known_keys):
return True

# Try to inject stored credentials into env, then re-check any known provider key
try:
from ...llm.credentials import inject_credentials_into_env
inject_credentials_into_env()
except ImportError:
# Fallback if credential module not available
pass

# Check all known provider env vars after potential injection
if any(os.environ.get(k) for k in known_keys):
return True

# Final check using LLM resolution with credential fallback
try:
from ...llm.credentials import resolve_llm_endpoint_with_credentials
endpoint = resolve_llm_endpoint_with_credentials()
return bool(endpoint.api_key)
except ImportError:
# Fallback to basic env check
return bool(os.environ.get("OPENAI_API_KEY"))
except Exception:
return False


@app.callback(invoke_without_command=True)
def run_main(
ctx: typer.Context,
Expand Down Expand Up @@ -156,6 +112,50 @@ def run_main(
output = get_output_controller()
_ = get_current_context() # Initialize context

# Early credential check before any processing
if target: # Only check if we actually have something to run
from ...llm.credentials import is_configured
import sys

# Check if credentials are configured (use model if provided, else check general)
if not is_configured(model):
# In non-interactive mode, show clear error
if not sys.stdin.isatty() or output.is_json_mode:
output.print_error(
"No API key configured. Run: praisonai setup\n"
"or set environment variables like OPENAI_API_KEY"
)
raise typer.Exit(1)

# In interactive mode, offer to run setup
typer.echo(f"No API key configured{f' for model {model}' if model else ''}.")
run_setup = typer.confirm("Would you like to run the setup wizard now?")

if run_setup:
from ..commands.setup import _run_setup
exit_code = _run_setup(
non_interactive=False,
provider=None,
api_key=None,
model=None
)
if exit_code != 0:
output.print_error("Setup failed. Exiting.")
raise typer.Exit(exit_code)

output.print_success("Setup complete! Continuing with your run...")
# Re-check after setup
if not is_configured(model):
output.print_error("Setup completed but credentials still not detected.")
raise typer.Exit(1)
Comment on lines +121 to +150

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Model-specific is_configured triggers false "not configured" for cross-provider users

When a user passes --model gpt-4o-mini but has only Anthropic credentials stored (and no OPENAI_API_KEY env var), is_configured("gpt-4o-mini") returns False because the GPT-prefix branch checks "openai" in providers and finds nothing. This user is fully configured — just not for that specific model — but they get dropped into the onboarding flow as if they were a fresh install.

The failure scenario is even worse on the re-check at line 96: if the user runs setup and stores an Anthropic credential again (or any non-OpenAI provider), is_configured("gpt-4o-mini") still returns False, and they receive "Setup completed but credentials still not detected" with an Exit(1) — a deeply confusing dead-end for a user who has working credentials.

app.py already fixed the equivalent issue by calling is_configured() with no argument. The same fix is needed here: drop the model argument from the check so it gates only on "any credential configured," which is the intent of fresh-install detection. Model/provider compatibility is a separate validation concern that belongs closer to the LLM call.

else:
output.print_info(
"To configure credentials:\n"
" - Run: praisonai setup\n"
" - Or set environment variables like OPENAI_API_KEY"
)
raise typer.Exit(0)

# Resolve configuration if model not explicitly provided
if model is None:
try:
Expand Down Expand Up @@ -374,12 +374,7 @@ def _run_from_file(
"""Run agents from a YAML file."""
output = get_output_controller()

# Preflight check for API key availability
if not _check_api_key_available():
output.print_error(
"No API key configured. Run: praisonai auth login"
)
raise typer.Exit(1)
# Note: Credential check already done in run_main() entry point

try:
# Use existing PraisonAI class
Expand Down Expand Up @@ -489,12 +484,7 @@ def _run_prompt(
"""Run a direct prompt."""
output = get_output_controller()

# Preflight check for API key availability
if not _check_api_key_available():
output.print_error(
"No API key configured. Run: praisonai auth login"
)
raise typer.Exit(1)
# Note: Credential check already done in run_main() entry point

try:
# Handle session continuity first (before any execution mode)
Expand Down Expand Up @@ -699,9 +689,35 @@ def _run_from_file_profiled(
praison.config_list[0]['model'] = model

# Apply session continuity if requested
session_id, auto_save_name = resolve_session_params(
continue_session, session, fork, no_save
)
session_id = None
auto_save_name = None

if continue_session or session or fork:
from ..state.project_sessions import get_project_session_store, find_last_session

if continue_session:
session_id = find_last_session()
if not session_id:
typer.echo("Warning: No previous sessions found. Starting new session.", err=True)
elif session:
project_store = get_project_session_store()
if project_store.session_exists(session):
session_id = session

if fork:
from praisonaiagents.session.hierarchy import HierarchicalSessionStore
from ..utils.project import get_project_sessions_dir

hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
forked_session_id = hierarchical_store.fork_session(session_id)
session_id = forked_session_id
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment on lines +702 to +713

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Silent failure when specified session does not exist.

When --session is provided but the session doesn't exist, this profiled path silently continues without setting session_id, while the non-profiled _run_from_file (lines 400-401) properly emits an error and exits. Similarly, when --continue finds no previous session, no warning is logged here (contrast with lines 390-391).

This inconsistency could cause confusing behavior where profiled runs silently ignore session flags.

Proposed fix to add error handling
     if continue_session or session or fork:
         from ..state.project_sessions import get_project_session_store, find_last_session
         
         if continue_session:
             session_id = find_last_session()
+            if not session_id:
+                typer.echo("Warning: No previous sessions found. Starting new session.", err=True)
         elif session:
             project_store = get_project_session_store()
             if project_store.session_exists(session):
                 session_id = session
                 
                 if fork:
                     from praisonaiagents.session.hierarchy import HierarchicalSessionStore
                     from ..utils.project import get_project_sessions_dir
                     
                     hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
                     forked_session_id = hierarchical_store.fork_session(session_id)
                     session_id = forked_session_id
+            else:
+                typer.echo(f"Error: Session not found: {session}", err=True)
+                raise typer.Exit(1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
elif session:
project_store = get_project_session_store()
if project_store.session_exists(session):
session_id = session
if fork:
from praisonaiagents.session.hierarchy import HierarchicalSessionStore
from ..utils.project import get_project_sessions_dir
hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
forked_session_id = hierarchical_store.fork_session(session_id)
session_id = forked_session_id
if continue_session or session or fork:
from ..state.project_sessions import get_project_session_store, find_last_session
if continue_session:
session_id = find_last_session()
if not session_id:
typer.echo("Warning: No previous sessions found. Starting new session.", err=True)
elif session:
project_store = get_project_session_store()
if project_store.session_exists(session):
session_id = session
if fork:
from praisonaiagents.session.hierarchy import HierarchicalSessionStore
from ..utils.project import get_project_sessions_dir
hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
forked_session_id = hierarchical_store.fork_session(session_id)
session_id = forked_session_id
else:
typer.echo(f"Error: Session not found: {session}", err=True)
raise typer.Exit(1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai/praisonai/cli/commands/run.py` around lines 684 - 695, The
session existence check using `project_store.session_exists(session)` only
handles the true case where the session exists, but lacks an else clause to
handle when the session does not exist, causing the code to silently continue
without setting `session_id`. Add an else block after the `if
project_store.session_exists(session)` condition that emits an error message
stating the session was not found and exits execution, matching the error
handling pattern used in `_run_from_file` (lines 400-401). Additionally, ensure
the `--continue` flag handling also includes appropriate warning or error
logging consistent with the pattern seen at lines 390-391 when no previous
session is found.

else:
typer.echo(f"Error: Session not found: {session}", err=True)
raise typer.Exit(1)

if not no_save:
import uuid
auto_save_name = session_id or "session-" + str(uuid.uuid4())[:8]
if session_id or auto_save_name:
class Args:
pass
Expand Down Expand Up @@ -866,9 +882,35 @@ def _run_prompt_profiled(
agent_config["llm"] = model

# Apply session continuity if requested
session_id, auto_save_name = resolve_session_params(
continue_session, session, fork, no_save
)
session_id = None
auto_save_name = None

if continue_session or session or fork:
from ..state.project_sessions import get_project_session_store, find_last_session

if continue_session:
session_id = find_last_session()
if not session_id:
typer.echo("Warning: No previous sessions found. Starting new session.", err=True)
elif session:
project_store = get_project_session_store()
if project_store.session_exists(session):
session_id = session

if fork:
from praisonaiagents.session.hierarchy import HierarchicalSessionStore
from ..utils.project import get_project_sessions_dir

hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
forked_session_id = hierarchical_store.fork_session(session_id)
session_id = forked_session_id
Comment on lines +895 to +906

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Same silent failure issue as _run_from_file_profiled.

This code has the identical missing error handling when --session specifies a non-existent session or when --continue finds no previous sessions. The non-profiled _run_prompt (lines 498-506) properly handles these cases.

Proposed fix to add error handling
     if continue_session or session or fork:
         from ..state.project_sessions import get_project_session_store, find_last_session
         
         if continue_session:
             session_id = find_last_session()
+            if not session_id:
+                typer.echo("Warning: No previous sessions found. Starting new session.", err=True)
         elif session:
             project_store = get_project_session_store()
             if project_store.session_exists(session):
                 session_id = session
                 
                 if fork:
                     from praisonaiagents.session.hierarchy import HierarchicalSessionStore
                     from ..utils.project import get_project_sessions_dir
                     
                     hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
                     forked_session_id = hierarchical_store.fork_session(session_id)
                     session_id = forked_session_id
+            else:
+                typer.echo(f"Error: Session not found: {session}", err=True)
+                raise typer.Exit(1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai/praisonai/cli/commands/run.py` around lines 872 - 883, The
session existence check in the elif session block is missing error handling for
when the specified session does not exist. The code checks if
project_store.session_exists(session) is true but has no corresponding else
clause to handle the false case, causing silent failures. Add proper error
handling (similar to how the non-profiled _run_prompt method handles this at
lines 498-506) by adding an else clause after the session_exists check that
raises an appropriate exception with a clear error message when the session
cannot be found, ensuring users are informed when their specified session does
not exist.

else:
typer.echo(f"Error: Session not found: {session}", err=True)
raise typer.Exit(1)

if not no_save:
import uuid
auto_save_name = session_id or "session-" + str(uuid.uuid4())[:8]
if session_id or auto_save_name:
from ..state.project_sessions import build_cli_memory_config, apply_cli_session_continuity

Expand Down
67 changes: 66 additions & 1 deletion src/praisonai/praisonai/llm/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,69 @@ def inject_credentials_into_env() -> bool:

except Exception:
# Ignore errors to avoid breaking the application
return False
return False


def is_configured(model: Optional[str] = None) -> bool:
"""
Check if credentials are configured for the specified or default model.

This checks both environment variables and stored credentials to determine
if the user has configured any usable API keys.

Args:
model: Optional model name to check for specific provider credentials.
If not provided, checks for any configured credentials.

Returns:
True if credentials are available, False otherwise
"""
import os

# Check environment variables first
known_keys = (
"OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
"GEMINI_API_KEY", "GROQ_API_KEY", "COHERE_API_KEY",
"OLLAMA_HOST", # Ollama doesn't need an API key
)

# If any env var is set, consider it configured
if any(os.environ.get(k) for k in known_keys):
return True

# Check stored credentials
try:
store = CredentialStore()
providers = store.list_providers()

# If we have any stored credentials, we're configured
if providers:
# If model is specified, check for that specific provider
if model:
# Map model prefixes to providers
model_lower = model.lower()
if model_lower.startswith("gpt"):
return "openai" in [p.lower() for p in providers]
elif model_lower.startswith("claude"):
return "anthropic" in [p.lower() for p in providers]
elif model_lower.startswith("gemini"):
return "google" in [p.lower() for p in providers] or "gemini" in [p.lower() for p in providers]
elif model_lower.startswith("llama") or model_lower.startswith("mistral"):
# Could be Ollama or Groq
return "ollama" in [p.lower() for p in providers] or "groq" in [p.lower() for p in providers]

# Any stored credential means we're configured
return True

Comment on lines +126 to +160

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Model-specific gate is bypassed by unrelated credentials.

Line 134 treats any credential signal (including OLLAMA_HOST) as configured, and Line 159 returns True for any stored provider when the model prefix is unknown. With downstream is_configured(model="gpt-4o-mini"), this can skip onboarding even when no OpenAI-compatible credential exists, leading back to first-call failures.

Suggested fix (model-aware credential checks)
 def is_configured(model: Optional[str] = None) -> bool:
     import os
+    provider_env = {
+        "openai": ("OPENAI_API_KEY",),
+        "anthropic": ("ANTHROPIC_API_KEY",),
+        "google": ("GOOGLE_API_KEY", "GEMINI_API_KEY"),
+        "gemini": ("GOOGLE_API_KEY", "GEMINI_API_KEY"),
+        "groq": ("GROQ_API_KEY",),
+        "cohere": ("COHERE_API_KEY",),
+        "ollama": ("OLLAMA_HOST",),
+    }
+
+    def providers_for_model(m: Optional[str]) -> Optional[set[str]]:
+        if not m:
+            return None
+        ml = m.lower()
+        if ml.startswith("gpt"):
+            return {"openai"}
+        if ml.startswith("claude"):
+            return {"anthropic"}
+        if ml.startswith("gemini"):
+            return {"google", "gemini"}
+        if ml.startswith("llama") or ml.startswith("mistral"):
+            return {"ollama", "groq"}
+        return set()  # unknown: don't auto-pass; defer to resolver fallback
+
+    target = providers_for_model(model)

-    known_keys = (
-        "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
-        "GEMINI_API_KEY", "GROQ_API_KEY", "COHERE_API_KEY",
-        "OLLAMA_HOST",
-    )
-    if any(os.environ.get(k) for k in known_keys):
-        return True
+    if target is None:
+        known_keys = tuple(k for keys in provider_env.values() for k in keys)
+        if any(os.environ.get(k) for k in known_keys):
+            return True
+    elif target:
+        target_keys = tuple(k for p in target for k in provider_env[p])
+        if any(os.environ.get(k) for k in target_keys):
+            return True

     try:
         store = CredentialStore()
-        providers = store.list_providers()
-        if providers:
-            ...
-            return True
+        providers = {p.lower() for p in store.list_providers()}
+        if target is None:
+            return any((p == "ollama" and os.environ.get("OLLAMA_HOST")) or store.has_credential(p) for p in providers)
+        if target:
+            return any((p == "ollama" and os.environ.get("OLLAMA_HOST")) or store.has_credential(p) for p in (providers & target))
+        # unknown model: continue to resolver fallback
     except Exception:
         pass
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai/praisonai/llm/credentials.py` around lines 126 - 160, The
is_configured function is too permissive when a specific model is requested - it
returns True if any unrelated credential exists (e.g., OLLAMA_HOST when checking
for gpt-4o-mini). To fix this, modify the initial environment variable check to
filter known_keys based on the requested model when model is specified, only
checking relevant credentials for that model prefix. Additionally, at the final
return statement after listing providers, do not return True unconditionally for
any stored provider when model is specified - instead, only return True if a
matching provider for the requested model was actually found in the earlier
model prefix checks. This ensures that requesting a specific model requires the
appropriate credential type rather than any unrelated credential.

except Exception:
# If we can't check stored credentials, fall back to env check
pass

# Finally, check if we can resolve an endpoint with credentials
try:
endpoint = resolve_llm_endpoint_with_credentials()
return bool(endpoint.api_key)
except Exception:
pass

return False
Comment on lines +165 to +172

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The file still lacks a trailing newline at the end of the newly-added is_configured function.

Suggested change
# Finally, check if we can resolve an endpoint with credentials
try:
endpoint = resolve_llm_endpoint_with_credentials()
return bool(endpoint.api_key)
except Exception:
pass
return False
# Finally, check if we can resolve an endpoint with credentials
try:
endpoint = resolve_llm_endpoint_with_credentials()
return bool(endpoint.api_key)
except Exception:
pass
return False

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Loading