feat: add handling for when failed to refresh access token (#168)
Co-authored-by: Charles Packer <packercharles@gmail.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
34
src/index.ts
34
src/index.ts
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user