Fix agent context errors and input visibility
This commit is contained in:
@@ -16161,12 +16161,11 @@ impl View for Input {
|
|||||||
ambient_agent_model.as_ref(app).should_show_status_footer()
|
ambient_agent_model.as_ref(app).should_show_status_footer()
|
||||||
});
|
});
|
||||||
|
|
||||||
if FeatureFlag::CloudMode.is_enabled() && should_show_status_footer {
|
if FeatureFlag::AgentView.is_enabled() && self.agent_view_controller.as_ref(app).is_active()
|
||||||
self.render_ambient_agent_status_footer(app)
|
|
||||||
} else if FeatureFlag::AgentView.is_enabled()
|
|
||||||
&& self.agent_view_controller.as_ref(app).is_active()
|
|
||||||
{
|
{
|
||||||
self.render_agent_input(app)
|
self.render_agent_input(app)
|
||||||
|
} else if FeatureFlag::CloudMode.is_enabled() && should_show_status_footer {
|
||||||
|
self.render_ambient_agent_status_footer(app)
|
||||||
} else if FeatureFlag::AgentView.is_enabled()
|
} else if FeatureFlag::AgentView.is_enabled()
|
||||||
&& !self.agent_view_controller.as_ref(app).is_active()
|
&& !self.agent_view_controller.as_ref(app).is_active()
|
||||||
&& !should_render_ps1_prompt(&self.model.lock(), app)
|
&& !should_render_ps1_prompt(&self.model.lock(), app)
|
||||||
|
|||||||
+19
-31
@@ -8367,21 +8367,6 @@ impl TerminalView {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In cloud agent conversations, once the shared session is ready but before the first
|
|
||||||
// agent exchange arrives, we hide the interactive input view. A non-interactive footer is
|
|
||||||
// rendered instead (see `TerminalView::render`).
|
|
||||||
if !FeatureFlag::CloudModeSetupV2.is_enabled()
|
|
||||||
&& !FeatureFlag::HandoffCloudCloud.is_enabled()
|
|
||||||
&& ambient_agent::is_cloud_agent_pre_first_exchange(
|
|
||||||
self.ambient_agent_view_model.as_ref(),
|
|
||||||
&self.agent_view_controller,
|
|
||||||
model,
|
|
||||||
app,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.has_active_init_project(app) && self.is_last_block_init_step(app) {
|
if self.has_active_init_project(app) && self.is_last_block_init_step(app) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -8417,23 +8402,28 @@ impl TerminalView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let active_ai_block = self.active_ai_block(app);
|
|
||||||
if active_ai_block.is_some_and(|ai_block| {
|
|
||||||
let ai_block = ai_block.as_ref(app);
|
|
||||||
ai_block.is_blocked_on_user_confirmation(app)
|
|
||||||
|| ai_block.has_expanded_running_commands(app)
|
|
||||||
}) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let active_command_block = model.block_list().active_block();
|
let active_command_block = model.block_list().active_block();
|
||||||
let is_active_and_long_running = active_command_block.is_active_and_long_running();
|
let is_active_and_long_running = active_command_block.is_active_and_long_running();
|
||||||
let is_oz_env_startup_command = active_command_block.is_oz_environment_startup_command();
|
let is_oz_env_startup_command = active_command_block.is_oz_environment_startup_command();
|
||||||
let is_running_in_band_command =
|
let is_running_in_band_command =
|
||||||
model.block_list().is_writing_or_executing_in_band_command();
|
model.block_list().is_writing_or_executing_in_band_command();
|
||||||
|
let has_active_long_running_agent_interaction = active_command_block
|
||||||
|
.is_agent_driving_command()
|
||||||
|
|| active_command_block.is_agent_tagged_in();
|
||||||
|
let is_agent_view_active = FeatureFlag::AgentView.is_enabled()
|
||||||
|
&& self.agent_view_controller.as_ref(app).is_active();
|
||||||
|
|
||||||
let has_active_long_running_agent_interaction =
|
let active_ai_block = self.active_ai_block(app);
|
||||||
active_command_block.is_agent_monitoring() || active_command_block.is_agent_tagged_in();
|
if !is_agent_view_active
|
||||||
|
&& !has_active_long_running_agent_interaction
|
||||||
|
&& active_ai_block.is_some_and(|ai_block| {
|
||||||
|
let ai_block = ai_block.as_ref(app);
|
||||||
|
ai_block.is_blocked_on_user_confirmation(app)
|
||||||
|
|| ai_block.has_expanded_running_commands(app)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (active_ai_block.is_none() || has_active_long_running_agent_interaction)
|
if (active_ai_block.is_none() || has_active_long_running_agent_interaction)
|
||||||
&& is_active_and_long_running
|
&& is_active_and_long_running
|
||||||
@@ -8441,11 +8431,9 @@ impl TerminalView {
|
|||||||
&& !is_running_in_band_command
|
&& !is_running_in_band_command
|
||||||
&& model.block_list().is_bootstrapped()
|
&& model.block_list().is_bootstrapped()
|
||||||
{
|
{
|
||||||
// Show the input if:
|
// Keep the agent prompt available while the agent owns, starts, or waits on
|
||||||
// * The agent is control of the active, long running block, so long as the agent is not blocked.
|
// a long-running command; hide it for user-owned commands unless tagged in.
|
||||||
// * OR the user has 'tagged in' the agent.
|
return active_command_block.is_agent_driving_command()
|
||||||
return (active_command_block.is_agent_in_control()
|
|
||||||
&& !active_command_block.is_agent_blocked())
|
|
||||||
|| active_command_block.is_agent_tagged_in();
|
|| active_command_block.is_agent_tagged_in();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ pub fn create_cloud_mode_view(
|
|||||||
|
|
||||||
/// Returns `true` when a cloud agent shared session is in any pre-first-exchange phase —
|
/// Returns `true` when a cloud agent shared session is in any pre-first-exchange phase —
|
||||||
/// either still spawning (loading screen) or running setup commands before the first
|
/// either still spawning (loading screen) or running setup commands before the first
|
||||||
/// agent turn. In this state, we hide the interactive input and render a loading footer.
|
/// agent turn.
|
||||||
pub fn is_cloud_agent_pre_first_exchange(
|
pub fn is_cloud_agent_pre_first_exchange(
|
||||||
ambient_agent_view_model: Option<&ModelHandle<AmbientAgentViewModel>>,
|
ambient_agent_view_model: Option<&ModelHandle<AmbientAgentViewModel>>,
|
||||||
agent_view_controller: &ModelHandle<AgentViewController>,
|
agent_view_controller: &ModelHandle<AgentViewController>,
|
||||||
|
|||||||
@@ -1787,6 +1787,60 @@ fn fresh_cloud_mode_setup_enters_agent_view_when_view_pending() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn legacy_cloud_mode_waiting_for_session_keeps_input_visible() {
|
||||||
|
App::test((), |mut app| async move {
|
||||||
|
initialize_app_for_terminal_view(&mut app);
|
||||||
|
let _agent_view = FeatureFlag::AgentView.override_enabled(true);
|
||||||
|
let _cloud_mode = FeatureFlag::CloudMode.override_enabled(true);
|
||||||
|
let _handoff = FeatureFlag::HandoffCloudCloud.override_enabled(false);
|
||||||
|
let _setup_v2 = FeatureFlag::CloudModeSetupV2.override_enabled(false);
|
||||||
|
|
||||||
|
let terminal = add_window_with_cloud_mode_terminal(&mut app);
|
||||||
|
|
||||||
|
terminal.update(&mut app, |view, ctx| {
|
||||||
|
view.model
|
||||||
|
.lock()
|
||||||
|
.set_shared_session_status(SharedSessionStatus::ViewPending);
|
||||||
|
view.enter_ambient_agent_setup(Some("write the tests".to_string()), ctx);
|
||||||
|
view.ambient_agent_view_model()
|
||||||
|
.expect("cloud mode terminal should have ambient model")
|
||||||
|
.update(ctx, |model, ctx| {
|
||||||
|
model.spawn_agent_with_request(
|
||||||
|
SpawnAgentRequest {
|
||||||
|
prompt: Some("write the tests".to_string()),
|
||||||
|
mode: UserQueryMode::Normal,
|
||||||
|
config: None,
|
||||||
|
title: None,
|
||||||
|
team: None,
|
||||||
|
agent_identity_uid: None,
|
||||||
|
skill: None,
|
||||||
|
attachments: vec![],
|
||||||
|
interactive: None,
|
||||||
|
parent_run_id: None,
|
||||||
|
runtime_skills: vec![],
|
||||||
|
referenced_attachments: vec![],
|
||||||
|
conversation_id: None,
|
||||||
|
initial_snapshot_token: None,
|
||||||
|
snapshot_disabled: None,
|
||||||
|
orchestration_handoff: None,
|
||||||
|
},
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let model = view.model.lock();
|
||||||
|
assert!(ambient_agent::is_cloud_agent_pre_first_exchange(
|
||||||
|
view.ambient_agent_view_model.as_ref(),
|
||||||
|
&view.agent_view_controller,
|
||||||
|
&model,
|
||||||
|
ctx,
|
||||||
|
));
|
||||||
|
assert!(view.is_input_box_visible(&model, ctx));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn shared_third_party_viewer_sync_enters_agent_view_and_retags_existing_block() {
|
fn shared_third_party_viewer_sync_enters_agent_view_and_retags_existing_block() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |mut app| async move {
|
||||||
@@ -5486,6 +5540,57 @@ fn inline_agent_view_exits_when_tagged_in_long_running_command_is_tagged_out() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn agent_view_keeps_input_visible_for_agent_requested_and_blocked_commands() {
|
||||||
|
App::test((), |mut app| async move {
|
||||||
|
initialize_app_for_terminal_view(&mut app);
|
||||||
|
let _agent_view = FeatureFlag::AgentView.override_enabled(true);
|
||||||
|
|
||||||
|
let terminal = add_window_with_terminal(&mut app, None);
|
||||||
|
|
||||||
|
terminal.update(&mut app, |view, ctx| {
|
||||||
|
let conversation_id = view.agent_view_controller().update(ctx, |controller, ctx| {
|
||||||
|
controller
|
||||||
|
.try_enter_agent_view(
|
||||||
|
None,
|
||||||
|
AgentViewEntryOrigin::Input {
|
||||||
|
was_prompt_autodetected: false,
|
||||||
|
},
|
||||||
|
ctx,
|
||||||
|
)
|
||||||
|
.expect("should enter agent view")
|
||||||
|
});
|
||||||
|
bootstrap_with_long_running_block(view);
|
||||||
|
set_active_block_agent_driving(view, conversation_id);
|
||||||
|
|
||||||
|
{
|
||||||
|
let model = view.model.lock();
|
||||||
|
let active_block = model.block_list().active_block();
|
||||||
|
assert!(active_block.is_agent_driving_command());
|
||||||
|
assert!(view.is_input_box_visible(&model, ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
let task_id = TaskId::new("test-cli-subagent".to_owned());
|
||||||
|
view.model
|
||||||
|
.lock()
|
||||||
|
.block_list_mut()
|
||||||
|
.active_block_mut()
|
||||||
|
.set_agent_interaction_mode_for_agent_monitored_command(&task_id, conversation_id)
|
||||||
|
.expect("agent-requested command should become monitored");
|
||||||
|
view.model
|
||||||
|
.lock()
|
||||||
|
.block_list_mut()
|
||||||
|
.active_block_mut()
|
||||||
|
.update_is_agent_blocked(true);
|
||||||
|
|
||||||
|
let model = view.model.lock();
|
||||||
|
let active_block = model.block_list().active_block();
|
||||||
|
assert!(active_block.is_agent_blocked());
|
||||||
|
assert!(view.is_input_box_visible(&model, ctx));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ctrl_c_after_stop_takeover_cancels_conversation() {
|
fn ctrl_c_after_stop_takeover_cancels_conversation() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |mut app| async move {
|
||||||
|
|||||||
@@ -22,16 +22,23 @@ where
|
|||||||
let control_future = initial_control.receive().fuse();
|
let control_future = initial_control.receive().fuse();
|
||||||
futures::pin_mut!(stream_future, control_future);
|
futures::pin_mut!(stream_future, control_future);
|
||||||
|
|
||||||
let mut rig_stream = futures::select_biased! {
|
let stream_result = futures::select_biased! {
|
||||||
command = control_future => match command {
|
command = control_future => match command {
|
||||||
Ok(TurnCommand::Cancel) => {
|
Ok(TurnCommand::Cancel) => {
|
||||||
return Ok(stopped_before_stream(runtime_request_id));
|
return Ok(stopped_before_stream(runtime_request_id));
|
||||||
}
|
}
|
||||||
Ok(TurnCommand::Steer { .. }) | Err(_) => {
|
Ok(TurnCommand::Steer { .. }) | Err(_) => stream_future.await,
|
||||||
stream_future.await.map_err(map_completion_error)?
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
result = stream_future => result.map_err(map_completion_error)?,
|
result = stream_future => result,
|
||||||
|
};
|
||||||
|
let mut rig_stream = match stream_result {
|
||||||
|
Ok(stream) => stream,
|
||||||
|
Err(error) => {
|
||||||
|
if let Some(reason) = completion_error_stop_reason(&error) {
|
||||||
|
return Ok(stopped_with_reason(runtime_request_id, reason));
|
||||||
|
}
|
||||||
|
return Err(map_completion_error(error));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let events = async_stream::stream! {
|
let events = async_stream::stream! {
|
||||||
@@ -156,16 +163,23 @@ where
|
|||||||
let control_future = initial_control.receive().fuse();
|
let control_future = initial_control.receive().fuse();
|
||||||
futures::pin_mut!(completion_future, control_future);
|
futures::pin_mut!(completion_future, control_future);
|
||||||
|
|
||||||
let response = futures::select_biased! {
|
let completion_result = futures::select_biased! {
|
||||||
command = control_future => match command {
|
command = control_future => match command {
|
||||||
Ok(TurnCommand::Cancel) => {
|
Ok(TurnCommand::Cancel) => {
|
||||||
return Ok(stopped_before_stream(runtime_request_id));
|
return Ok(stopped_before_stream(runtime_request_id));
|
||||||
}
|
}
|
||||||
Ok(TurnCommand::Steer { .. }) | Err(_) => {
|
Ok(TurnCommand::Steer { .. }) | Err(_) => completion_future.await,
|
||||||
completion_future.await.map_err(map_completion_error)?
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
result = completion_future => result.map_err(map_completion_error)?,
|
result = completion_future => result,
|
||||||
|
};
|
||||||
|
let response = match completion_result {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(error) => {
|
||||||
|
if let Some(reason) = completion_error_stop_reason(&error) {
|
||||||
|
return Ok(stopped_with_reason(runtime_request_id, reason));
|
||||||
|
}
|
||||||
|
return Err(map_completion_error(error));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let events = async_stream::stream! {
|
let events = async_stream::stream! {
|
||||||
@@ -230,11 +244,13 @@ fn domain_tool_call(tool_call: rig_core::message::ToolCall) -> ToolCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn stopped_before_stream(runtime_request_id: String) -> AgentEventStream {
|
fn stopped_before_stream(runtime_request_id: String) -> AgentEventStream {
|
||||||
|
stopped_with_reason(runtime_request_id, StopReason::Cancelled)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stopped_with_reason(runtime_request_id: String, reason: StopReason) -> AgentEventStream {
|
||||||
Box::pin(futures::stream::iter([
|
Box::pin(futures::stream::iter([
|
||||||
Ok(AgentEvent::TurnStarted { runtime_request_id }),
|
Ok(AgentEvent::TurnStarted { runtime_request_id }),
|
||||||
Ok(AgentEvent::TurnStopped {
|
Ok(AgentEvent::TurnStopped { reason }),
|
||||||
reason: StopReason::Cancelled,
|
|
||||||
}),
|
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +264,10 @@ pub(crate) fn map_usage(usage: rig_core::completion::Usage) -> Usage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn completion_error_stop_reason(error: &CompletionError) -> Option<StopReason> {
|
pub(crate) fn completion_error_stop_reason(error: &CompletionError) -> Option<StopReason> {
|
||||||
|
if completion_error_indicates_context_window_exceeded(error) {
|
||||||
|
return Some(StopReason::ContextWindowExceeded);
|
||||||
|
}
|
||||||
|
|
||||||
match error {
|
match error {
|
||||||
// rig-bedrock 0.40 currently surfaces Bedrock's MaxTokens stop as a
|
// rig-bedrock 0.40 currently surfaces Bedrock's MaxTokens stop as a
|
||||||
// provider error. Normalize it here so the UI sees the same semantic
|
// provider error. Normalize it here so the UI sees the same semantic
|
||||||
@@ -259,28 +279,74 @@ pub(crate) fn completion_error_stop_reason(error: &CompletionError) -> Option<St
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn completion_error_indicates_context_window_exceeded(error: &CompletionError) -> bool {
|
||||||
|
if let Ok(Some(value)) = error.provider_response_json()
|
||||||
|
&& json_value_indicates_context_window_exceeded(&value)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
error
|
||||||
|
.provider_response_body()
|
||||||
|
.is_some_and(text_indicates_context_window_exceeded)
|
||||||
|
|| text_indicates_context_window_exceeded(&error.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_value_indicates_context_window_exceeded(value: &serde_json::Value) -> bool {
|
||||||
|
match value {
|
||||||
|
serde_json::Value::String(text) => text_indicates_context_window_exceeded(text),
|
||||||
|
serde_json::Value::Array(values) => values
|
||||||
|
.iter()
|
||||||
|
.any(json_value_indicates_context_window_exceeded),
|
||||||
|
serde_json::Value::Object(map) => map
|
||||||
|
.values()
|
||||||
|
.any(json_value_indicates_context_window_exceeded),
|
||||||
|
serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::Number(_) => {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_indicates_context_window_exceeded(text: &str) -> bool {
|
||||||
|
let normalized = text.to_ascii_lowercase();
|
||||||
|
normalized.contains("modelcontextwindowexceeded")
|
||||||
|
|| normalized.contains("context_length_exceeded")
|
||||||
|
|| normalized.contains("context window")
|
||||||
|
|| normalized.contains("context length")
|
||||||
|
|| normalized.contains("too many tokens")
|
||||||
|
|| normalized.contains("maximum context")
|
||||||
|
|| normalized.contains("input is too long")
|
||||||
|
|| (normalized.contains("input exceeds") && normalized.contains("context"))
|
||||||
|
|| normalized.contains("exceeds the context")
|
||||||
|
}
|
||||||
|
|
||||||
fn map_completion_error(error: CompletionError) -> AgentError {
|
fn map_completion_error(error: CompletionError) -> AgentError {
|
||||||
|
let is_context_window_exceeded = completion_error_indicates_context_window_exceeded(&error);
|
||||||
let status = error
|
let status = error
|
||||||
.provider_response_status()
|
.provider_response_status()
|
||||||
.map(|status| status.as_u16());
|
.map(|status| status.as_u16());
|
||||||
let kind = match status {
|
let kind = if is_context_window_exceeded {
|
||||||
Some(401 | 403) => AgentErrorKind::Authentication,
|
AgentErrorKind::ContextWindowExceeded
|
||||||
Some(429) => AgentErrorKind::RateLimited,
|
} else {
|
||||||
Some(400 | 404 | 413 | 422) => AgentErrorKind::InvalidRequest,
|
match status {
|
||||||
Some(500..=599) => AgentErrorKind::Provider,
|
Some(401 | 403) => AgentErrorKind::Authentication,
|
||||||
Some(_) => AgentErrorKind::Provider,
|
Some(429) => AgentErrorKind::RateLimited,
|
||||||
None => match &error {
|
Some(400 | 404 | 413 | 422) => AgentErrorKind::InvalidRequest,
|
||||||
CompletionError::HttpError(_)
|
Some(500..=599) => AgentErrorKind::Provider,
|
||||||
| CompletionError::UrlError(_)
|
Some(_) => AgentErrorKind::Provider,
|
||||||
| CompletionError::RequestError(_) => AgentErrorKind::Transport,
|
None => match &error {
|
||||||
CompletionError::JsonError(_) | CompletionError::ResponseError(_) => {
|
CompletionError::HttpError(_)
|
||||||
AgentErrorKind::Protocol
|
| CompletionError::UrlError(_)
|
||||||
}
|
| CompletionError::RequestError(_) => AgentErrorKind::Transport,
|
||||||
CompletionError::ProviderError(_) | CompletionError::ProviderResponse(_) => {
|
CompletionError::JsonError(_) | CompletionError::ResponseError(_) => {
|
||||||
AgentErrorKind::Provider
|
AgentErrorKind::Protocol
|
||||||
}
|
}
|
||||||
_ => AgentErrorKind::Provider,
|
CompletionError::ProviderError(_) | CompletionError::ProviderResponse(_) => {
|
||||||
},
|
AgentErrorKind::Provider
|
||||||
|
}
|
||||||
|
_ => AgentErrorKind::Provider,
|
||||||
|
},
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let mut mapped = AgentError::new(kind, error.to_string());
|
let mut mapped = AgentError::new(kind, error.to_string());
|
||||||
mapped.recoverable = matches!(
|
mapped.recoverable = matches!(
|
||||||
@@ -292,7 +358,10 @@ fn map_completion_error(error: CompletionError) -> AgentError {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::domain_tool_call;
|
use galaxy_agent_core::{AgentErrorKind, StopReason};
|
||||||
|
use rig_core::completion::CompletionError;
|
||||||
|
|
||||||
|
use super::{completion_error_stop_reason, domain_tool_call, map_completion_error};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn domain_tool_call_prefers_responses_call_id() {
|
fn domain_tool_call_prefers_responses_call_id() {
|
||||||
@@ -326,4 +395,37 @@ mod tests {
|
|||||||
assert_eq!(call.id, "fc_item_123");
|
assert_eq!(call.id, "fc_item_123");
|
||||||
assert_eq!(call.name, "read_files");
|
assert_eq!(call.name, "read_files");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn provider_context_window_error_maps_to_semantic_stop_reason_and_kind() {
|
||||||
|
let status = rig_core::http_client::Response::builder()
|
||||||
|
.status(400)
|
||||||
|
.body(())
|
||||||
|
.unwrap()
|
||||||
|
.status();
|
||||||
|
let error = CompletionError::from_http_response(
|
||||||
|
status,
|
||||||
|
r#"{"error":{"message":"Your input exceeds the context window of this model. Please adjust your input and try again.","code":"400"}}"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
completion_error_stop_reason(&error),
|
||||||
|
Some(StopReason::ContextWindowExceeded)
|
||||||
|
);
|
||||||
|
let mapped = map_completion_error(error);
|
||||||
|
assert_eq!(mapped.kind, AgentErrorKind::ContextWindowExceeded);
|
||||||
|
assert!(!mapped.recoverable);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn provider_context_length_string_maps_to_semantic_stop_reason() {
|
||||||
|
let error = CompletionError::ProviderError(
|
||||||
|
"context_length_exceeded: maximum context length is 128000 tokens".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
completion_error_stop_reason(&error),
|
||||||
|
Some(StopReason::ContextWindowExceeded)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user