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.
|
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
|
## Transcripts
|
||||||
|
|
||||||
Context lifecycle events are recorded as metadata:
|
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::path::PathBuf;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::context::{
|
use crate::context::{
|
||||||
|
|
@ -57,10 +58,19 @@ impl App {
|
||||||
stream: false,
|
stream: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = match self.provider.chat(request).await {
|
let timeout = Duration::from_secs(self.config.provider.request_timeout_seconds);
|
||||||
Ok(ChatResponse::Complete(response)) => response,
|
let response = match tokio::time::timeout(timeout, self.provider.chat(request)).await {
|
||||||
Ok(ChatResponse::Stream(chunks)) => chunks.concat(),
|
Err(_) => {
|
||||||
Err(error) => {
|
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());
|
self.transcript.record_error(&error.to_string());
|
||||||
return Err(error.into());
|
return Err(error.into());
|
||||||
}
|
}
|
||||||
|
|
@ -612,6 +622,20 @@ mod tests {
|
||||||
assert!(seen.lock().expect("seen lock").is_empty());
|
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]
|
#[test]
|
||||||
fn stdin_context_uses_default_provider_path() {
|
fn stdin_context_uses_default_provider_path() {
|
||||||
let mut app = App::new(test_config(), Box::new(NoopProvider));
|
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 {
|
fn test_config() -> Config {
|
||||||
Config {
|
Config {
|
||||||
provider: ProviderConfig {
|
provider: ProviderConfig {
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ impl Repl {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("waiting for provider response...");
|
||||||
match self.app.send(input).await {
|
match self.app.send(input).await {
|
||||||
Ok(response) => println!("\n{}\n", render_assistant_output(&response)),
|
Ok(response) => println!("\n{}\n", render_assistant_output(&response)),
|
||||||
Err(error) => eprintln!("request failed: {error}"),
|
Err(error) => eprintln!("request failed: {error}"),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user