refactor: replace ADE /agents/ links with /chat/ and centralize URL building [LET-7798] (#1254)
Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
@@ -193,6 +193,7 @@ import {
|
||||
toLines,
|
||||
} from "./helpers/accumulator";
|
||||
import { classifyApprovals } from "./helpers/approvalClassification";
|
||||
import { buildChatUrl } from "./helpers/appUrls";
|
||||
import { backfillBuffers } from "./helpers/backfill";
|
||||
import { chunkLog } from "./helpers/chunkLog";
|
||||
import {
|
||||
@@ -6275,7 +6276,7 @@ export default function App({
|
||||
});
|
||||
|
||||
// Build success message with hints
|
||||
const agentUrl = `https://app.letta.com/projects/default-project/agents/${agent.id}`;
|
||||
const agentUrl = buildChatUrl(agent.id);
|
||||
const memfsTip = settingsManager.isMemfsEnabled(agent.id)
|
||||
? "Memory will be auto-initialized on your first message."
|
||||
: "Tip: use /init to initialize your agent's memory system!";
|
||||
@@ -6964,10 +6965,9 @@ export default function App({
|
||||
|
||||
// Special handling for /ade command - open agent in browser
|
||||
if (trimmed === "/ade") {
|
||||
const adeUrl =
|
||||
conversationIdRef.current === "default"
|
||||
? `https://app.letta.com/agents/${agentId}`
|
||||
: `https://app.letta.com/agents/${agentId}?conversation=${conversationIdRef.current}`;
|
||||
const adeUrl = buildChatUrl(agentId, {
|
||||
conversationId: conversationIdRef.current,
|
||||
});
|
||||
|
||||
const cmd = commandRunner.start("/ade", "Opening ADE...");
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { buildChatUrl } from "../helpers/appUrls";
|
||||
|
||||
const DEFAULT_WORKFLOW_PATH = ".github/workflows/letta.yml";
|
||||
const ALTERNATE_WORKFLOW_PATH = ".github/workflows/letta-code.yml";
|
||||
@@ -512,9 +513,7 @@ export async function installGithubApp(
|
||||
committed: false,
|
||||
secretAction: "set",
|
||||
agentId: resolvedAgentId,
|
||||
agentUrl: resolvedAgentId
|
||||
? `https://app.letta.com/agents/${resolvedAgentId}`
|
||||
: null,
|
||||
agentUrl: resolvedAgentId ? buildChatUrl(resolvedAgentId) : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -540,9 +539,7 @@ export async function installGithubApp(
|
||||
committed: true,
|
||||
secretAction: "set",
|
||||
agentId: resolvedAgentId,
|
||||
agentUrl: resolvedAgentId
|
||||
? `https://app.letta.com/agents/${resolvedAgentId}`
|
||||
: null,
|
||||
agentUrl: resolvedAgentId ? buildChatUrl(resolvedAgentId) : null,
|
||||
};
|
||||
} finally {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
|
||||
@@ -9,6 +9,7 @@ import { settingsManager } from "../../settings-manager";
|
||||
import { getErrorMessage } from "../../utils/error";
|
||||
import { registerWithCloud } from "../../websocket/listen-register";
|
||||
import type { Buffers, Line } from "../helpers/accumulator";
|
||||
import { buildChatUrl } from "../helpers/appUrls";
|
||||
|
||||
// tiny helper for unique ids
|
||||
function uid(prefix: string) {
|
||||
@@ -179,11 +180,10 @@ export async function handleListen(
|
||||
const buildConnectionUrl = (connId: string): string => {
|
||||
if (!ctx.agentId) return "";
|
||||
|
||||
let url = `https://app.letta.com/agents/${ctx.agentId}?deviceId=${connId}`;
|
||||
if (ctx.conversationId) {
|
||||
url += `&conversationId=${ctx.conversationId}`;
|
||||
}
|
||||
return url;
|
||||
return buildChatUrl(ctx.agentId, {
|
||||
deviceId: connId,
|
||||
conversationId: ctx.conversationId ?? undefined,
|
||||
});
|
||||
};
|
||||
|
||||
// Start listen flow
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { ModelReasoningEffort } from "../../agent/model";
|
||||
import { DEFAULT_AGENT_NAME } from "../../constants";
|
||||
import { settingsManager } from "../../settings-manager";
|
||||
import { getVersion } from "../../version";
|
||||
import { buildAppUrl, buildChatUrl } from "../helpers/appUrls";
|
||||
import { useTerminalWidth } from "../hooks/useTerminalWidth";
|
||||
import { colors } from "./colors";
|
||||
import { Text } from "./Text";
|
||||
@@ -71,7 +72,7 @@ export const AgentInfoBar = memo(function AgentInfoBar({
|
||||
const isCloudUser = serverUrl?.includes("api.letta.com");
|
||||
const adeConversationUrl =
|
||||
agentId && agentId !== "loading"
|
||||
? `https://app.letta.com/agents/${agentId}${conversationId && conversationId !== "default" ? `?conversation=${conversationId}` : ""}`
|
||||
? buildChatUrl(agentId, { conversationId })
|
||||
: "";
|
||||
const showBottomBar = agentId && agentId !== "loading";
|
||||
const reasoningLabel = formatReasoningLabel(currentReasoningEffort);
|
||||
@@ -130,7 +131,7 @@ export const AgentInfoBar = memo(function AgentInfoBar({
|
||||
<Text>Open in ADE ↗</Text>
|
||||
</Link>
|
||||
<Text dimColor>· </Text>
|
||||
<Link url="https://app.letta.com/settings/organization/usage">
|
||||
<Link url={buildAppUrl("/settings/organization/usage")}>
|
||||
<Text>View usage ↗</Text>
|
||||
</Link>
|
||||
</Box>
|
||||
@@ -139,7 +140,7 @@ export const AgentInfoBar = memo(function AgentInfoBar({
|
||||
<Box width={rightWidth} flexShrink={1}>
|
||||
<Text dimColor wrap="truncate-end">
|
||||
{truncateText(
|
||||
`Open in ADE: ${adeConversationUrl} · Usage: https://app.letta.com/settings/organization/usage`,
|
||||
`Open in ADE: ${adeConversationUrl} · Usage: ${buildAppUrl("/settings/organization/usage")}`,
|
||||
rightWidth,
|
||||
)}
|
||||
</Text>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type TreeNode,
|
||||
} from "../../agent/memoryScanner";
|
||||
import { generateAndOpenMemoryViewer } from "../../web/generate-memory-viewer";
|
||||
import { buildChatUrl } from "../helpers/appUrls";
|
||||
import { useTerminalWidth } from "../hooks/useTerminalWidth";
|
||||
import { colors } from "./colors";
|
||||
import { Text } from "./Text";
|
||||
@@ -51,7 +52,7 @@ export function MemfsTreeViewer({
|
||||
const terminalWidth = useTerminalWidth();
|
||||
const solidLine = SOLID_LINE.repeat(Math.max(terminalWidth, 10));
|
||||
const isTmux = Boolean(process.env.TMUX);
|
||||
const adeUrl = `https://app.letta.com/agents/${agentId}?view=memory${conversationId && conversationId !== "default" ? `&conversation=${conversationId}` : ""}`;
|
||||
const adeUrl = buildChatUrl(agentId, { view: "memory", conversationId });
|
||||
|
||||
// State
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
@@ -4,6 +4,7 @@ import Link from "ink-link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getClient } from "../../agent/client";
|
||||
import { debugLog } from "../../utils/debug";
|
||||
import { buildChatUrl } from "../helpers/appUrls";
|
||||
import { useTerminalWidth } from "../hooks/useTerminalWidth";
|
||||
import { colors } from "./colors";
|
||||
import { MarkdownDisplay } from "./MarkdownDisplay";
|
||||
@@ -40,7 +41,7 @@ export function MemoryTabViewer({
|
||||
const terminalWidth = useTerminalWidth();
|
||||
const solidLine = SOLID_LINE.repeat(Math.max(terminalWidth, 10));
|
||||
const isTmux = Boolean(process.env.TMUX);
|
||||
const adeUrl = `https://app.letta.com/agents/${agentId}?view=memory${conversationId && conversationId !== "default" ? `&conversation=${conversationId}` : ""}`;
|
||||
const adeUrl = buildChatUrl(agentId, { view: "memory", conversationId });
|
||||
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||
const [scrollOffset, setScrollOffset] = useState(0);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SessionStatsSnapshot } from "../../agent/stats";
|
||||
import { buildAppUrl } from "../helpers/appUrls";
|
||||
import { formatCompact } from "../helpers/format";
|
||||
|
||||
export function formatDuration(ms: number): string {
|
||||
@@ -66,7 +67,7 @@ export function formatUsageStats({
|
||||
|
||||
outputLines.push(
|
||||
`Plan: [${balance.billing_tier}]`,
|
||||
"https://app.letta.com/settings/organization/usage",
|
||||
buildAppUrl("/settings/organization/usage"),
|
||||
"",
|
||||
`Available credits: ◎${formatNumber(totalCredits)} ($${toDollars(totalCredits)})`,
|
||||
`Monthly credits: ◎${formatNumber(monthlyCredits)} ($${toDollars(monthlyCredits)})`,
|
||||
|
||||
36
src/cli/helpers/appUrls.ts
Normal file
36
src/cli/helpers/appUrls.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
const APP_BASE = "https://app.letta.com";
|
||||
|
||||
/**
|
||||
* Build a chat URL for an agent, with optional conversation and extra query params.
|
||||
*/
|
||||
export function buildChatUrl(
|
||||
agentId: string,
|
||||
options?: {
|
||||
conversationId?: string;
|
||||
view?: string;
|
||||
deviceId?: string;
|
||||
},
|
||||
): string {
|
||||
const base = `${APP_BASE}/chat/${agentId}`;
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (options?.view) {
|
||||
params.set("view", options.view);
|
||||
}
|
||||
if (options?.deviceId) {
|
||||
params.set("deviceId", options.deviceId);
|
||||
}
|
||||
if (options?.conversationId && options.conversationId !== "default") {
|
||||
params.set("conversation", options.conversationId);
|
||||
}
|
||||
|
||||
const qs = params.toString();
|
||||
return qs ? `${base}?${qs}` : base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a non-agent app URL (e.g. settings pages).
|
||||
*/
|
||||
export function buildAppUrl(path: string): string {
|
||||
return `${APP_BASE}${path}`;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { APIError } from "@letta-ai/letta-client/core/error";
|
||||
import { buildAppUrl, buildChatUrl } from "./appUrls";
|
||||
import { getErrorContext } from "./errorContext";
|
||||
import { checkZaiError } from "./zaiErrors";
|
||||
|
||||
const LETTA_USAGE_URL = "https://app.letta.com/settings/organization/usage";
|
||||
const LETTA_AGENTS_URL =
|
||||
"https://app.letta.com/projects/default-project/agents";
|
||||
const LETTA_USAGE_URL = buildAppUrl("/settings/organization/usage");
|
||||
const LETTA_AGENTS_URL = buildAppUrl("/projects/default-project/agents");
|
||||
|
||||
function extractReasonList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
@@ -756,6 +756,6 @@ function createAgentLink(
|
||||
agentId: string,
|
||||
conversationId?: string,
|
||||
): string {
|
||||
const url = `https://app.letta.com/agents/${agentId}${conversationId && conversationId !== "default" ? `?conversation=${conversationId}` : ""}`;
|
||||
const url = buildChatUrl(agentId, { conversationId });
|
||||
return `View agent: \x1b]8;;${url}\x1b\\${agentId}\x1b]8;;\x1b\\ (run: ${runId})`;
|
||||
}
|
||||
|
||||
@@ -271,12 +271,12 @@ describe("success screen content", () => {
|
||||
secretAction: "set",
|
||||
agentId: "agent-aaaabbbb-cccc-dddd-eeee-ffffffffffff",
|
||||
agentUrl:
|
||||
"https://app.letta.com/agents/agent-aaaabbbb-cccc-dddd-eeee-ffffffffffff",
|
||||
"https://app.letta.com/chat/agent-aaaabbbb-cccc-dddd-eeee-ffffffffffff",
|
||||
};
|
||||
|
||||
test("agentUrl points to app.letta.com ADE", () => {
|
||||
test("agentUrl points to app.letta.com chat", () => {
|
||||
expect(baseResult.agentUrl).toBe(
|
||||
`https://app.letta.com/agents/${baseResult.agentId}`,
|
||||
`https://app.letta.com/chat/${baseResult.agentId}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -361,15 +361,15 @@ describe("success screen content", () => {
|
||||
expect(allText).not.toContain("Agent configured");
|
||||
});
|
||||
|
||||
test("agent URL uses correct ADE format for any agent ID", () => {
|
||||
test("agent URL uses correct chat format for any agent ID", () => {
|
||||
const agentId = "agent-12345678-abcd-efgh-ijkl-123456789012";
|
||||
const expectedUrl = `https://app.letta.com/agents/${agentId}`;
|
||||
const expectedUrl = `https://app.letta.com/chat/${agentId}`;
|
||||
|
||||
// This mirrors the logic in installGithubApp
|
||||
const agentUrl = agentId ? `https://app.letta.com/agents/${agentId}` : null;
|
||||
const agentUrl = agentId ? `https://app.letta.com/chat/${agentId}` : null;
|
||||
|
||||
expect(agentUrl).toBe(expectedUrl);
|
||||
expect(agentUrl).toContain("app.letta.com/agents/");
|
||||
expect(agentUrl).toContain("app.letta.com/chat/");
|
||||
expect(agentUrl).toContain(agentId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -232,7 +232,7 @@ describe("waitForBackgroundSubagentLink", () => {
|
||||
|
||||
setTimeout(() => {
|
||||
updateSubagent("subagent-link-1", {
|
||||
agentURL: "https://app.letta.com/agents/agent-123",
|
||||
agentURL: "https://app.letta.com/chat/agent-123",
|
||||
});
|
||||
}, 20);
|
||||
|
||||
|
||||
@@ -952,7 +952,7 @@ html.dark .warning-badge { background: hsl(42, 30%, 18%); color: hsl(42, 80%, 70
|
||||
} else {
|
||||
adeBase = 'https://app.letta.com';
|
||||
}
|
||||
agentIdEl.href = adeBase + '/agents/' + encodeURIComponent(agentId);
|
||||
agentIdEl.href = adeBase + '/chat/' + encodeURIComponent(agentId);
|
||||
agentIdEl.target = '_blank';
|
||||
}
|
||||
document.getElementById('generated-at').textContent = 'Generated ' + new Date(DATA.generatedAt).toLocaleString();
|
||||
|
||||
Reference in New Issue
Block a user