Conversation History¶
conversation
¶
Conversation history — in-memory storage with optional disk persistence.
Messages are kept in a plain Python list so they are always available for context without any I/O. The list is also written to a JSON file on disk (in the user's data directory) so prior conversations survive restarts.
Design notes¶
- No database dependency; JSON is self-contained and human-readable.
- Only text content is persisted. Image attachments are not stored on
disk (they can be large and are usually transient). The
has_imageflag lets the UI indicate that images were part of a turn. max_messagescaps the in-memory list so RAM stays bounded during very long sessions. Older messages are trimmed from the front (oldest first), preserving the most recent context.
Encryption¶
Pass encrypt=True (the default when the cryptography package is
installed) to store the history file as a Fernet-encrypted blob. The
symmetric key is kept in a separate history.key file in the same
directory, protected with 0o600 permissions.
::
history = ConversationHistory(encrypt=True) # key auto-created
history.add("user", "Hello!")
# ~/.local/share/linux-ai-npu-assistant/history.enc ← ciphertext
# ~/.local/share/linux-ai-npu-assistant/history.key ← AES key (owner only)
Message
dataclass
¶
A single turn in the conversation.
ConversationHistory
¶
ConversationHistory(max_messages=_DEFAULT_MAX_MESSAGES, persist_path=_DEFAULT_HISTORY_FILE, system_prompt='', encrypt=False, encryption_key=None)
Thread-safe, persistent conversation history.
Parameters¶
max_messages:
Maximum number of messages kept in memory. When the list exceeds
this limit the oldest messages are removed first.
persist_path:
JSON file path for persistence. Pass None to disable disk
persistence (history lives only for the current session). When
encrypt is True the path is rewritten with a .enc
extension automatically.
system_prompt:
An optional system message prepended to every API call to establish
the assistant's persona / instructions.
encrypt:
When True (and the cryptography package is installed), the
history file is encrypted with Fernet symmetric encryption. A key
file is stored alongside the history file with 0o600 permissions.
Defaults to True when cryptography is available, False
otherwise (graceful degradation).
encryption_key:
Optional pre-existing Fernet key bytes. When omitted the key is
loaded from (or created in) a history.key file next to the
history file.
Source code in src/conversation.py
add
¶
Append a message and persist immediately.
Parameters¶
role:
"user" or "assistant".
content:
Text content of the message.
has_image:
Set to True when the turn included an image (screenshot or
uploaded file). The image itself is not stored here.
Returns¶
Message The newly added message object.
Source code in src/conversation.py
clear
¶
Remove all messages from memory and erase the on-disk file.
all_messages
¶
recent
¶
to_openai_messages
¶
Return the message list in OpenAI /chat/completions format.
Parameters¶
include_system: Prepend the system prompt if one is configured. max_context: Only include the most recent max_context messages (besides the system message). Use this to avoid hitting context-length limits.
Source code in src/conversation.py
to_ollama_messages
¶
Return the message list in Ollama /api/chat format.
Ollama's chat endpoint mirrors the OpenAI format, so this is a thin
wrapper around :meth:to_openai_messages.
Source code in src/conversation.py
set_password
¶
Derive a new encryption key from password and re-save.
Uses PBKDF2-HMAC-SHA256 with a fresh random salt so the on-disk key file is updated. The history is immediately re-written with the new key.
Parameters¶
password: User-chosen password. An empty string disables password-based encryption and falls back to the auto-generated random key.
Raises¶
RuntimeError
If the cryptography package is not installed.
Source code in src/conversation.py
change_password
¶
Change the encryption password.
Verifies that old_password correctly decrypts the current file before switching to new_password.
Raises¶
ValueError If old_password is wrong (decryption fails).
Source code in src/conversation.py
export_plaintext
¶
Write the conversation history as unencrypted JSON to export_path.
The exported file is not protected by encryption or restricted permissions — callers are responsible for handling it securely.
Parameters¶
export_path: Destination file path (will be created or overwritten).
Source code in src/conversation.py
import_history
¶
Import conversation history from a file.
Supports both plain-JSON exports and Fernet-encrypted .enc files.
Parameters¶
import_path:
Path to the file to import. May be a plain-JSON file produced by
:meth:export_plaintext or an encrypted .enc produced when
encrypt is enabled.
password:
Password for encrypted files. Pass None for plain-JSON.
If the file looks encrypted and no password is supplied a
:class:ValueError is raised.
merge:
When True messages from the import are merged with the
existing history (deduplication by timestamp). When False
(default) the current history is replaced by the imported one.
Returns¶
int Number of messages successfully imported.
Raises¶
FileNotFoundError If import_path does not exist. ValueError If the password is wrong, the file is not valid history JSON, or the file appears encrypted but no password was given.
Source code in src/conversation.py
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 | |
generate_encryption_key
¶
Generate a new Fernet key (32 bytes, URL-safe base64-encoded).
Returns¶
bytes
A 44-byte URL-safe base64 string suitable for Fernet(key).
Source code in src/conversation.py
load_or_create_key
¶
Load an existing Fernet key from key_path, or create one.
The key file is written with 0o600 permissions (owner read/write
only). If the file already exists and has correct permissions its
contents are returned unchanged.
Parameters¶
key_path:
Path to the history.key file.
Returns¶
bytes The Fernet key bytes.
Source code in src/conversation.py
encrypt_data
¶
Encrypt plaintext with Fernet and return a base64 ciphertext string.
The returned string is ASCII-safe and can be written to a text file.
Source code in src/conversation.py
decrypt_data
¶
Decrypt Fernet ciphertext and return the original plaintext.
Raises¶
cryptography.fernet.InvalidToken If the key is wrong or the data was tampered with.