Add an AI client module that integrates with OpenAI-compatible API
providers (OpenAI, Perplexity, and custom providers) to provide
AI-assisted responses within the profanity client.
The implementation includes:
- src/ai/ai_client.c/h: Core AI client with provider management,
session handling, and async HTTP request handling via libcurl.
Supports per-provider API keys stored in preferences, reference-
counted sessions, and conversation history tracking.
- src/ui/window.c/window_list.c: New AI window type (ProfAiWin) for
displaying AI conversations, with response streaming and error
display capabilities.
- Command integration: New `/ai` command (cmd_defs.c, cmd_funcs.c)
for creating sessions, sending prompts, and managing providers.
Provider autocomplete support in cmd_ac.c.
- Preferences integration: API keys for providers are persisted in
the preferences system (config/preferences.c).
- Unit tests: 472 lines of comprehensive tests covering provider
management, session lifecycle, JSON escaping, and autocomplete
(tests/unittests/test_ai_client.c).
Architecture decisions:
- Asynchronous design: HTTP requests run on a separate thread to
avoid blocking the main UI loop. Callbacks are invoked on the main
thread via direct function call (profanity uses ncurses, not GLib
main loop).
- Reference counting: Both AIProvider and AISession use ref counting
for safe shared ownership.
- Response size limit: 10MB cap on HTTP responses to prevent OOM.
The synchronous `get_message_from_editor` blocked the main loop (`prof_run`) while launching an external editor like `vim`, halting network I/O in `session_process_events` and preventing incoming message reception.
Introduced `get_message_from_editor_async` for `cmd_editor` to run the editor asynchronously. It forks and execs the editor in a thread (`editor_thread`), suspending NCurses to free the terminal. The main loop skips `inp_readline` and `ui_update` via a new `background_mode` flag while the editor runs, allowing `session_process_events` to keep the connection alive.
On editor completion, `editor_process` (called per loop iteration) resumes NCurses with `ui_resize`, inserts the result into the readline buffer and clears `background_mode`.
Retained synchronous `get_message_from_editor` for ~20 existing code paths (e.g., `vcard_nickname`) to avoid breaking them.
Tested with `vim` and `nano`: confirms no rendering conflicts, messages received during editing, and seamless resume. Edge cases like editor crashes handled via error logging and seamless resume.
While trying to get the unit tests working again I stumbled over all those
things that I thought could be better^TM.
Now we also know "TODO: why does this make the test fail?" - because
the unit tests are brittle AF ... and we have to init the subsystems
we use in the test, otherwise the cleanup will fail...
BTW. you can now also only run a single test ... or a pattern or so ...
you'd have to read how `cmocka_set_test_filter()` works exactly.
Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
Instead of adding stuff to `_shutdown()`, we can now register a shutdown
routine from the respective `init()` function of our modules.
This also has the advantage, that we're sure they're called in reverse
order from how the initialization happened.
I didn't simply use `atexit()` because POSIX says that the number of
possible callbacks is limited (min 32) and I was not sure whether
we will maybe extend this number at one point.
Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
This allows to kind of automate what profanity should do as first jobs,
e.g. `--cmd /foo --cmd /bar --cmd /quit` so one can easily check for memory
leaks.
Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
Before this change issuing `/quit` directly called `exit(0)` and did not
invoke all the graceful shutdown routines. Now we first try to exit from
the event loop, which includes cleaning up everything.
In case the event loop is stuck for some reason, you could try to issue a
second `/quit`, which will then directly call `exit(0)`.
Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
Revert "Merge pull request #1943 from H3rnand3zzz/gmainloop
This reverts commit 609fde0998, reversing
changes made to 2ec94064ed.
Revert "Merge pull request #1948 from H3rnand3zzz/fix/rl-less-refreshes"
This reverts commit 11762fd2b0, reversing
changes made to 609fde0998.
We have got several issues, that we don't quite see how to solve, with
the merge of the gmainloop PR.
* Slashguard is broken (#1955) (though #1956 could fix that)
* One person reported problems with copy paste selection via mouse
* Some input buffer seems not to be cleared correctly
It happened that I was debugging profanity used `/connect` and typed
the password. I then debugged so long that a time out occurred, so
profanity disconnected. Then it printed "unknown command: $password".
There was something else that I forgot now.
Bottomline is: so far we didn't get it right so we will undo these
changes until someone proposes a working solution.
We got a slight performance increase (apparently noticable when
alt+mouse scrolling) but got too many issues with this change.
1. close logfile as last action
2. Fix `plugins_shutdown()` accessing `((ProfPlugin*)curr->data)->lang`
after `curr->data` had already potentially been free'd.
Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
- Added JABBER_RAW_CONNECT[ING/ED] connection states
- Added cl_ev_connect_raw and session_connect_raw to conform to normal
connection functions
- Fixed SIGABRT during registration
- Added a check in cmd_register to ensure it's actually connected before
registering--but this will always fail atm
We call omemo_init() when starting profanity and should have an
omemo_close() at exit.
For now we free the fingerprint autocompleter in there.
Fixes valgrind:
```
==13226== 24 bytes in 1 blocks are definitely lost in loss record 2,855
of 6,958
==13226== at 0x483677F: malloc (in
/usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==13226== by 0x48AD39: autocomplete_new (autocomplete.c:57)
==13226== by 0x4AB89F: omemo_init (omemo.c:127)
==13226== by 0x42C283: _init (profanity.c:206)
==13226== by 0x42BFF3: prof_run (profanity.c:98)
==13226== by 0x4B25E6: main (main.c:172)
```
Regards https://github.com/profanity-im/profanity/issues/1131