feat: Support authenticating or refreshing OAuth for supported clients from within the REPL
This commit is contained in:
@@ -166,6 +166,7 @@ clients:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
loki --authenticate my-claude-oauth
|
loki --authenticate my-claude-oauth
|
||||||
|
# Or via the REPL: .authenticate
|
||||||
```
|
```
|
||||||
|
|
||||||
For full details, see the [authentication documentation](./docs/clients/CLIENTS.md#authentication).
|
For full details, see the [authentication documentation](./docs/clients/CLIENTS.md#authentication).
|
||||||
|
|||||||
+2
-1
@@ -210,7 +210,8 @@ clients:
|
|||||||
- type: claude
|
- type: claude
|
||||||
api_base: https://api.anthropic.com/v1 # Optional
|
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
|
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/
|
# See https://docs.mistral.ai/
|
||||||
- type: openai-compatible
|
- 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)
|
- [`.edit` - Modify configuration files](#edit---modify-configuration-files)
|
||||||
- [`.delete` - Delete configurations from Loki](#delete---delete-configurations-from-loki)
|
- [`.delete` - Delete configurations from Loki](#delete---delete-configurations-from-loki)
|
||||||
- [`.info` - Display information about the current mode](#info---display-information-about-the-current-mode)
|
- [`.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)
|
- [`.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)
|
- [`.help` - Show the help guide](#help---show-the-help-guide)
|
||||||
<!--toc:end-->
|
<!--toc:end-->
|
||||||
@@ -237,6 +238,11 @@ The following entities are supported:
|
|||||||
| `.info agent` | Display information about the active agent |
|
| `.info agent` | Display information about the active agent |
|
||||||
| `.info rag` | Display information about the active RAG |
|
| `.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
|
### `.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.
|
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
|
loki --authenticate my-claude-oauth
|
||||||
```
|
```
|
||||||
|
|
||||||
This opens your browser for the OAuth authorization flow. After authorizing, paste the authorization code back into
|
Or if you have only one OAuth-configured client, you can omit the name:
|
||||||
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:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
loki --authenticate
|
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**
|
**Step 3: Use normally**
|
||||||
|
|
||||||
Once authenticated, the client works like any other. Loki uses the stored OAuth tokens automatically:
|
Once authenticated, the client works like any other. Loki uses the stored OAuth tokens automatically:
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ impl ClaudeClient {
|
|||||||
impl Client for ClaudeClient {
|
impl Client for ClaudeClient {
|
||||||
client_common_fns!();
|
client_common_fns!();
|
||||||
|
|
||||||
|
fn supports_oauth(&self) -> bool {
|
||||||
|
self.config.auth.as_deref() == Some("oauth")
|
||||||
|
}
|
||||||
|
|
||||||
async fn chat_completions_inner(
|
async fn chat_completions_inner(
|
||||||
&self,
|
&self,
|
||||||
client: &ReqwestClient,
|
client: &ReqwestClient,
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ pub trait Client: Sync + Send {
|
|||||||
|
|
||||||
fn model(&self) -> &Model;
|
fn model(&self) -> &Model;
|
||||||
|
|
||||||
|
fn supports_oauth(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn build_client(&self) -> Result<ReqwestClient> {
|
fn build_client(&self) -> Result<ReqwestClient> {
|
||||||
let mut builder = ReqwestClient::builder();
|
let mut builder = ReqwestClient::builder();
|
||||||
let extra = self.extra_config();
|
let extra = self.extra_config();
|
||||||
|
|||||||
+20
-2
@@ -6,7 +6,7 @@ use self::completer::ReplCompleter;
|
|||||||
use self::highlighter::ReplHighlighter;
|
use self::highlighter::ReplHighlighter;
|
||||||
use self::prompt::ReplPrompt;
|
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::{
|
use crate::config::{
|
||||||
AgentVariables, AssertState, Config, GlobalConfig, Input, LastMessage, StateFlags,
|
AgentVariables, AssertState, Config, GlobalConfig, Input, LastMessage, StateFlags,
|
||||||
macro_execute,
|
macro_execute,
|
||||||
@@ -17,6 +17,7 @@ use crate::utils::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::mcp::McpRegistry;
|
use crate::mcp::McpRegistry;
|
||||||
|
use crate::resolve_oauth_client;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
@@ -32,10 +33,15 @@ use std::{env, mem, process};
|
|||||||
|
|
||||||
const MENU_NAME: &str = "completion_menu";
|
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(".help", "Show this help guide", AssertState::pass()),
|
||||||
ReplCommand::new(".info", "Show system info", 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(
|
ReplCommand::new(
|
||||||
".edit config",
|
".edit config",
|
||||||
"Modify configuration file",
|
"Modify configuration file",
|
||||||
@@ -421,6 +427,18 @@ pub async fn run_repl_command(
|
|||||||
}
|
}
|
||||||
None => println!("Usage: .model <name>"),
|
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 {
|
".prompt" => match args {
|
||||||
Some(text) => {
|
Some(text) => {
|
||||||
config.write().use_prompt(text)?;
|
config.write().use_prompt(text)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user