Skip to content

Commit b6ccb62

Browse files
authored
fix: truncate FileContext to limits (#572)
* fix: validate FileContext limits * truncate * fmt * clean up
1 parent 96b0f0a commit b6ccb62

File tree

4 files changed

+78
-21
lines changed

4 files changed

+78
-21
lines changed

crates/fig_api_client/src/clients/client.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ use crate::{
4343
Error,
4444
};
4545

46+
// Limits for FileContext
47+
pub const FILE_CONTEXT_LEFT_FILE_CONTENT_MAX_LEN: usize = 10240;
48+
pub const FILE_CONTEXT_RIGHT_FILE_CONTENT_MAX_LEN: usize = 10240;
49+
pub const FILE_CONTEXT_FILE_NAME_MAX_LEN: usize = 1024;
50+
4651
mod inner {
4752
use amzn_codewhisperer_client::Client as CodewhispererClient;
4853
use amzn_consolas_client::Client as ConsolasClient;
@@ -96,7 +101,36 @@ impl Client {
96101
Ok(Self(inner::Inner::Consolas(ConsolasClient::from_conf(conf))))
97102
}
98103

99-
pub async fn generate_recommendations(&self, input: RecommendationsInput) -> Result<RecommendationsOutput, Error> {
104+
pub async fn generate_recommendations(
105+
&self,
106+
mut input: RecommendationsInput,
107+
) -> Result<RecommendationsOutput, Error> {
108+
let truncate_left = |s: String, max_len: usize| {
109+
if s.len() > max_len {
110+
s[(s.len() - max_len)..].into()
111+
} else {
112+
s
113+
}
114+
};
115+
116+
let truncate_right = |s: String, max_len: usize| {
117+
if s.len() > max_len { s[..max_len].into() } else { s }
118+
};
119+
120+
let filename = truncate_right(input.file_context.filename, FILE_CONTEXT_FILE_NAME_MAX_LEN);
121+
let left_content = truncate_left(
122+
input.file_context.left_file_content,
123+
FILE_CONTEXT_LEFT_FILE_CONTENT_MAX_LEN,
124+
);
125+
let right_content = truncate_right(
126+
input.file_context.right_file_content,
127+
FILE_CONTEXT_RIGHT_FILE_CONTENT_MAX_LEN,
128+
);
129+
130+
input.file_context.filename = filename;
131+
input.file_context.left_file_content = left_content;
132+
input.file_context.right_file_content = right_content;
133+
100134
match &self.0 {
101135
inner::Inner::Codewhisperer(client) => Ok(codewhisperer_generate_recommendation(client, input).await?),
102136
inner::Inner::Consolas(client) => Ok(consolas_generate_recommendation(client, input).await?),

crates/fig_api_client/src/clients/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,10 @@ mod client;
22
mod shared;
33
mod streaming_client;
44

5-
pub use client::Client;
5+
pub use client::{
6+
Client,
7+
FILE_CONTEXT_FILE_NAME_MAX_LEN,
8+
FILE_CONTEXT_LEFT_FILE_CONTENT_MAX_LEN,
9+
FILE_CONTEXT_RIGHT_FILE_CONTENT_MAX_LEN,
10+
};
611
pub use streaming_client::StreamingClient;

crates/fig_api_client/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
pub(crate) mod clients;
1+
pub mod clients;
22
pub(crate) mod consts;
33
pub(crate) mod credentials;
44
mod customization;
55
mod endpoints;
66
mod error;
77
pub(crate) mod interceptor;
88
pub mod model;
9-
// mod stage;
109

1110
pub use clients::{
1211
Client,

crates/figterm/src/inline/mod.rs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::time::{
1010
};
1111

1212
use fig_api_client::Client;
13+
use fig_api_client::clients::FILE_CONTEXT_LEFT_FILE_CONTENT_MAX_LEN;
1314
use fig_api_client::model::{
1415
FileContext,
1516
LanguageName,
@@ -232,7 +233,7 @@ pub async fn handle_request(
232233
},
233234
};
234235

235-
let prompt = prompt(&history, buffer);
236+
let prompt = prompt(&history, buffer).unwrap();
236237

237238
let input = RecommendationsInput {
238239
file_context: FileContext {
@@ -362,20 +363,28 @@ pub async fn handle_set_enabled(figterm_request: InlineShellCompletionSetEnabled
362363
*INLINE_ENABLED.lock().await = figterm_request.enabled;
363364
}
364365

365-
fn prompt(history: &[CommandInfo], buffer: &str) -> String {
366-
history
367-
.iter()
368-
.rev()
369-
.filter_map(|c| c.command.clone())
370-
.chain([buffer.into()])
371-
.enumerate()
372-
.fold(String::new(), |mut acc, (i, c)| {
373-
if i > 0 {
374-
acc.push('\n');
375-
}
376-
let _ = write!(acc, "{:>5} {c}", i + 1);
377-
acc
378-
})
366+
fn prompt(history: &[CommandInfo], buffer: &str) -> Option<String> {
367+
for i in (0..history.len()).rev() {
368+
let formatted_prompt = history
369+
.iter()
370+
.rev()
371+
.take(i + 1)
372+
.filter_map(|c| c.command.clone())
373+
.chain([buffer.into()])
374+
.enumerate()
375+
.fold(String::new(), |mut acc, (i, c)| {
376+
if i > 0 {
377+
acc.push('\n');
378+
}
379+
let _ = write!(acc, "{:>5} {c}", i + 1);
380+
acc
381+
});
382+
383+
if formatted_prompt.len() < FILE_CONTEXT_LEFT_FILE_CONTENT_MAX_LEN {
384+
return Some(formatted_prompt);
385+
}
386+
}
387+
None
379388
}
380389

381390
static RE_1: LazyLock<Regex> = LazyLock::new(|| Regex::new(&format!("{}\\s+.*", *HISTORY_COUNT + 1)).unwrap());
@@ -420,7 +429,7 @@ mod tests {
420429
},
421430
];
422431

423-
let prompt = prompt(&history, "echo ");
432+
let prompt = prompt(&history, "echo ").unwrap();
424433
println!("{prompt}");
425434

426435
assert_eq!(prompt, " 1 echo hello\n 2 echo world\n 3 echo ");
@@ -446,6 +455,16 @@ mod tests {
446455
);
447456
}
448457

458+
#[test]
459+
fn too_long_prompt() {
460+
let history = vec![CommandInfo {
461+
command: Some("a".repeat(FILE_CONTEXT_LEFT_FILE_CONTENT_MAX_LEN + 1)),
462+
..Default::default()
463+
}];
464+
465+
assert!(prompt(&history, "echo ").is_none());
466+
}
467+
449468
#[ignore = "not in CI"]
450469
#[tokio::test]
451470
async fn test_inline_suggestion_prompt() {
@@ -458,7 +477,7 @@ mod tests {
458477
0,
459478
)
460479
.unwrap();
461-
let prompt = prompt(&commands, "cd ");
480+
let prompt = prompt(&commands, "cd ").unwrap();
462481

463482
let client = fig_api_client::Client::new().await.unwrap();
464483
let out = client

0 commit comments

Comments
 (0)