Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions Cotabby/Models/SuggestionSettingsModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion Cotabby/Support/BaseCompletionPromptRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Comment on lines +46 to +51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Invariant is comment-only with no programmatic guard

The relationship between notes maxChars (1300) and SuggestionSettingsModel.maximumExtendedContextCharacters + label_length (1200 + 32 = 1232) is documented in the comment but never asserted. If the label string in line 51 grows or maximumExtendedContextCharacters rises above 1268 (1300 − 32), notes will again be silently clipped without any failing test or compile-time signal. The ExtendedContextTests only validate the model-layer cap, not that the renderer's maxChars is large enough to pass the full blob through.

Consider a #if DEBUG assertion in BaseCompletionPromptRenderer or an XCTAssert in the renderer tests that directly checks notes maxChars >= SuggestionSettingsModel.maximumExtendedContextCharacters + labelLength so the two constants cannot diverge silently again.

Fix in Codex Fix in Claude Code

}
if let clip = Self.nonEmpty(clipboardContext) {
sections.append(Self.contextSection("clipboard", "On the clipboard: \(clip)", priority: 35, maxChars: 400))
Expand Down