Skip to content
Open
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
3 changes: 2 additions & 1 deletion clai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Either way, running `clai` will start an interactive session where you can chat
## Help

```
usage: clai [-h] [-m [MODEL]] [-a AGENT] [-l] [-t [CODE_THEME]] [--no-stream] [--version] [prompt]
usage: clai [-h] [-m [MODEL]] [-a AGENT] [-l] [-t [CODE_THEME]] [--no-stream] [--tui] [--version] [prompt]

Pydantic AI CLI v...

Expand All @@ -77,5 +77,6 @@ options:
-t [CODE_THEME], --code-theme [CODE_THEME]
Which colors to use for code, can be "dark", "light" or any theme from pygments.org/styles/. Defaults to "dark" which works well on dark terminals.
--no-stream Disable streaming from the model
--tui Launch clai as a TUI application.
--version Show version and exit
```
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@

from typing_inspection.introspection import get_literal_values

from . import __version__
from ._run_context import AgentDepsT
from .agent import AbstractAgent, Agent
from .exceptions import UserError
from .messages import ModelMessage, TextPart
from .models import KnownModelName, infer_model
from .output import OutputDataT
from .. import __version__
from .._run_context import AgentDepsT
from ..agent import AbstractAgent, Agent
from ..exceptions import UserError
from ..messages import ModelMessage, TextPart
from ..models import KnownModelName, infer_model
from ..output import OutputDataT
from .tui import CLAIApp

try:
import argcomplete
Expand Down Expand Up @@ -102,7 +103,7 @@ def cli_exit(prog_name: str = 'pai'): # pragma: no cover
sys.exit(cli(prog_name=prog_name))


def cli( # noqa: C901
def cli(
args_list: Sequence[str] | None = None, *, prog_name: str = 'pai', default_model: str = 'openai:gpt-4.1'
) -> int:
"""Run the CLI and return the exit code for the process."""
Expand Down Expand Up @@ -149,18 +150,19 @@ def cli( # noqa: C901
default='dark',
)
parser.add_argument('--no-stream', action='store_true', help='Disable streaming from the model')
parser.add_argument('--tui', action='store_true', help='Launch clai as a TUI application.')
parser.add_argument('--version', action='store_true', help='Show version and exit')

argcomplete.autocomplete(parser)
args = parser.parse_args(args_list)

console = Console()
name_version = f'[green]{prog_name} - Pydantic AI CLI v{__version__}[/green]'
name_version = f'{prog_name} - Pydantic AI CLI v{__version__}'
if args.version:
console.print(name_version, highlight=False)
console.print(wrap_color(name_version, 'green'), highlight=False)
return 0
if args.list_models:
console.print(f'{name_version}\n\n[green]Available models:[/green]')
console.print(wrap_color(f'{name_version}\n\nAvailable models:', 'green'))
for model in qualified_model_names:
console.print(f' {model}', highlight=False)
return 0
Expand All @@ -185,19 +187,22 @@ def cli( # noqa: C901
try:
agent.model = infer_model(args.model or default_model)
except UserError as e:
console.print(f'Error initializing [magenta]{args.model}[/magenta]:\n[red]{e}[/red]')
console.print(f'Error initializing {wrap_color(args.model, "magenta")}:\n{wrap_color(e, "red")}')
return 1

model_name = agent.model if isinstance(agent.model, str) else f'{agent.model.system}:{agent.model.model_name}'
if args.agent and model_arg_set:
console.print(
f'{name_version} using custom agent [magenta]{args.agent}[/magenta] with [magenta]{model_name}[/magenta]',
highlight=False,

if args.tui:
app = CLAIApp(
agent,
PYDANTIC_AI_HOME / PROMPT_HISTORY_FILENAME,
prompt=args.prompt,
title=title(name_version, args.agent, model_name, tui=args.tui),
)
elif args.agent:
console.print(f'{name_version} using custom agent [magenta]{args.agent}[/magenta]', highlight=False)
else:
console.print(f'{name_version} with [magenta]{model_name}[/magenta]', highlight=False)
app.run()
return 0

console.print(title(name_version, args.agent, model_name, tui=args.tui), highlight=False)

stream = not args.no_stream
if args.code_theme == 'light':
Expand Down Expand Up @@ -366,3 +371,24 @@ def handle_slash_command(
else:
console.print(f'[red]Unknown command[/red] [magenta]`{ident_prompt}`[/magenta]')
return None, multiline


def wrap_color(obj: Any, color: str) -> str:
return f'[{color}]{obj}[/{color}]'


def title(name_version: str, agent: str | None = None, model: str | None = None, tui: bool = False) -> str:
if tui:
if agent and model:
return f'{name_version} using custom agent **{agent}** with `{model}`'
elif agent:
return f'{name_version} using custom agent **{agent}**'
else:
return f'{name_version} with `{model}`'
else:
if agent and model:
return f'{wrap_color(name_version, "green")} using custom agent {wrap_color(agent, "magenta")} with {wrap_color(model, "magenta")}'
elif agent:
return f'{wrap_color(name_version, "green")} using custom agent {wrap_color(agent, "magenta")}'
else:
return f'{wrap_color(name_version, "green")} with {wrap_color(model, "magenta")}'
Copy link

Choose a reason for hiding this comment

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

Bug: Title Function Misbehaves with Custom Agents

The title() function changes the display logic for custom agents, now always showing the model name when an agent is active, even if the model wasn't explicitly set via --model. This differs from previous behavior. Also, the function's else branches don't gracefully handle model=None, potentially displaying "None" literally.

Additional Locations (1)

Fix in Cursor Fix in Web

77 changes: 77 additions & 0 deletions pydantic_ai_slim/pydantic_ai/_cli/clai.tcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
Conversation {
height: auto;
max-height: 1fr;

#contents {
layout: stream;
height: 1fr;
}

#contents > * {
margin-bottom: 1;
}

Prompt {
height: auto;
padding: 0 0 0 1;
#prompt {
padding-left: 0;
color: $text-primary;
text-style: bold;
}
Input {
background: transparent;
padding: 0 1;
border: none;
height: 1;
}
TextArea {
background: transparent;
height: auto;
min-height: 3;
}
}

UserText {
background: black 10%;
padding: 1 0;
border-left: wide $success;
#prompt {
color: $text-muted;
}
#message {
color: $text-muted;
padding: 0 1;
}
}

Response {
padding: 0 1 0 1;
& > MarkdownBlock {
padding: 0;
&:last-child {
margin-bottom:0;
}
}
}

ErrorMessage {
background: $error 10%;
color: $text-error;
}
}

Footer {
background: black 10%;
.footer-key--key {
color: $text;
background: transparent;
text-style: bold;
padding: 0 1;
}
.footer-key--description {
padding: 0 1 0 0;
color: $text-muted;
background: $footer-description-background;
}
}
Loading
Loading