Skip to content

Commit 26504c6

Browse files
committed
feat(context): Implement structured XML context with embedded rules
This commit changes the gptel context format from a simple markdown block to a structured XML format. - Uses `<context_item>` tags for each context source. - Wraps all user content in `<![CDATA[...]]>` for safety. - Adds precise `start_line` and `end_line` numbers for all content.
1 parent 22cc86d commit 26504c6

File tree

1 file changed

+59
-39
lines changed

1 file changed

+59
-39
lines changed

gptel-context.el

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -291,12 +291,15 @@ afterwards."
291291
"Highlight the region from START to END.
292292
293293
ADVANCE controls the overlay boundary behavior."
294-
(let ((overlay (make-overlay start end nil (not advance) advance)))
294+
(let ((overlay (make-overlay start end nil (not advance) advance))
295+
(buffer (current-buffer))
296+
(entry (assoc (current-buffer) gptel-context--alist)))
295297
(overlay-put overlay 'evaporate t)
296298
(overlay-put overlay 'face 'gptel-context-highlight-face)
297299
(overlay-put overlay 'gptel-context t)
298-
(push overlay (alist-get (current-buffer)
299-
gptel-context--alist))
300+
(if entry
301+
(setf (cdr entry) (cons overlay (cdr entry)))
302+
(push (cons buffer (list overlay)) gptel-context--alist))
300303
overlay))
301304

302305
;;;###autoload
@@ -412,40 +415,31 @@ START and END signify the region delimiters."
412415

413416
(defun gptel-context--insert-buffer-string (buffer contexts)
414417
"Insert at point a context string from all CONTEXTS in BUFFER."
415-
(let ((is-top-snippet t)
416-
(previous-line 1))
417-
(insert (format "In buffer `%s`:" (gptel-context--format-buffer-name buffer))
418-
"\n\n```" (gptel--strip-mode-suffix (buffer-local-value
419-
'major-mode buffer))
420-
"\n")
421-
(dolist (context contexts)
422-
(let* ((start (overlay-start context))
423-
(end (overlay-end context))
424-
content)
425-
(let (lineno column)
426-
(with-current-buffer buffer
427-
(without-restriction
428-
(setq lineno (line-number-at-pos start t)
429-
column (save-excursion (goto-char start)
430-
(current-column))
431-
content (buffer-substring-no-properties start end))))
432-
;; We do not need to insert a line number indicator if we have two regions
433-
;; on the same line, because the previous region should have already put the
434-
;; indicator.
435-
(unless (= previous-line lineno)
436-
(unless (= lineno 1)
437-
(unless is-top-snippet
438-
(insert "\n"))
439-
(insert (format "... (Line %d)\n" lineno))))
440-
(setq previous-line lineno)
441-
(unless (zerop column) (insert " ..."))
442-
(if is-top-snippet
443-
(setq is-top-snippet nil)
444-
(unless (= previous-line lineno) (insert "\n"))))
445-
(insert content)))
446-
(unless (>= (overlay-end (car (last contexts))) (point-max))
447-
(insert "\n..."))
448-
(insert "\n```")))
418+
(let* ((major-mode (buffer-local-value 'major-mode buffer))
419+
(file-name (buffer-file-name buffer))
420+
(buffer-name (buffer-name buffer))
421+
(contexts (sort contexts #'overlay-start<)))
422+
(insert "<context_item>\n")
423+
(when buffer-name
424+
(insert (format " <buffer_name>%s</buffer_name>\n" buffer-name)))
425+
(when file-name
426+
(insert (format " <file_path>%s</file_path>\n" (abbreviate-file-name file-name))))
427+
(when major-mode
428+
(insert (format " <file_type>%s</file_type>\n" (gptel--strip-mode-suffix major-mode))))
429+
(dolist (context contexts)
430+
(let (start end content start-line end-line)
431+
(with-current-buffer buffer
432+
(setq start (overlay-start context)
433+
end (overlay-end context)
434+
content (buffer-substring-no-properties start end)
435+
start-line (line-number-at-pos start t)
436+
end-line (line-number-at-pos end t)))
437+
(insert (format " <content start_line=\"%d\" end_line=\"%d\"><![CDATA[\n" start-line end-line))
438+
(insert content)
439+
(unless (string-suffix-p "\n" content)
440+
(insert "\n"))
441+
(insert "]]></content>\n"))))
442+
(insert "</context_item>"))
449443

450444
(defun gptel-context--string (context-alist)
451445
"Format the aggregated gptel context as annotated markdown fragments.
@@ -457,14 +451,40 @@ context overlays, see `gptel-context--alist'."
457451
if (bufferp buf)
458452
do (gptel-context--insert-buffer-string buf ovs)
459453
else if (not (plist-get ovs :mime))
460-
do (gptel--insert-file-string buf) end
454+
do (let ((path buf))
455+
(let* ((content-and-mode
456+
(with-temp-buffer
457+
(insert-file-contents path)
458+
(list (buffer-string) major-mode)))
459+
(end-line (with-temp-buffer (insert (car content-and-mode)) (count-lines (point-min) (point-max)))))
460+
(insert "<context_item>\n")
461+
(insert (format " <file_path>%s</file_path>\n" (abbreviate-file-name path)))
462+
(when (cadr content-and-mode)
463+
(insert (format " <file_type>%s</file_type>\n"
464+
(gptel--strip-mode-suffix (cadr content-and-mode)))))
465+
(insert (format " <content start_line=\"1\" end_line=\"%d\"><![CDATA[\n" end-line))
466+
(insert (car content-and-mode))
467+
(unless (string-suffix-p "\n" (car content-and-mode))
468+
(insert "\n"))
469+
(insert "]]></content>\n")
470+
(insert "</context_item>"))) end
461471
do (insert "\n\n")
462472
finally do
463473
(skip-chars-backward "\n\t\r ")
464474
(delete-region (point) (point-max))
465475
(unless (bobp)
466476
(goto-char (point-min))
467-
(insert "Request context:\n\n"))
477+
(insert "CONTEXT USAGE AND RULES:\n\n"
478+
"A \"Request context\" block is provided below. This context is the primary source of truth for the files or buffers mentioned.\n\n"
479+
"- Each context item is wrapped in `<context_item>` tags.\n"
480+
"- It contains metadata like `<file_path>` and `<file_type>`.\n"
481+
"- The code/text is inside a `<content>` tag with `start_line` and `end_line` attributes. The content is wrapped in `<![CDATA[...]]>`.\n\n"
482+
"**Mandatory Rules for Using Context:**\n"
483+
"1. **Source of Truth:** The context provided is the ground truth. YOU MUST use the content within the `<content>` tags for any actions related to that file and line range.\n"
484+
"2. **No Redundant Reading:** DO NOT use file-reading tools for any file path if its content is already provided in the context. Rely on the context.\n"
485+
"3. **Precise File Edits:** When using file modification tools, your edits MUST be based on the provided context. The line numbers and the original text you are replacing must align exactly with the information in the `<content>` tags.\n\n"
486+
"---\n\n"
487+
"Request context:\n\n"))
468488
finally return
469489
(and (> (buffer-size) 0)
470490
(buffer-string)))))

0 commit comments

Comments
 (0)