feat(auth): 添加ChatGPT OAuth认证功能
添加auth子命令用于管理ChatGPT OAuth认证,包含登录、状态查看和登出功能 支持浏览器自动登录和手动输入授权码两种方式
This commit is contained in:
parent
f047f26df0
commit
730cf80c99
119
cli/main.py
119
cli/main.py
|
|
@ -23,8 +23,6 @@ from rich import box
|
||||||
from rich.align import Align
|
from rich.align import Align
|
||||||
from rich.rule import Rule
|
from rich.rule import Rule
|
||||||
|
|
||||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
|
||||||
from tradingagents.default_config import DEFAULT_CONFIG
|
|
||||||
from cli.models import AnalystType
|
from cli.models import AnalystType
|
||||||
from cli.utils import *
|
from cli.utils import *
|
||||||
from cli.announcements import fetch_announcements, display_announcements
|
from cli.announcements import fetch_announcements, display_announcements
|
||||||
|
|
@ -37,6 +35,8 @@ app = typer.Typer(
|
||||||
help="TradingAgents CLI: Multi-Agents LLM Financial Trading Framework",
|
help="TradingAgents CLI: Multi-Agents LLM Financial Trading Framework",
|
||||||
add_completion=True, # Enable shell completion
|
add_completion=True, # Enable shell completion
|
||||||
)
|
)
|
||||||
|
auth_app = typer.Typer(help="Manage ChatGPT OAuth credentials for codex_oauth.")
|
||||||
|
app.add_typer(auth_app, name="auth")
|
||||||
|
|
||||||
|
|
||||||
# Create a deque to store recent messages with a maximum length
|
# Create a deque to store recent messages with a maximum length
|
||||||
|
|
@ -536,10 +536,10 @@ def get_user_selections():
|
||||||
)
|
)
|
||||||
selected_research_depth = select_research_depth()
|
selected_research_depth = select_research_depth()
|
||||||
|
|
||||||
# Step 5: OpenAI backend
|
# Step 5: LLM backend
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box(
|
||||||
"Step 5: OpenAI backend", "Select which service to talk to"
|
"Step 5: LLM backend", "Select which service to talk to"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
selected_llm_provider, backend_url = select_llm_provider()
|
selected_llm_provider, backend_url = select_llm_provider()
|
||||||
|
|
@ -566,11 +566,11 @@ def get_user_selections():
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
thinking_level = ask_gemini_thinking_config()
|
thinking_level = ask_gemini_thinking_config()
|
||||||
elif provider_lower == "openai":
|
elif provider_lower in ("openai", "codex_oauth"):
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box(
|
||||||
"Step 7: Reasoning Effort",
|
"Step 7: Reasoning Effort",
|
||||||
"Configure OpenAI reasoning effort level"
|
"Configure reasoning effort level"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
reasoning_effort = ask_openai_reasoning_effort()
|
reasoning_effort = ask_openai_reasoning_effort()
|
||||||
|
|
@ -897,6 +897,9 @@ def format_tool_args(args, max_length=80) -> str:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def run_analysis():
|
def run_analysis():
|
||||||
|
from tradingagents.default_config import DEFAULT_CONFIG
|
||||||
|
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||||
|
|
||||||
# First get all user selections
|
# First get all user selections
|
||||||
selections = get_user_selections()
|
selections = get_user_selections()
|
||||||
|
|
||||||
|
|
@ -1167,6 +1170,110 @@ def run_analysis():
|
||||||
display_complete_report(final_state)
|
display_complete_report(final_state)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_ms(ms: int) -> str:
|
||||||
|
if not ms:
|
||||||
|
return "unknown"
|
||||||
|
return datetime.datetime.fromtimestamp(ms / 1000, tz=datetime.timezone.utc).isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def _load_codex_oauth_modules():
|
||||||
|
try:
|
||||||
|
from codex_oauth.auth import (
|
||||||
|
decode_jwt_payload,
|
||||||
|
exchange_authorization_code,
|
||||||
|
extract_chatgpt_account_id,
|
||||||
|
login_manual,
|
||||||
|
login_via_browser,
|
||||||
|
)
|
||||||
|
from codex_oauth.exceptions import NotAuthenticatedError, OAuthFlowError
|
||||||
|
from codex_oauth.store import AuthStore, OAuthCredentials
|
||||||
|
except ModuleNotFoundError as exc:
|
||||||
|
console.print(
|
||||||
|
"[red]Missing dependency `langchain-codex-oauth`. "
|
||||||
|
"Install project dependencies first.[/red]"
|
||||||
|
)
|
||||||
|
raise typer.Exit(code=1) from exc
|
||||||
|
|
||||||
|
return {
|
||||||
|
"decode_jwt_payload": decode_jwt_payload,
|
||||||
|
"exchange_authorization_code": exchange_authorization_code,
|
||||||
|
"extract_chatgpt_account_id": extract_chatgpt_account_id,
|
||||||
|
"login_manual": login_manual,
|
||||||
|
"login_via_browser": login_via_browser,
|
||||||
|
"NotAuthenticatedError": NotAuthenticatedError,
|
||||||
|
"OAuthFlowError": OAuthFlowError,
|
||||||
|
"AuthStore": AuthStore,
|
||||||
|
"OAuthCredentials": OAuthCredentials,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@auth_app.command("login")
|
||||||
|
def auth_login(
|
||||||
|
manual: bool = typer.Option(False, "--manual", help="Paste redirect URL/code manually."),
|
||||||
|
timeout_s: int = typer.Option(180, "--timeout-s", min=30, help="Browser callback timeout in seconds."),
|
||||||
|
):
|
||||||
|
"""Login via ChatGPT OAuth and save local credentials."""
|
||||||
|
modules = _load_codex_oauth_modules()
|
||||||
|
try:
|
||||||
|
if manual:
|
||||||
|
code, verifier = modules["login_manual"]()
|
||||||
|
else:
|
||||||
|
result = modules["login_via_browser"](timeout_s=timeout_s)
|
||||||
|
if not result:
|
||||||
|
raise modules["OAuthFlowError"](
|
||||||
|
"OAuth callback timed out. Retry with --manual or try again."
|
||||||
|
)
|
||||||
|
code, verifier = result
|
||||||
|
|
||||||
|
token = modules["exchange_authorization_code"](code=code, verifier=verifier)
|
||||||
|
payload = modules["decode_jwt_payload"](token.access)
|
||||||
|
if not payload:
|
||||||
|
raise modules["OAuthFlowError"]("Received invalid access token.")
|
||||||
|
account_id = modules["extract_chatgpt_account_id"](payload)
|
||||||
|
if not account_id:
|
||||||
|
raise modules["OAuthFlowError"](
|
||||||
|
"Failed to extract chatgpt_account_id from access token."
|
||||||
|
)
|
||||||
|
|
||||||
|
modules["AuthStore"]().save(
|
||||||
|
modules["OAuthCredentials"](
|
||||||
|
access=token.access,
|
||||||
|
refresh=token.refresh,
|
||||||
|
expires=token.expires_at_ms,
|
||||||
|
account_id=account_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except modules["OAuthFlowError"] as exc:
|
||||||
|
console.print(f"[red]{exc}[/red]")
|
||||||
|
raise typer.Exit(code=2)
|
||||||
|
|
||||||
|
console.print("[green]Login successful. OAuth credentials saved.[/green]")
|
||||||
|
|
||||||
|
|
||||||
|
@auth_app.command("status")
|
||||||
|
def auth_status():
|
||||||
|
"""Show current OAuth credential status."""
|
||||||
|
modules = _load_codex_oauth_modules()
|
||||||
|
store = modules["AuthStore"]()
|
||||||
|
try:
|
||||||
|
creds = store.load()
|
||||||
|
except modules["NotAuthenticatedError"] as exc:
|
||||||
|
console.print(f"[red]{exc}[/red]")
|
||||||
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
|
console.print("[green]Logged in: yes[/green]")
|
||||||
|
console.print(f"Account id: {creds.account_id}")
|
||||||
|
console.print(f"Expires (UTC): {_format_ms(creds.expires)}")
|
||||||
|
|
||||||
|
|
||||||
|
@auth_app.command("logout")
|
||||||
|
def auth_logout():
|
||||||
|
"""Delete local OAuth credentials."""
|
||||||
|
modules = _load_codex_oauth_modules()
|
||||||
|
modules["AuthStore"]().delete()
|
||||||
|
console.print("[green]Logged out.[/green]")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def analyze():
|
def analyze():
|
||||||
run_analysis()
|
run_analysis()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue