-
Notifications
You must be signed in to change notification settings - Fork 1
feat(providers): OpenAI + Anthropic API usage with honest limits (tasks 007, 008) #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,974
−65
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
c5b9d80
feat(providers): OpenAI + Anthropic API usage with honest limits (tas…
ogrodev c6fec8c
docs(tasks): mark OpenAI + Anthropic API providers done (007, 008)
ogrodev f6e3ba4
fix(providers): correct Anthropic cost units, guard OpenAI non-finite…
ogrodev e95e9b5
refactor(providers): typed usage note, shared cost-provider helper, u…
ogrodev a1691b8
fix(providers): treat OpenAI non-admin cost 401 as the honest org-adm…
ogrodev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| //! Live end-to-end check: read Anthropic **API** usage with a real key and print the snapshot. | ||
| //! NOT a CI test (hits the network). Provide the key via the ANTHROPIC_API_KEY env var so the | ||
| //! check needs no keychain entry: | ||
| //! | ||
| //! ANTHROPIC_API_KEY=sk-ant-api-… cargo run -p mlt-adapters --example anthropic_live | ||
| //! | ||
| //! With a normal (non-admin) key, expect the honest-limitation note rather than a spend figure — | ||
| //! that is the point of task 008. | ||
| use mlt_adapters::{ReqwestHttp, SystemClock}; | ||
| use mlt_core::domain::ProviderId; | ||
| use mlt_core::ports::{PortError, SecretStore}; | ||
| use mlt_core::providers::anthropic::AnthropicStrategy; | ||
| use mlt_core::providers::{FetchContext, FetchStrategy}; | ||
| use std::sync::Arc; | ||
|
|
||
| /// Serves the key from ANTHROPIC_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("ANTHROPIC_API_KEY") else { | ||
| eprintln!("set ANTHROPIC_API_KEY to your sk-ant-api-… key"); | ||
| std::process::exit(1); | ||
| }; | ||
| let strategy = AnthropicStrategy { | ||
| secrets: Arc::new(EnvKey(key)), | ||
| http: Arc::new(ReqwestHttp::new()), | ||
| clock: Arc::new(SystemClock), | ||
| }; | ||
| let ctx = FetchContext { | ||
| provider: ProviderId::new("anthropic"), | ||
| }; | ||
| 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); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| //! Live end-to-end check: read OpenAI usage with a real key and print the snapshot. NOT a CI test | ||
| //! (hits the network). Provide the key via the OPENAI_API_KEY env var so the check needs no | ||
| //! keychain entry: | ||
| //! | ||
| //! OPENAI_API_KEY=sk-… cargo run -p mlt-adapters --example openai_live | ||
| //! | ||
| //! A normal key (not an `sk-admin…` org key) is expected to hit the honest org-usage limitation — | ||
| //! the snapshot still prints, carrying the limitation note instead of a fabricated percentage. | ||
| use mlt_adapters::{ReqwestHttp, SystemClock}; | ||
| use mlt_core::domain::ProviderId; | ||
| use mlt_core::ports::{PortError, SecretStore}; | ||
| use mlt_core::providers::openai::OpenAiStrategy; | ||
| use mlt_core::providers::{FetchContext, FetchStrategy}; | ||
| use std::sync::Arc; | ||
|
|
||
| /// Serves the key from OPENAI_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("OPENAI_API_KEY") else { | ||
| eprintln!("set OPENAI_API_KEY to your sk-… key"); | ||
| std::process::exit(1); | ||
| }; | ||
| let strategy = OpenAiStrategy { | ||
| secrets: Arc::new(EnvKey(key)), | ||
| http: Arc::new(ReqwestHttp::new()), | ||
| clock: Arc::new(SystemClock), | ||
| }; | ||
| let ctx = FetchContext { | ||
| provider: ProviderId::new("openai"), | ||
| }; | ||
| match strategy.fetch(&ctx).await { | ||
| Ok(snapshot) => println!("{}", serde_json::to_string_pretty(&snapshot).unwrap()), | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| Err(e) => { | ||
| eprintln!("usage fetch failed: {e}"); | ||
| std::process::exit(1); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| //! Anthropic **API** adapter: assemble the API-key usage strategy from our keychain + HTTP client. | ||
| //! | ||
| //! This is the Anthropic *API* provider (ADR 0014/0016) — distinct from the `"claude-code"` | ||
| //! subscription provider. There is no login to discover and no OAuth to refresh: the user pastes | ||
| //! a normal `sk-ant-api…` key (stored by task 003 under our own keychain service), which the | ||
| //! strategy reads via the `SecretStore` port and uses to poll Anthropic's org cost endpoint. All | ||
| //! IO lives here; the parsing and honest-note mapping are pure in [`mlt_core::providers::anthropic`]. | ||
| use std::sync::Arc; | ||
|
|
||
| use mlt_core::providers::anthropic::AnthropicStrategy; | ||
|
|
||
| use crate::{KeyringSecretStore, ReqwestHttp, SystemClock, KEYCHAIN_SERVICE}; | ||
|
|
||
| /// Build a ready-to-run Anthropic **API** usage strategy: read the stored API key from our | ||
| /// keychain and poll the org cost endpoint. The key is only ever read — never written back. | ||
| pub fn anthropic_strategy() -> AnthropicStrategy { | ||
| AnthropicStrategy { | ||
| secrets: Arc::new(KeyringSecretStore::new(KEYCHAIN_SERVICE)), | ||
| http: Arc::new(ReqwestHttp::new()), | ||
| clock: Arc::new(SystemClock), | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| //! OpenAI adapter: assemble the API-key usage strategy from our keychain + HTTP client. | ||
| //! | ||
| //! OpenAI 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 OpenAI's org cost endpoint. All IO | ||
| //! lives here; the parsing and honesty decisions are pure in [`mlt_core::providers::openai`]. | ||
| use std::sync::Arc; | ||
|
|
||
| use mlt_core::providers::openai::OpenAiStrategy; | ||
|
|
||
| use crate::{KeyringSecretStore, ReqwestHttp, SystemClock, KEYCHAIN_SERVICE}; | ||
|
|
||
| /// Build a ready-to-run OpenAI usage strategy: read the stored API key from our keychain and poll | ||
| /// the organization cost endpoint. The key is only ever read — never written back. | ||
| pub fn openai_strategy() -> OpenAiStrategy { | ||
| OpenAiStrategy { | ||
| secrets: Arc::new(KeyringSecretStore::new(KEYCHAIN_SERVICE)), | ||
| http: Arc::new(ReqwestHttp::new()), | ||
| clock: Arc::new(SystemClock), | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.