Skip to content

Commit 7ebf3b0

Browse files
feat: add auto compaction, fix/refactor conversation state handling (#1275)
1 parent 8e3c5b5 commit 7ebf3b0

File tree

20 files changed

+1873
-1126
lines changed

20 files changed

+1873
-1126
lines changed

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/fig_api_client/src/clients/streaming_client.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ impl StreamingClient {
9898
}
9999

100100
pub async fn send_message(&self, conversation_state: ConversationState) -> Result<SendMessageOutput, Error> {
101-
debug!("Sending conversation: {:?}", conversation_state);
101+
debug!("Sending conversation: {:#?}", conversation_state);
102102
let ConversationState {
103103
conversation_id,
104104
user_input_message,
@@ -132,8 +132,15 @@ impl StreamingClient {
132132
Ok(resp) => Ok(SendMessageOutput::Codewhisperer(resp)),
133133
Err(e) => {
134134
let is_quota_breach = e.raw_response().is_some_and(|resp| resp.status().as_u16() == 429);
135+
let is_context_window_overflow = e.as_service_error().is_some_and(|err| {
136+
matches!(err, err if err.meta().code() == Some("ValidationException")
137+
&& err.meta().message() == Some("Input is too long."))
138+
});
139+
135140
if is_quota_breach {
136141
Err(Error::QuotaBreach("quota has reached its limit"))
142+
} else if is_context_window_overflow {
143+
Err(Error::ContextWindowOverflow)
137144
} else {
138145
Err(e.into())
139146
}

crates/fig_api_client/src/error.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use amzn_codewhisperer_client::operation::generate_completions::GenerateCompletionsError;
22
use amzn_codewhisperer_client::operation::list_available_customizations::ListAvailableCustomizationsError;
3-
use amzn_codewhisperer_streaming_client::operation::generate_assistant_response::GenerateAssistantResponseError;
3+
pub use amzn_codewhisperer_streaming_client::operation::generate_assistant_response::GenerateAssistantResponseError;
44
// use amzn_codewhisperer_streaming_client::operation::send_message::SendMessageError as
55
// CodewhispererSendMessageError;
66
use amzn_codewhisperer_streaming_client::types::error::ChatResponseStreamError as CodewhispererChatResponseStreamError;
@@ -10,7 +10,7 @@ use amzn_qdeveloper_streaming_client::operation::send_message::SendMessageError
1010
use amzn_qdeveloper_streaming_client::types::error::ChatResponseStreamError as QDeveloperChatResponseStreamError;
1111
use aws_credential_types::provider::error::CredentialsError;
1212
use aws_smithy_runtime_api::client::orchestrator::HttpResponse;
13-
use aws_smithy_runtime_api::client::result::SdkError;
13+
pub use aws_smithy_runtime_api::client::result::SdkError;
1414
use aws_smithy_types::event_stream::RawMessage;
1515
use fig_aws_common::SdkErrorDisplay;
1616
use thiserror::Error;
@@ -48,6 +48,14 @@ pub enum Error {
4848
#[error("quota has reached its limit")]
4949
QuotaBreach(&'static str),
5050

51+
/// Returned from the backend when the user input is too large to fit within the model context
52+
/// window.
53+
///
54+
/// Note that we currently do not receive token usage information regarding how large the
55+
/// context window is.
56+
#[error("the context window has overflowed")]
57+
ContextWindowOverflow,
58+
5159
#[error(transparent)]
5260
SmithyBuild(#[from] aws_smithy_types::error::operation::BuildError),
5361

@@ -71,6 +79,7 @@ impl Error {
7179
| Error::QDeveloperChatResponseStream(_)
7280
| Error::SmithyBuild(_)
7381
| Error::UnsupportedConsolas(_)
82+
| Error::ContextWindowOverflow
7483
| Error::QuotaBreach(_) => false,
7584
}
7685
}
@@ -84,6 +93,7 @@ impl Error {
8493
Error::ListAvailableServices(e) => e.as_service_error().is_some(),
8594
Error::CodewhispererGenerateAssistantResponse(e) => e.as_service_error().is_some(),
8695
Error::QDeveloperSendMessage(e) => e.as_service_error().is_some(),
96+
Error::ContextWindowOverflow => true,
8797
Error::CodewhispererChatResponseStream(_)
8898
| Error::QDeveloperChatResponseStream(_)
8999
| Error::SmithyBuild(_)

crates/fig_api_client/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pub(crate) mod consts;
33
pub(crate) mod credentials;
44
mod customization;
55
mod endpoints;
6-
mod error;
6+
pub mod error;
77
pub(crate) mod interceptor;
88
pub mod model;
99

crates/fig_api_client/src/model.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
use aws_smithy_types::Document;
2+
use serde::{
3+
Deserialize,
4+
Serialize,
5+
};
26

37
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
48
#[serde(rename_all = "camelCase")]
@@ -284,7 +288,7 @@ impl From<ToolResultContentBlock> for amzn_qdeveloper_streaming_client::types::T
284288
}
285289
}
286290

287-
#[derive(Debug, Clone)]
291+
#[derive(Debug, Clone, Serialize, Deserialize)]
288292
pub enum ToolResultStatus {
289293
Error,
290294
Success,
@@ -497,7 +501,7 @@ impl From<amzn_qdeveloper_streaming_client::types::ChatResponseStream> for ChatR
497501
}
498502
}
499503

500-
#[derive(Debug, Clone, Default)]
504+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
501505
pub struct EnvState {
502506
pub operating_system: Option<String>,
503507
pub current_working_directory: Option<String>,
@@ -534,7 +538,7 @@ impl From<EnvState> for amzn_qdeveloper_streaming_client::types::EnvState {
534538
}
535539
}
536540

537-
#[derive(Debug, Clone)]
541+
#[derive(Debug, Clone, Serialize, Deserialize)]
538542
pub struct EnvironmentVariable {
539543
pub key: String,
540544
pub value: String,
@@ -598,9 +602,10 @@ impl From<ShellHistoryEntry> for amzn_qdeveloper_streaming_client::types::ShellH
598602
}
599603
}
600604

601-
#[derive(Debug, Clone)]
605+
#[derive(Debug, Clone, Serialize, Deserialize)]
602606
pub struct ShellState {
603607
pub shell_name: String,
608+
#[serde(skip)]
604609
pub shell_history: Option<Vec<ShellHistoryEntry>>,
605610
}
606611

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use super::token_counter::TokenCounter;
2+
3+
// These limits are the internal undocumented values from the service for each item
4+
5+
pub const MAX_CURRENT_WORKING_DIRECTORY_LEN: usize = 256;
6+
7+
/// Limit to send the number of messages as part of chat.
8+
pub const MAX_CONVERSATION_STATE_HISTORY_LEN: usize = 250;
9+
10+
pub const MAX_TOOL_RESPONSE_SIZE: usize = 800_000;
11+
12+
/// TODO: Use this to gracefully handle user message sizes.
13+
#[allow(dead_code)]
14+
pub const MAX_USER_MESSAGE_SIZE: usize = 600_000;
15+
16+
/// In tokens
17+
pub const CONTEXT_WINDOW_SIZE: usize = 200_000;
18+
19+
pub const MAX_CHARS: usize = TokenCounter::token_to_chars(CONTEXT_WINDOW_SIZE); // Character-based warning threshold

crates/q_cli/src/cli/chat/context.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use serde::{
1818
Deserialize,
1919
Serialize,
2020
};
21+
use tracing::debug;
2122

2223
use super::hooks::{
2324
Hook,
@@ -553,7 +554,7 @@ fn profile_dir_path(ctx: &Context, profile_name: &str) -> Result<PathBuf> {
553554
}
554555

555556
/// Path to the context config file for `profile_name`.
556-
fn profile_context_path(ctx: &Context, profile_name: &str) -> Result<PathBuf> {
557+
pub fn profile_context_path(ctx: &Context, profile_name: &str) -> Result<PathBuf> {
557558
Ok(directories::chat_profiles_dir(ctx)?
558559
.join(profile_name)
559560
.join("context.json"))
@@ -564,6 +565,7 @@ fn profile_context_path(ctx: &Context, profile_name: &str) -> Result<PathBuf> {
564565
/// If the global configuration file doesn't exist, returns a default configuration.
565566
async fn load_global_config(ctx: &Context) -> Result<ContextConfig> {
566567
let global_path = directories::chat_global_context_path(&ctx)?;
568+
debug!(?global_path, "loading profile config");
567569
if ctx.fs().exists(&global_path) {
568570
let contents = ctx.fs().read_to_string(&global_path).await?;
569571
let config: ContextConfig =
@@ -587,6 +589,7 @@ async fn load_global_config(ctx: &Context) -> Result<ContextConfig> {
587589
/// If the profile configuration file doesn't exist, creates a default configuration.
588590
async fn load_profile_config(ctx: &Context, profile_name: &str) -> Result<ContextConfig> {
589591
let profile_path = profile_context_path(ctx, profile_name)?;
592+
debug!(?profile_path, "loading profile config");
590593
if ctx.fs().exists(&profile_path) {
591594
let contents = ctx.fs().read_to_string(&profile_path).await?;
592595
let config: ContextConfig =

0 commit comments

Comments
 (0)