feat: Support authenticating or refreshing OAuth for supported clients from within the REPL
This commit is contained in:
@@ -166,6 +166,7 @@ clients:
|
||||
|
||||
```sh
|
||||
loki --authenticate my-claude-oauth
|
||||
# Or via the REPL: .authenticate
|
||||
```
|
||||
|
||||
For full details, see the [authentication documentation](./docs/clients/CLIENTS.md#authentication).
|
||||
|
||||
+2
-1
@@ -210,7 +210,8 @@ clients:
|
||||
- type: claude
|
||||
api_base: https://api.anthropic.com/v1 # Optional
|
||||
api_key: '{{ANTHROPIC_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||
auth: null # When set to 'oauth', Loki will use OAuth instead of an API key; Authenticate with `loki --authenticate`
|
||||
auth: null # When set to 'oauth', Loki will use OAuth instead of an API key
|
||||
# Authenticate with `loki --authenticate` or `.authenticate` in the REPL
|
||||
|
||||
# See https://docs.mistral.ai/
|
||||
- type: openai-compatible
|
||||
|
||||
@@ -23,6 +23,7 @@ You can enter the REPL by simply typing `loki` without any follow-up flags or ar
|
||||
- [`.edit` - Modify configuration files](#edit---modify-configuration-files)
|
||||
- [`.delete` - Delete configurations from Loki](#delete---delete-configurations-from-loki)
|
||||
- [`.info` - Display information about the current mode](#info---display-information-about-the-current-mode)
|
||||
- [`.authenticate` - Authenticate the current model client via OAuth](#authenticate---authenticate-the-current-model-client-via-oauth)
|
||||
- [`.exit` - Exit an agent/role/session/rag or the Loki REPL itself](#exit---exit-an-agentrolesessionrag-or-the-loki-repl-itself)
|
||||
- [`.help` - Show the help guide](#help---show-the-help-guide)
|
||||
<!--toc:end-->
|
||||
@@ -237,6 +238,11 @@ The following entities are supported:
|
||||
| `.info agent` | Display information about the active agent |
|
||||
| `.info rag` | Display information about the active RAG |
|
||||
|
||||
### `.authenticate` - Authenticate the current model client via OAuth
|
||||
The `.authenticate` command will start the OAuth flow for the current model client if
|
||||
* The client supports OAuth (See the [clients documentation](./clients/CLIENTS.md#providers-that-support-oauth) for supported clients)
|
||||
* The client is configured in your Loki configuration to use OAuth via the `auth: oauth` property
|
||||
|
||||
### `.exit` - Exit an agent/role/session/rag or the Loki REPL itself
|
||||
The `.exit` command is used to move between modes in the Loki REPL.
|
||||
|
||||
|
||||
@@ -129,15 +129,17 @@ Run the `--authenticate` flag with the client name:
|
||||
loki --authenticate my-claude-oauth
|
||||
```
|
||||
|
||||
This opens your browser for the OAuth authorization flow. After authorizing, paste the authorization code back into
|
||||
the terminal. Loki stores the tokens in `~/.cache/loki/oauth` and automatically refreshes them when they expire.
|
||||
|
||||
If you have only one OAuth-configured client, you can omit the name:
|
||||
Or if you have only one OAuth-configured client, you can omit the name:
|
||||
|
||||
```sh
|
||||
loki --authenticate
|
||||
```
|
||||
|
||||
Alternatively, you can use the REPL command `.authenticate`.
|
||||
|
||||
This opens your browser for the OAuth authorization flow. After authorizing, paste the authorization code back into
|
||||
the terminal. Loki stores the tokens in `~/.cache/loki/oauth` and automatically refreshes them when they expire.
|
||||
|
||||
**Step 3: Use normally**
|
||||
|
||||
Once authenticated, the client works like any other. Loki uses the stored OAuth tokens automatically:
|
||||
|
||||
@@ -27,7 +27,7 @@ pub struct ClaudeConfig {
|
||||
impl ClaudeClient {
|
||||
config_get_fn!(api_key, get_api_key);
|
||||
config_get_fn!(api_base, get_api_base);
|
||||
|
||||
|
||||
create_oauth_supported_client_config!();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ impl ClaudeClient {
|
||||
impl Client for ClaudeClient {
|
||||
client_common_fns!();
|
||||
|
||||
fn supports_oauth(&self) -> bool {
|
||||
self.config.auth.as_deref() == Some("oauth")
|
||||
}
|
||||
|
||||
async fn chat_completions_inner(
|
||||
&self,
|
||||
client: &ReqwestClient,
|
||||
|
||||
@@ -47,6 +47,10 @@ pub trait Client: Sync + Send {
|
||||
|
||||
fn model(&self) -> &Model;
|
||||
|
||||
fn supports_oauth(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn build_client(&self) -> Result<ReqwestClient> {
|
||||
let mut builder = ReqwestClient::builder();
|
||||
let extra = self.extra_config();
|
||||
|
||||
+20
-2
@@ -6,7 +6,7 @@ use self::completer::ReplCompleter;
|
||||
use self::highlighter::ReplHighlighter;
|
||||
use self::prompt::ReplPrompt;
|
||||
|
||||
use crate::client::{call_chat_completions, call_chat_completions_streaming};
|
||||
use crate::client::{call_chat_completions, call_chat_completions_streaming, init_client, oauth};
|
||||
use crate::config::{
|
||||
AgentVariables, AssertState, Config, GlobalConfig, Input, LastMessage, StateFlags,
|
||||
macro_execute,
|
||||
@@ -17,6 +17,7 @@ use crate::utils::{
|
||||
};
|
||||
|
||||
use crate::mcp::McpRegistry;
|
||||
use crate::resolve_oauth_client;
|
||||
use anyhow::{Context, Result, bail};
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
use fancy_regex::Regex;
|
||||
@@ -32,10 +33,15 @@ use std::{env, mem, process};
|
||||
|
||||
const MENU_NAME: &str = "completion_menu";
|
||||
|
||||
static REPL_COMMANDS: LazyLock<[ReplCommand; 37]> = LazyLock::new(|| {
|
||||
static REPL_COMMANDS: LazyLock<[ReplCommand; 38]> = LazyLock::new(|| {
|
||||
[
|
||||
ReplCommand::new(".help", "Show this help guide", AssertState::pass()),
|
||||
ReplCommand::new(".info", "Show system info", AssertState::pass()),
|
||||
ReplCommand::new(
|
||||
".authenticate",
|
||||
"Authenticate the current model client via OAuth (if configured)",
|
||||
AssertState::pass(),
|
||||
),
|
||||
ReplCommand::new(
|
||||
".edit config",
|
||||
"Modify configuration file",
|
||||
@@ -421,6 +427,18 @@ pub async fn run_repl_command(
|
||||
}
|
||||
None => println!("Usage: .model <name>"),
|
||||
},
|
||||
".authenticate" => {
|
||||
let client = init_client(config, None)?;
|
||||
if !client.supports_oauth() {
|
||||
bail!(
|
||||
"Client '{}' doesn't either support OAuth or isn't configured to use it (i.e. uses an API key instead)",
|
||||
client.name()
|
||||
);
|
||||
}
|
||||
let clients = config.read().clients.clone();
|
||||
let (client_name, provider) = resolve_oauth_client(Some(client.name()), &clients)?;
|
||||
oauth::run_oauth_flow(&provider, &client_name).await?;
|
||||
}
|
||||
".prompt" => match args {
|
||||
Some(text) => {
|
||||
config.write().use_prompt(text)?;
|
||||
|
||||
Reference in New Issue
Block a user