feat: add handling for when failed to refresh access token (#168)

Co-authored-by: Charles Packer <packercharles@gmail.com>
This commit is contained in:
Shelley Pham
2025-12-10 14:19:08 -08:00
committed by GitHub
parent ed5c6d71b7
commit 6db2fcfc05
5 changed files with 38 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
import Letta from "@letta-ai/letta-client";
import { refreshAccessToken } from "../auth/oauth";
import { LETTA_CLOUD_API_URL, refreshAccessToken } from "../auth/oauth";
import { settingsManager } from "../settings-manager";
export async function getClient() {
@@ -40,14 +40,17 @@ export async function getClient() {
}
}
// Check if refresh token is missing for Letta Cloud
const baseURL =
process.env.LETTA_BASE_URL ||
settings.env?.LETTA_BASE_URL ||
"https://api.letta.com";
LETTA_CLOUD_API_URL;
if (!apiKey && baseURL === "https://api.letta.com") {
if (!apiKey && baseURL === LETTA_CLOUD_API_URL) {
console.error("Missing LETTA_API_KEY");
console.error("Run 'letta setup' to configure authentication");
console.error(
"Run 'letta setup' to configure authentication or set your LETTA_API_KEY environment variable",
);
process.exit(1);
}

View File

@@ -5,11 +5,13 @@
import Letta from "@letta-ai/letta-client";
export const LETTA_CLOUD_API_URL = "https://api.letta.com";
export const OAUTH_CONFIG = {
clientId: "ci-let-724dea7e98f4af6f8f370f4b1466200c",
clientSecret: "", // Not needed for device code flow
authBaseUrl: "https://app.letta.com",
apiBaseUrl: "https://api.letta.com",
apiBaseUrl: LETTA_CLOUD_API_URL,
} as const;
export interface DeviceCodeResponse {

View File

@@ -148,7 +148,7 @@ export function SetupUI({ onComplete }: SetupUIProps) {
<Text>{asciiLogo}</Text>
<Text bold>Welcome to Letta Code!</Text>
<Text> </Text>
<Text>Please choose how you'd like to authenticate:</Text>
<Text>Let's get you authenticated:</Text>
<Text> </Text>
<Box>
<Text color={selectedOption === 0 ? "cyan" : undefined}>

View File

@@ -3,6 +3,7 @@ import { Box, Text, useInput } from "ink";
import SpinnerLib from "ink-spinner";
import type { ComponentType } from "react";
import { useEffect, useRef, useState } from "react";
import { LETTA_CLOUD_API_URL } from "../../auth/oauth";
import type { PermissionMode } from "../../permissions/mode";
import { permissionMode } from "../../permissions/mode";
import { settingsManager } from "../../settings-manager";
@@ -113,7 +114,7 @@ export function Input({
const serverUrl =
process.env.LETTA_BASE_URL ||
settings.env?.LETTA_BASE_URL ||
"https://api.letta.com";
LETTA_CLOUD_API_URL;
// Handle escape key for interrupt (when streaming) or double-escape-to-clear (when not)
useInput((_input, key) => {

View File

@@ -5,6 +5,7 @@ import { getResumeData, type ResumeData } from "./agent/check-approval";
import { getClient } from "./agent/client";
import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context";
import type { AgentProvenance } from "./agent/create";
import { LETTA_CLOUD_API_URL } from "./auth/oauth";
import { permissionMode } from "./permissions/mode";
import { settingsManager } from "./settings-manager";
import { loadTools, upsertToolsToServer } from "./tools/manager";
@@ -46,15 +47,15 @@ OPTIONS
BEHAVIOR
By default, letta auto-resumes the last agent used in the current directory
(stored in .letta/settings.local.json).
(stored in .letta/settings.local.json).
Memory blocks (persona, human, project, skills) are shared between agents:
- Global blocks (persona, human) are shared across all agents
- Local blocks (project, skills) are shared within the current directory
Use --new to create a new agent that reuses your global persona/human blocks.
Use --fresh-blocks to create a completely isolated agent with new blocks.
If no credentials are configured, you'll be prompted to authenticate via
Letta Cloud OAuth on first run.
@@ -64,10 +65,10 @@ EXAMPLES
letta --new # New agent, keeps your persona/human blocks
letta --fresh-blocks # New agent, all blocks fresh (full isolation)
letta --agent agent_123
# inside the interactive session
/logout # Clear credentials and exit
# headless with JSON output (includes stats)
letta -p "hello" --output-format json
@@ -265,13 +266,28 @@ async function main() {
const baseURL =
process.env.LETTA_BASE_URL ||
settings.env?.LETTA_BASE_URL ||
"https://api.letta.com";
LETTA_CLOUD_API_URL;
if (!apiKey && baseURL === "https://api.letta.com") {
// Check if refresh token is missing for Letta Cloud (only when not using env var)
if (
!isHeadless &&
baseURL === LETTA_CLOUD_API_URL &&
!settings.refreshToken
) {
// For interactive mode, show setup flow
const { runSetup } = await import("./auth/setup");
await runSetup();
// After setup, restart main flow
return main();
}
if (!apiKey && baseURL === LETTA_CLOUD_API_URL) {
// For headless mode, error out (assume automation context)
if (isHeadless) {
console.error("Missing LETTA_API_KEY");
console.error("Run 'letta' in interactive mode to authenticate");
console.error(
"Run 'letta' in interactive mode to authenticate or export the missing environment variable",
);
process.exit(1);
}