Skip to content

Commit 67eae89

Browse files
committed
feat(inference): add attributed debug logging for inference.local
1 parent dcd9912 commit 67eae89

13 files changed

Lines changed: 791 additions & 57 deletions

File tree

‎Cargo.lock‎

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

‎architecture/inference-routing.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@ Before forwarding inference requests, the proxy strips sensitive and hop-by-hop
180180
- **Request**: `authorization`, `x-api-key`, `host`, `content-length`, and hop-by-hop headers (`connection`, `keep-alive`, `proxy-authenticate`, `proxy-authorization`, `proxy-connection`, `te`, `trailer`, `transfer-encoding`, `upgrade`).
181181
- **Response**: `content-length` and hop-by-hop headers.
182182

183+
### Internal debug capture
184+
185+
For Milestone 1 attribution work, the sandbox proxy supports an internal append-only JSONL debug log for `inference.local` traffic.
186+
187+
- Enable it by setting `OPENSHELL_INFERENCE_DEBUG_LOG=/absolute/path/to/inference-debug.jsonl` in the sandbox runtime environment before the proxy starts.
188+
- Each parsed `inference.local` request appends one JSON object with process attribution (`pid`, binary path, ancestor binaries, cmdline paths), request metadata, selected route metadata, sanitized headers, and capped request/response body capture.
189+
- Request and response bodies are captured up to 64 KiB each. The record includes total byte counts plus explicit truncation flags and captured-byte counts when the body exceeds that cap.
190+
- Header logging is separate from forwarding logic. Auth headers are stripped and auth-like values such as cookies or token-bearing headers are redacted before they reach the JSONL file.
191+
183192
### Response streaming
184193

185194
The router supports two response modes:

‎crates/openshell-router/src/backend.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ mod tests {
411411
name: "inference.local".to_string(),
412412
endpoint: endpoint.to_string(),
413413
model: "test-model".to_string(),
414+
provider_type: None,
414415
api_key: "sk-test".to_string(),
415416
protocols: protocols.iter().map(|p| (*p).to_string()).collect(),
416417
auth,

‎crates/openshell-router/src/config.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub struct ResolvedRoute {
3939
pub name: String,
4040
pub endpoint: String,
4141
pub model: String,
42+
pub provider_type: Option<String>,
4243
pub api_key: String,
4344
pub protocols: Vec<String>,
4445
/// How to inject the API key on outgoing requests.
@@ -53,6 +54,7 @@ impl std::fmt::Debug for ResolvedRoute {
5354
.field("name", &self.name)
5455
.field("endpoint", &self.endpoint)
5556
.field("model", &self.model)
57+
.field("provider_type", &self.provider_type)
5658
.field("api_key", &"[REDACTED]")
5759
.field("protocols", &self.protocols)
5860
.field("auth", &self.auth)
@@ -125,6 +127,7 @@ impl RouteConfig {
125127
name: self.name.clone(),
126128
endpoint: self.endpoint.clone(),
127129
model: self.model.clone(),
130+
provider_type: self.provider_type.clone(),
128131
api_key: self.resolve_api_key()?,
129132
protocols,
130133
auth,
@@ -252,6 +255,7 @@ routes:
252255
name: "test".to_string(),
253256
endpoint: "https://api.example.com/v1".to_string(),
254257
model: "test-model".to_string(),
258+
provider_type: Some("openai".to_string()),
255259
api_key: "sk-super-secret-key-12345".to_string(),
256260
protocols: vec!["openai_chat_completions".to_string()],
257261
auth: AuthHeader::Bearer,

‎crates/openshell-router/src/mock.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ mod tests {
127127
name: "test".to_string(),
128128
endpoint: endpoint.to_string(),
129129
model: model.to_string(),
130+
provider_type: None,
130131
api_key: "key".to_string(),
131132
protocols: protocols.iter().map(ToString::to_string).collect(),
132133
auth: crate::config::AuthHeader::Bearer,

‎crates/openshell-router/tests/backend_integration.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ fn mock_candidates(base_url: &str) -> Vec<ResolvedRoute> {
1111
name: "inference.local".to_string(),
1212
endpoint: base_url.to_string(),
1313
model: "meta/llama-3.1-8b-instruct".to_string(),
14+
provider_type: Some("openai".to_string()),
1415
api_key: "test-api-key".to_string(),
1516
protocols: vec!["openai_chat_completions".to_string()],
1617
auth: AuthHeader::Bearer,
@@ -113,6 +114,7 @@ async fn proxy_no_compatible_route_returns_error() {
113114
name: "inference.local".to_string(),
114115
endpoint: "http://localhost:1234".to_string(),
115116
model: "test".to_string(),
117+
provider_type: None,
116118
api_key: "key".to_string(),
117119
protocols: vec!["anthropic_messages".to_string()],
118120
auth: AuthHeader::Custom("x-api-key"),
@@ -174,6 +176,7 @@ async fn proxy_mock_route_returns_canned_response() {
174176
name: "inference.local".to_string(),
175177
endpoint: "mock://test".to_string(),
176178
model: "mock/test-model".to_string(),
179+
provider_type: None,
177180
api_key: "unused".to_string(),
178181
protocols: vec!["openai_chat_completions".to_string()],
179182
auth: AuthHeader::Bearer,
@@ -308,6 +311,7 @@ async fn proxy_uses_x_api_key_for_anthropic_route() {
308311
name: "inference.local".to_string(),
309312
endpoint: mock_server.uri(),
310313
model: "claude-sonnet-4-20250514".to_string(),
314+
provider_type: Some("anthropic".to_string()),
311315
api_key: "test-anthropic-key".to_string(),
312316
protocols: vec!["anthropic_messages".to_string()],
313317
auth: AuthHeader::Custom("x-api-key"),
@@ -366,6 +370,7 @@ async fn proxy_anthropic_does_not_send_bearer_auth() {
366370
name: "inference.local".to_string(),
367371
endpoint: mock_server.uri(),
368372
model: "claude-sonnet-4-20250514".to_string(),
373+
provider_type: Some("anthropic".to_string()),
369374
api_key: "anthropic-key".to_string(),
370375
protocols: vec!["anthropic_messages".to_string()],
371376
auth: AuthHeader::Custom("x-api-key"),
@@ -410,6 +415,7 @@ async fn proxy_forwards_client_anthropic_version_header() {
410415
name: "inference.local".to_string(),
411416
endpoint: mock_server.uri(),
412417
model: "claude-sonnet-4-20250514".to_string(),
418+
provider_type: Some("anthropic".to_string()),
413419
api_key: "test-anthropic-key".to_string(),
414420
protocols: vec!["anthropic_messages".to_string()],
415421
auth: AuthHeader::Custom("x-api-key"),

‎crates/openshell-sandbox/Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ bytes = { workspace = true }
5656
ipnet = "2"
5757

5858
# Serialization
59+
serde = { workspace = true }
5960
serde_json = { workspace = true }
6061
serde_yaml = { workspace = true }
62+
base64 = { workspace = true }
6163

6264
# Logging
6365
tracing = { workspace = true }

0 commit comments

Comments
 (0)