mirror of
https://github.com/khodges42/exoshell.git
synced 2026-06-14 18:08:37 +00:00
Another pass for context.
This commit is contained in:
parent
8cfd968e72
commit
c084e2f3c0
|
|
@ -85,6 +85,19 @@ Before a provider request, Exoshell checks enabled context against the configure
|
|||
|
||||
Pruning is deterministic and non-mutating. Low-priority unpinned entries are selected for removal first. Pinned entries and critical-priority entries are preserved as long as possible.
|
||||
|
||||
## Provider Waiting and Timeouts
|
||||
|
||||
Provider requests are bounded by:
|
||||
|
||||
```toml
|
||||
[provider]
|
||||
request_timeout_seconds = 120
|
||||
```
|
||||
|
||||
The default is 120 seconds. Exoshell prints a waiting message before sending a provider request so slow local model inference is visible. If the timeout is exceeded, Exoshell records the timeout in the transcript and returns to the REPL.
|
||||
|
||||
Keyboard interrupt behavior is still terminal-level: interrupting the process stops the active request, and normal transcript writing happens when the REPL exits cleanly.
|
||||
|
||||
## Transcripts
|
||||
|
||||
Context lifecycle events are recorded as metadata:
|
||||
|
|
|
|||
42
src/app.rs
42
src/app.rs
|
|
@ -1,4 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::{
|
||||
|
|
@ -57,10 +58,19 @@ impl App {
|
|||
stream: false,
|
||||
};
|
||||
|
||||
let response = match self.provider.chat(request).await {
|
||||
Ok(ChatResponse::Complete(response)) => response,
|
||||
Ok(ChatResponse::Stream(chunks)) => chunks.concat(),
|
||||
Err(error) => {
|
||||
let timeout = Duration::from_secs(self.config.provider.request_timeout_seconds);
|
||||
let response = match tokio::time::timeout(timeout, self.provider.chat(request)).await {
|
||||
Err(_) => {
|
||||
let message = format!(
|
||||
"provider request timed out after {} seconds",
|
||||
self.config.provider.request_timeout_seconds
|
||||
);
|
||||
self.transcript.record_error(&message);
|
||||
return Err(AppError::Provider(ProviderError::Network(message)));
|
||||
}
|
||||
Ok(Ok(ChatResponse::Complete(response))) => response,
|
||||
Ok(Ok(ChatResponse::Stream(chunks))) => chunks.concat(),
|
||||
Ok(Err(error)) => {
|
||||
self.transcript.record_error(&error.to_string());
|
||||
return Err(error.into());
|
||||
}
|
||||
|
|
@ -612,6 +622,20 @@ mod tests {
|
|||
assert!(seen.lock().expect("seen lock").is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn provider_request_times_out() {
|
||||
let mut config = test_config();
|
||||
config.provider.request_timeout_seconds = 0;
|
||||
let mut app = App::new(config, Box::new(SlowProvider));
|
||||
|
||||
let error = app
|
||||
.send("hello".into())
|
||||
.await
|
||||
.expect_err("request should time out");
|
||||
|
||||
assert!(error.to_string().contains("timed out"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_context_uses_default_provider_path() {
|
||||
let mut app = App::new(test_config(), Box::new(NoopProvider));
|
||||
|
|
@ -647,6 +671,16 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
struct SlowProvider;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Provider for SlowProvider {
|
||||
async fn chat(&self, _request: ChatRequest) -> Result<ChatResponse, ProviderError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
Ok(ChatResponse::Complete("late".into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn test_config() -> Config {
|
||||
Config {
|
||||
provider: ProviderConfig {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ impl Repl {
|
|||
continue;
|
||||
}
|
||||
|
||||
println!("waiting for provider response...");
|
||||
match self.app.send(input).await {
|
||||
Ok(response) => println!("\n{}\n", render_assistant_output(&response)),
|
||||
Err(error) => eprintln!("request failed: {error}"),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user