Prevent provider follow-up setup panics
This commit is contained in:
@@ -4800,39 +4800,54 @@ impl BlocklistAIController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
history_model.update(ctx, |history_model, ctx| {
|
let update_result = history_model.update(ctx, |history_model, ctx| {
|
||||||
match history_model.update_conversation_for_new_request_input(
|
history_model.update_conversation_for_new_request_input(
|
||||||
request_input,
|
request_input,
|
||||||
response_stream_id.clone(),
|
response_stream_id.clone(),
|
||||||
self.terminal_surface_id,
|
self.terminal_surface_id,
|
||||||
ctx,
|
ctx,
|
||||||
) {
|
)
|
||||||
Ok(_) => {
|
});
|
||||||
|
if let Err(error) = update_result {
|
||||||
|
let message = format!("failed to create AI response exchange: {error:?}");
|
||||||
|
self.cleanup_failed_response_stream_setup(
|
||||||
|
conversation_data.id,
|
||||||
|
&response_stream_id,
|
||||||
|
&response_stream,
|
||||||
|
message.clone(),
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
return Err(anyhow!(message));
|
||||||
|
}
|
||||||
|
history_model.update(ctx, |history_model, ctx| {
|
||||||
history_model.update_conversation_status(
|
history_model.update_conversation_status(
|
||||||
self.terminal_surface_id,
|
self.terminal_surface_id,
|
||||||
conversation_data.id,
|
conversation_data.id,
|
||||||
ConversationStatus::InProgress,
|
ConversationStatus::InProgress,
|
||||||
ctx,
|
ctx,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("Failed to push new exchange to AI conversation: {e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let provider_projection_target = if provider_configs.is_some() {
|
let provider_projection_target = if provider_configs.is_some() {
|
||||||
let (task_id, exchange_id) = history_model
|
let target = history_model
|
||||||
.as_ref(ctx)
|
.as_ref(ctx)
|
||||||
.conversation(&conversation_data.id)
|
.conversation(&conversation_data.id)
|
||||||
.and_then(|conversation| {
|
.and_then(|conversation| {
|
||||||
conversation.provider_projection_target(&response_stream_id)
|
conversation.provider_projection_target(&response_stream_id)
|
||||||
})
|
});
|
||||||
.ok_or_else(|| {
|
let Some((task_id, exchange_id)) = target else {
|
||||||
anyhow!(
|
let message =
|
||||||
"direct-provider response stream does not have exactly one projection target"
|
"direct-provider response stream does not have exactly one projection target"
|
||||||
)
|
.to_owned();
|
||||||
})?;
|
self.cleanup_failed_response_stream_setup(
|
||||||
|
conversation_data.id,
|
||||||
|
&response_stream_id,
|
||||||
|
&response_stream,
|
||||||
|
message.clone(),
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
return Err(anyhow!(message));
|
||||||
|
};
|
||||||
Some(ProviderProjectionTarget {
|
Some(ProviderProjectionTarget {
|
||||||
task_id,
|
task_id,
|
||||||
exchange_id,
|
exchange_id,
|
||||||
@@ -4864,20 +4879,43 @@ impl BlocklistAIController {
|
|||||||
conversation_data.id,
|
conversation_data.id,
|
||||||
response_stream_id.as_str()
|
response_stream_id.as_str()
|
||||||
));
|
));
|
||||||
let root_task_id = history_model
|
let Some(root_task_id) = history_model
|
||||||
.as_ref(ctx)
|
.as_ref(ctx)
|
||||||
.conversation(&conversation_data.id)
|
.conversation(&conversation_data.id)
|
||||||
.expect("conversation exists while starting provider run")
|
.map(|conversation| conversation.get_root_task_id().clone())
|
||||||
.get_root_task_id()
|
else {
|
||||||
.clone();
|
let message = format!(
|
||||||
|
"conversation {:?} disappeared while starting provider run",
|
||||||
|
conversation_data.id
|
||||||
|
);
|
||||||
|
self.cleanup_failed_response_stream_setup(
|
||||||
|
conversation_data.id,
|
||||||
|
&response_stream_id,
|
||||||
|
&response_stream,
|
||||||
|
message.clone(),
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
return Err(anyhow!(message));
|
||||||
|
};
|
||||||
|
let Some(projection_target) = provider_projection_target else {
|
||||||
|
let message =
|
||||||
|
"direct-provider response stream is missing its projection target".to_owned();
|
||||||
|
self.cleanup_failed_response_stream_setup(
|
||||||
|
conversation_data.id,
|
||||||
|
&response_stream_id,
|
||||||
|
&response_stream,
|
||||||
|
message.clone(),
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
return Err(anyhow!(message));
|
||||||
|
};
|
||||||
let slot = ActiveProviderRunSlot {
|
let slot = ActiveProviderRunSlot {
|
||||||
stream_id: response_stream_id.clone(),
|
stream_id: response_stream_id.clone(),
|
||||||
response_stream,
|
response_stream,
|
||||||
did_input_contain_user_query: input_contains_user_query,
|
did_input_contain_user_query: input_contains_user_query,
|
||||||
run_id: provider_run_id,
|
run_id: provider_run_id,
|
||||||
root_task_id,
|
root_task_id,
|
||||||
projection_target: provider_projection_target
|
projection_target,
|
||||||
.expect("provider projection target was validated"),
|
|
||||||
run: None,
|
run: None,
|
||||||
checkpoint: None,
|
checkpoint: None,
|
||||||
turn_control: None,
|
turn_control: None,
|
||||||
@@ -4964,6 +5002,35 @@ impl BlocklistAIController {
|
|||||||
Ok((conversation_data.id, response_stream_id))
|
Ok((conversation_data.id, response_stream_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cleanup_failed_response_stream_setup(
|
||||||
|
&self,
|
||||||
|
conversation_id: AIConversationId,
|
||||||
|
stream_id: &ResponseStreamId,
|
||||||
|
response_stream: &ModelHandle<ResponseStream>,
|
||||||
|
message: String,
|
||||||
|
ctx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
BlocklistAIHistoryModel::handle(ctx).update(ctx, |history_model, ctx| {
|
||||||
|
history_model.mark_response_stream_completed_with_error(
|
||||||
|
RenderableAIError::Other {
|
||||||
|
error_message: message,
|
||||||
|
will_attempt_resume: false,
|
||||||
|
waiting_for_network: false,
|
||||||
|
is_user_error: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
stream_id,
|
||||||
|
conversation_id,
|
||||||
|
self.terminal_surface_id,
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
if let Some(conversation) = history_model.conversation_mut(&conversation_id) {
|
||||||
|
conversation.cleanup_completed_response_stream(stream_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.unsubscribe_from_model(response_stream);
|
||||||
|
}
|
||||||
|
|
||||||
fn provider_generation_is_terminalizing_for_follow_up(
|
fn provider_generation_is_terminalizing_for_follow_up(
|
||||||
&self,
|
&self,
|
||||||
conversation_id: AIConversationId,
|
conversation_id: AIConversationId,
|
||||||
|
|||||||
@@ -601,10 +601,10 @@ impl ProviderRun {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProviderRunState::AwaitingTools { batch } => {
|
ProviderRunState::AwaitingTools { batch } => {
|
||||||
self.validate_post_model_phase()?;
|
|
||||||
if batch.calls.is_empty() {
|
if batch.calls.is_empty() {
|
||||||
return Err(invalid("pending tool batch is empty".to_string()));
|
return Err(invalid("pending tool batch is empty".to_string()));
|
||||||
}
|
}
|
||||||
|
self.validate_post_model_phase()?;
|
||||||
let mut call_ids = HashSet::new();
|
let mut call_ids = HashSet::new();
|
||||||
for pending in &batch.calls {
|
for pending in &batch.calls {
|
||||||
if pending.call.id.is_empty() {
|
if pending.call.id.is_empty() {
|
||||||
|
|||||||
Reference in New Issue
Block a user