From 5902078a5917ec58e21d86b90c46e9b7ab9dfa4e Mon Sep 17 00:00:00 2001 From: Jacob Fu <141651335+FuJacob@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:59:29 -0700 Subject: [PATCH] Match Extended Context cap to what the engine actually uses The UI advertised a 4000-character Extended Context limit, but the Open Source base-model path rendered it as a 'notes' section hard-capped at 600 chars (including a ~32-char label), so only the first ~570 chars were ever used on-device and the rest was silently dropped. Lower the user-facing/storage cap to 1200 and raise the notes section cap to 1300 so the full entry reaches the local model. The total base-prompt budget stays 2400 chars, so prompt size and latency are unchanged; this only stops wasting the budget. Both caps now document the invariant (notes maxChars >= UI cap + label) so they cannot silently diverge again. Also shrinks the Foundation Models nano context-window overflow described in #486. Refs #486 --- Cotabby/Models/SuggestionSettingsModel.swift | 15 +++++++++------ .../Support/BaseCompletionPromptRenderer.swift | 7 ++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Cotabby/Models/SuggestionSettingsModel.swift b/Cotabby/Models/SuggestionSettingsModel.swift index 20c1009..46e3f97 100644 --- a/Cotabby/Models/SuggestionSettingsModel.swift +++ b/Cotabby/Models/SuggestionSettingsModel.swift @@ -138,12 +138,15 @@ final class SuggestionSettingsModel: ObservableObject { static let defaultGhostTextOpacity: Double = 1.0 static let ghostTextOpacityStep: Double = 0.1 - /// Hard upper bound on the persisted Extended Context blob, in characters. Sized so the user - /// can paste a meaningful glossary or style guide without crowding the model's shared context: - /// roughly ~1000 tokens of English, which still leaves headroom for instructions, prefix text, - /// clipboard, and visual context inside Apple's 4096-token window. Larger pastes are truncated - /// at write time so the cost is bounded on every subsequent request. - static let maximumExtendedContextCharacters: Int = 4_000 + /// Hard upper bound on the persisted Extended Context blob, in characters. Sized to match what the + /// engines actually consume rather than what they can store: the OSS base path renders this as a + /// budgeted "notes" section (`BaseCompletionPromptRenderer`, `maxChars` 1300) inside a 2400-char + /// prompt, so a larger cap would just be clipped on-device instead of used. ~1200 chars (~300 + /// tokens) is a meaningful glossary or style guide that still leaves room for the prefix and other + /// context, and stays well inside Apple's 4096-token window on the Foundation Models path. Keep this + /// at or below the notes section's `maxChars` minus its label so the full blob survives on the OSS + /// path. Larger pastes are truncated at write time so the cost is bounded on every request. + static let maximumExtendedContextCharacters: Int = 1_200 init( configuration: SuggestionConfiguration, diff --git a/Cotabby/Support/BaseCompletionPromptRenderer.swift b/Cotabby/Support/BaseCompletionPromptRenderer.swift index f92ac62..dd1f5dc 100644 --- a/Cotabby/Support/BaseCompletionPromptRenderer.swift +++ b/Cotabby/Support/BaseCompletionPromptRenderer.swift @@ -43,7 +43,12 @@ enum BaseCompletionPromptRenderer { sections.append(Self.contextSection("language", language, priority: 50, maxChars: 300)) } if let notes = Self.nonEmpty(extendedContext) { - sections.append(Self.contextSection("notes", "Notes the writer keeps in mind: \(notes)", priority: 40, maxChars: 600)) + // `maxChars` must stay at or above `SuggestionSettingsModel.maximumExtendedContextCharacters` + // plus this label (~32 chars) so the full user-entered Extended Context survives here instead + // of being silently clipped far under the advertised cap. It still competes for the 2400-char + // total budget below (priority 40), so an unusually long prefix can trim it, but in normal use + // the whole blob lands. + sections.append(Self.contextSection("notes", "Notes the writer keeps in mind: \(notes)", priority: 40, maxChars: 1300)) } if let clip = Self.nonEmpty(clipboardContext) { sections.append(Self.contextSection("clipboard", "On the clipboard: \(clip)", priority: 35, maxChars: 400))