Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions crates/adapters/examples/openrouter_live.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! Live end-to-end check: read OpenRouter usage with a real key and print the snapshot. NOT a
//! CI test (hits the network). Provide the key via the OPENROUTER_API_KEY env var so the check
//! needs no keychain entry:
//!
//! OPENROUTER_API_KEY=sk-or-v1-… cargo run -p mlt-adapters --example openrouter_live
use mlt_adapters::{ReqwestHttp, SystemClock};
use mlt_core::domain::ProviderId;
use mlt_core::ports::{PortError, SecretStore};
use mlt_core::providers::openrouter::OpenRouterStrategy;
use mlt_core::providers::{FetchContext, FetchStrategy};
use std::sync::Arc;

/// Serves the key from OPENROUTER_API_KEY, standing in for the OS keychain for this hand-run
/// check (the real app reads the user-entered key the same way, via the `SecretStore` port).
struct EnvKey(String);
impl SecretStore for EnvKey {
fn get(&self, _key: &str) -> Result<Option<String>, PortError> {
Ok(Some(self.0.clone()))
}
fn set(&self, _key: &str, _value: &str) -> Result<(), PortError> {
Ok(())
}
fn delete(&self, _key: &str) -> Result<(), PortError> {
Ok(())
}
}

#[tokio::main]
async fn main() {
let Ok(key) = std::env::var("OPENROUTER_API_KEY") else {
eprintln!("set OPENROUTER_API_KEY to your sk-or-v1-… key");
std::process::exit(1);
};
let strategy = OpenRouterStrategy {
secrets: Arc::new(EnvKey(key)),
http: Arc::new(ReqwestHttp::new()),
clock: Arc::new(SystemClock),
};
let ctx = FetchContext {
provider: ProviderId::new("openrouter"),
};
match strategy.fetch(&ctx).await {
Ok(snapshot) => println!("{}", serde_json::to_string_pretty(&snapshot).unwrap()),
Err(e) => {
eprintln!("usage fetch failed: {e}");
std::process::exit(1);
}
}
}
2 changes: 2 additions & 0 deletions crates/adapters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod consent;
pub mod http;
pub mod identity;
pub mod labels;
pub mod openrouter;
pub(crate) mod resilience;
pub mod secrets;
pub mod sources;
Expand All @@ -24,6 +25,7 @@ pub use consent::FileConsentStore;
pub use http::ReqwestHttp;
pub use identity::FileIdentityStore;
pub use labels::FileLabelStore;
pub use openrouter::openrouter_strategy;
pub use secrets::KeyringSecretStore;
pub use sources::LocalSourceProbe;

Expand Down
21 changes: 21 additions & 0 deletions crates/adapters/src/openrouter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! OpenRouter adapter: assemble the API-key usage strategy from our keychain + HTTP client.
//!
//! OpenRouter is an API-key provider (ADR 0014/0016), so there is no login to discover and no
//! OAuth to refresh — the strategy reads the user-entered key (stored by task 003 under our own
//! keychain service) via the `SecretStore` port and polls OpenRouter. All IO lives here; the
//! parsing and window mapping are pure in [`mlt_core::providers::openrouter`].
use std::sync::Arc;

use mlt_core::providers::openrouter::OpenRouterStrategy;

use crate::{KeyringSecretStore, ReqwestHttp, SystemClock, KEYCHAIN_SERVICE};

/// Build a ready-to-run OpenRouter usage strategy: read the stored API key from our keychain and
/// poll the key + credits endpoints. The key is only ever read — never written back.
pub fn openrouter_strategy() -> OpenRouterStrategy {
OpenRouterStrategy {
secrets: Arc::new(KeyringSecretStore::new(KEYCHAIN_SERVICE)),
http: Arc::new(ReqwestHttp::new()),
clock: Arc::new(SystemClock),
}
}
Loading