v0.8.6: general mode, clipboard text, PDF text extraction

Working mode: --mode coding|general (CST_MODE, settings.json, /mode,
WebUI run settings). general swaps the default framing for a general
assistant one -- workflow, safety notes, tool wording (run_shell, the
subagents) -- and /status shows it; minimal (the paper profile) stays
byte-frozen. The prompt state carries the mode so a switch leaves a
system note in history.

Clipboard: clipboard_read / clipboard_write tools (16 MiB cap read,
default 64 KiB window; writing is a WRITE permission with a preview) on
pbcopy / wl-copy / xclip / pbpaste / wl-paste / xclip and the Win32
clipboard. In the TUI, /attach paste falls back to clipboard text when
there is no image on it.

PDF: a dependency-free text extractor (src/pdf_text.c): inflate,
Flate/ASCIIHex/ASCII85, PNG/TIFF predictors, layout-independent object
scan, object streams, page tree walk with inherited resources, content
lexer (Tj/TJ/'/" and the Td/TD/T*/Tm spacing heuristics), per-font
ToUnicode CMaps and inline images skipped. Encrypted files and pages
with no text layer are refused with the reason.

Attachments: /attach (a path or files dropped on the terminal) now take
images, PDFs and text. A PDF is stored as its text layer with the mime
still saying application/pdf and its page count; anything else has to be
UTF-8 text. Documents travel inline as text parts, so CST_NO_IMAGES and
text-only models no longer drop them, while images keep their old path.

Tests: test_pdf_text (plain, bare and zlib-framed Flate, inline image,
CID + ToUnicode, object stream, error paths), documents in test_attach,
inlined documents in test_llm_body, mode in test_config.
This commit is contained in:
WHC
2026-10-11 11:09:39 +08:00
parent 50abcf1a91
commit e0b0eddf3a
28 changed files with 3291 additions and 111 deletions
+24 -14
View File
@@ -9,16 +9,24 @@
extern "C" {
#endif
/* --- image attachments ---------------------------------------------------
* An attachment is a validated copy of an image file, stored beside the
* session so the bytes a model is shown stay the bytes that were picked.
* Only the metadata travels in the JSONL; the bytes are encoded into the
* request when it is built. */
/* --- attachments ---------------------------------------------------------
* An attachment is a validated copy of a file, stored beside the session so
* what a model is shown stays what was picked. Images are stored as they are
* and encoded into the request when it is built; a PDF is reduced to its text
* layer and a text file is stored as is, both of which travel inline as text
* (so a text-only model still gets them). Only the metadata goes in the
* JSONL. */
/* Supported: PNG, JPEG, GIF, WebP, BMP (static images). */
#define CST_ATTACH_MAX_BYTES (16 * 1024 * 1024) /* one stored image */
/* Supported: PNG, JPEG, GIF, WebP, BMP (static images), PDF, UTF-8 text. */
#define CST_ATTACH_MAX_BYTES (16 * 1024 * 1024) /* one picked file */
#define CST_ATTACH_MAX_TOTAL (32 * 1024 * 1024) /* per request */
#define CST_ATTACH_MAX_COUNT 8 /* per message */
/* Text stored and inlined per document (a PDF also caps its text here). */
#define CST_ATTACH_TEXT_MAX (4 * 1024 * 1024)
/* Images go on the wire as image parts; everything else is inlined as text. */
int cst_attach_is_image(const cst_attachment_t *a);
int cst_attach_is_text(const cst_attachment_t *a);
/* Magic-byte test: 0 and a canonical mime when data holds a supported
* image, -1 otherwise (an extension alone never decides). */
@@ -35,23 +43,25 @@ int cst_attach_dir(const char *session_path, char *out, size_t out_sz);
int cst_attach_full_path(const char *session_path, const cst_attachment_t *a,
char *out, size_t out_sz);
/* Read src_path, verify it is a supported image within limits, and copy it
* into the session's attachment directory. Returns a malloc'd entry the
* caller owns (cst_attach_free), or NULL with err filled. */
/* Read src_path and copy it into the session's attachment directory: an image
* as it is, a PDF as its extracted text, anything else only when it is text.
* Returns a malloc'd entry the caller owns (cst_attach_free), or NULL with
* err filled. */
cst_attachment_t *cst_attach_import_file(const char *session_path,
const char *src_path, char *err,
size_t err_sz);
/* Store already-read bytes (clipboard, downloads); name is display-only. */
/* Store already-read image bytes (clipboard, downloads); name is
* display-only. */
cst_attachment_t *cst_attach_import_bytes(const char *session_path,
const char *name, const char *data,
size_t n, char *err, size_t err_sz);
/* Validate and read back a stored image, or NULL when it is gone. */
/* Validate and read back a stored payload, or NULL when it is gone. */
char *cst_attach_read(const char *session_path, const cst_attachment_t *a,
size_t *out_len, char *err, size_t err_sz);
/* "data:image/png;base64,...." for the wire; NULL when the file is gone. */
/* "data:image/png;base64,...." for the wire; images only, NULL otherwise. */
char *cst_attach_data_url(const char *session_path, const cst_attachment_t *a);
/* "1920x1080 PNG 412 KB" style one-liner for prompts and the UI. */
/* "1920x1080 png 412 KB" / "pdf 1.2 MB, 12 pages" one-liner for prompts. */
void cst_attach_describe(const cst_attachment_t *a, char *out, size_t out_sz);
void cst_attach_free(cst_attachment_t *a);