Metadata-Version: 2.5
Name: codex-chat-bot
Version: 0.1.7
Summary: A small single-session chat client for OpenAI-compatible Chat Completions API endpoints.
Author-email: GGN_2015 <neko@jlulug.org>
Requires-Python: >=3.10
Requires-Dist: openai<3,>=1.99.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# codex-chat-bot

A small Python chat project with:

- a Python programming interface
- a command-line interface
- single-session memory
- local image attachments for vision-capable models
- configurable API key, base URL, model, and system rules

The project uses OpenAI SDK-compatible Chat Completions API endpoints.

## Installation

```bash
python -m pip install codex-chat-bot
```

## Environment Variables

Only `CODEX_CHAT_*` environment variables are read:

```bash
export CODEX_CHAT_API_KEY="your API key"
export CODEX_CHAT_BASE_URL="https://api.openai.com/v1"
export CODEX_CHAT_MODEL="gpt-5.5"                       # optional
export CODEX_CHAT_TIMEOUT="600"                          # optional, seconds
export CODEX_CHAT_MAX_COMPLETION_TOKENS="1024"            # optional
```

Requests use a default timeout of 600 seconds. Set
`CODEX_CHAT_TIMEOUT` to override it for slower or faster endpoints.
If an environment value is surrounded by one matching pair of single or double
quotes, that outer pair is removed automatically. Unmatched quotes are kept.

`CODEX_CHAT_BASE_URL` must be the API root, not the provider's Web UI address.
For OpenAI-compatible gateways this commonly ends in `/v1`. If the endpoint
returns an HTML page, the client raises `InvalidEndpointResponseError` with a
configuration hint.

When a real `ChatSession` creates its SDK client and the configured URL does
not already end in `/v1`, it probes the current `/models` endpoint first and
then the `/v1/models` candidate. It appends `/v1` only when the candidate is a
recognizable JSON API endpoint and the original is not. These probes do not
create model completions or consume completion tokens.

## JSON Config File

You can also load `api_key`, `base_url`, `model`, and `system_rules` from a
UTF-8 JSON file:

```json
{
    "api_key": "your API key",
    "base_url": "https://api.openai.com/v1",
    "model": "gpt-5.5",
    "system_rules": [
        "You are a concise programming assistant.",
        "Answer in English."
    ]
}
```

Use `--config` or `--config-file` to choose the file:

```bash
codex-chat --config ./config.json "Hello"
```

Values from the config file are used before environment variables and command
line options, so `CODEX_CHAT_*` variables and explicit flags can still override
the file for one run.

## Command-Line Usage

Run a single chat turn:

```bash
codex-chat "Write a Python hello world example."
```

Start interactive chat:

```bash
codex-chat
```

If `codex-chat` is not on your PATH, run the module directly:

```bash
python -m codex_chat_bot.cli
python -m codex_chat_bot.cli "Hello"
```

Interactive commands:

- `/name NAME` changes the name used for your next messages. The default name is `user`.
- `/reset` clears the current session memory.
- `/import PATH` loads chat history from a JSON file.
- `/export PATH` saves chat history to a JSON file.
- `/exit` or `/quit` exits the chat.

Common options:

```bash
codex-chat --api-key "your API key" --base-url "https://api.openai.com/v1" "Hello"
codex-chat --config ./config.json "Hello"
codex-chat --model gpt-5.5 --system "You are a concise programming assistant."
codex-chat --system-rule "Answer in English." --system-rule "Keep answers short." "Explain pytest."
codex-chat --system-rules-file ./rules.txt "Review this idea."
codex-chat --bind-history ./history.json "Continue our chat."
codex-chat --base-url "https://api.openai.com/v1" "Explain pytest."
```

If `CODEX_CHAT_API_KEY` or `CODEX_CHAT_BASE_URL` is not set and the value was
not passed on the command line, interactive CLI startup prompts for the missing
value.

`--bind-history` loads the JSON file if it already exists, creates an empty
history file if it does not, and writes every new message to that file as the
chat changes. If the file exists but is not valid chat history JSON, the CLI
prints a warning and recreates it as an empty history file.

## Python API

The object-oriented API is built around `ChatConfig`, `ChatSession`, `Message`,
and `ImageAttachment`. See the [complete Python API guide](docs/python-api.md)
for lifecycle, persistence, error handling, and extension details.

### Text messages

```python
from codex_chat_bot import ChatConfig, ChatSession

session = ChatSession(ChatConfig.from_env())

print(session.ask("Remember that my project is named codex-chat-bot."))
print(session.ask("What is my project called?"))
print(session.ask("This is Alice speaking.", username="alice"))

session.bind_history("history.json")
session.ask("This message is saved automatically.")
```

### Local image attachments

Pass one local path directly to `images=`. The file is read immediately and
embedded as a Base64 data URL in the Chat Completions request, so no public URL
or separate file-hosting service is needed:

```python
from codex_chat_bot import ChatConfig, ChatSession

session = ChatSession(ChatConfig.from_env())
answer = session.ask(
    "Read the chart and summarize its trend.",
    images="./chart.png",
)
print(answer)
```

For multiple images or explicit control over image detail, create reusable
`ImageAttachment` objects:

```python
from codex_chat_bot import ChatConfig, ChatSession, ImageAttachment

session = ChatSession(ChatConfig.from_env())
diagram = ImageAttachment.from_file("./architecture.png", detail="high")

response = session.send(
    "Compare these two diagrams.",
    images=[diagram, "./architecture-v2.webp"],
)
print(response.text)
```

Supported local formats are PNG, JPEG, WEBP, and GIF. The configured model and
OpenAI-compatible endpoint must also support image input. `detail` can be
`"auto"`, `"low"`, or `"high"`.

`images=` explicitly accepts `None`, `[]`, one path string, or a list of path
strings. `None` and `[]` both send a normal text-only message; list order is
preserved for multiple images.

To construct the complete message object yourself, use `Message.user()` and
`send_message()`:

```python
from codex_chat_bot import ImageAttachment, Message

message = Message.user(
    "Inspect this screenshot.",
    username="alice",
    images=(ImageAttachment.from_file("./screen.png"),),
)
response = session.send_message(message)
```

You can also pass configuration explicitly:

```python
from codex_chat_bot import ChatConfig, ChatSession

config = ChatConfig(
    api_key="your API key",
    base_url="https://api.openai.com/v1",
    model="gpt-5.5",
    timeout=600,
    system_rules=(
        "You are a patient Python programming assistant.",
        "Answer in English.",
        "Keep code examples runnable.",
    ),
)

session = ChatSession(config)
answer = session.ask("Write a function that reads a JSON file.")
print(answer)
```

`system_rules` are added to the session's initial system message and stay active
until the session is reset.

Pass `username=` to `ask()` or `send()` to distinguish different people in one
session. User messages are sent to the model with the speaker name included, and
chat history stores each message's `username`.

Chat history JSON uses a top-level `messages` array:

```json
{
    "messages": [
        { "role": "system", "content": "You are a helpful assistant.", "username": "system" },
        { "role": "user", "content": "Hello", "username": "alice" },
        { "role": "assistant", "content": "Hi!", "username": "assistant" }
    ]
}
```

Messages with images include an `images` array containing the media type,
detail level, filename, and Base64 image bytes. This makes an exported history
self-contained, but it can be large and should be protected as sensitive data.

## Tests

```bash
python -m pytest
```
