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 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";
|
import { settingsManager } from "../settings-manager";
|
||||||
|
|
||||||
export async function getClient() {
|
export async function getClient() {
|
||||||
@@ -40,14 +40,17 @@ export async function getClient() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if refresh token is missing for Letta Cloud
|
||||||
const baseURL =
|
const baseURL =
|
||||||
process.env.LETTA_BASE_URL ||
|
process.env.LETTA_BASE_URL ||
|
||||||
settings.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("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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,13 @@
|
|||||||
|
|
||||||
import Letta from "@letta-ai/letta-client";
|
import Letta from "@letta-ai/letta-client";
|
||||||
|
|
||||||
|
export const LETTA_CLOUD_API_URL = "https://api.letta.com";
|
||||||
|
|
||||||
export const OAUTH_CONFIG = {
|
export const OAUTH_CONFIG = {
|
||||||
clientId: "ci-let-724dea7e98f4af6f8f370f4b1466200c",
|
clientId: "ci-let-724dea7e98f4af6f8f370f4b1466200c",
|
||||||
clientSecret: "", // Not needed for device code flow
|
clientSecret: "", // Not needed for device code flow
|
||||||
authBaseUrl: "https://app.letta.com",
|
authBaseUrl: "https://app.letta.com",
|
||||||
apiBaseUrl: "https://api.letta.com",
|
apiBaseUrl: LETTA_CLOUD_API_URL,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export interface DeviceCodeResponse {
|
export interface DeviceCodeResponse {
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export function SetupUI({ onComplete }: SetupUIProps) {
|
|||||||
<Text>{asciiLogo}</Text>
|
<Text>{asciiLogo}</Text>
|
||||||
<Text bold>Welcome to Letta Code!</Text>
|
<Text bold>Welcome to Letta Code!</Text>
|
||||||
<Text> </Text>
|
<Text> </Text>
|
||||||
<Text>Please choose how you'd like to authenticate:</Text>
|
<Text>Let's get you authenticated:</Text>
|
||||||
<Text> </Text>
|
<Text> </Text>
|
||||||
<Box>
|
<Box>
|
||||||
<Text color={selectedOption === 0 ? "cyan" : undefined}>
|
<Text color={selectedOption === 0 ? "cyan" : undefined}>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Box, Text, useInput } from "ink";
|
|||||||
import SpinnerLib from "ink-spinner";
|
import SpinnerLib from "ink-spinner";
|
||||||
import type { ComponentType } from "react";
|
import type { ComponentType } from "react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { LETTA_CLOUD_API_URL } from "../../auth/oauth";
|
||||||
import type { PermissionMode } from "../../permissions/mode";
|
import type { PermissionMode } from "../../permissions/mode";
|
||||||
import { permissionMode } from "../../permissions/mode";
|
import { permissionMode } from "../../permissions/mode";
|
||||||
import { settingsManager } from "../../settings-manager";
|
import { settingsManager } from "../../settings-manager";
|
||||||
@@ -113,7 +114,7 @@ export function Input({
|
|||||||
const serverUrl =
|
const serverUrl =
|
||||||
process.env.LETTA_BASE_URL ||
|
process.env.LETTA_BASE_URL ||
|
||||||
settings.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)
|
// Handle escape key for interrupt (when streaming) or double-escape-to-clear (when not)
|
||||||
useInput((_input, key) => {
|
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 { getClient } from "./agent/client";
|
||||||
import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context";
|
import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context";
|
||||||
import type { AgentProvenance } from "./agent/create";
|
import type { AgentProvenance } from "./agent/create";
|
||||||
|
import { LETTA_CLOUD_API_URL } from "./auth/oauth";
|
||||||
import { permissionMode } from "./permissions/mode";
|
import { permissionMode } from "./permissions/mode";
|
||||||
import { settingsManager } from "./settings-manager";
|
import { settingsManager } from "./settings-manager";
|
||||||
import { loadTools, upsertToolsToServer } from "./tools/manager";
|
import { loadTools, upsertToolsToServer } from "./tools/manager";
|
||||||
@@ -46,15 +47,15 @@ OPTIONS
|
|||||||
|
|
||||||
BEHAVIOR
|
BEHAVIOR
|
||||||
By default, letta auto-resumes the last agent used in the current directory
|
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:
|
Memory blocks (persona, human, project, skills) are shared between agents:
|
||||||
- Global blocks (persona, human) are shared across all agents
|
- Global blocks (persona, human) are shared across all agents
|
||||||
- Local blocks (project, skills) are shared within the current directory
|
- 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 --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.
|
Use --fresh-blocks to create a completely isolated agent with new blocks.
|
||||||
|
|
||||||
If no credentials are configured, you'll be prompted to authenticate via
|
If no credentials are configured, you'll be prompted to authenticate via
|
||||||
Letta Cloud OAuth on first run.
|
Letta Cloud OAuth on first run.
|
||||||
|
|
||||||
@@ -64,10 +65,10 @@ EXAMPLES
|
|||||||
letta --new # New agent, keeps your persona/human blocks
|
letta --new # New agent, keeps your persona/human blocks
|
||||||
letta --fresh-blocks # New agent, all blocks fresh (full isolation)
|
letta --fresh-blocks # New agent, all blocks fresh (full isolation)
|
||||||
letta --agent agent_123
|
letta --agent agent_123
|
||||||
|
|
||||||
# inside the interactive session
|
# inside the interactive session
|
||||||
/logout # Clear credentials and exit
|
/logout # Clear credentials and exit
|
||||||
|
|
||||||
# headless with JSON output (includes stats)
|
# headless with JSON output (includes stats)
|
||||||
letta -p "hello" --output-format json
|
letta -p "hello" --output-format json
|
||||||
|
|
||||||
@@ -265,13 +266,28 @@ async function main() {
|
|||||||
const baseURL =
|
const baseURL =
|
||||||
process.env.LETTA_BASE_URL ||
|
process.env.LETTA_BASE_URL ||
|
||||||
settings.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)
|
// For headless mode, error out (assume automation context)
|
||||||
if (isHeadless) {
|
if (isHeadless) {
|
||||||
console.error("Missing LETTA_API_KEY");
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user