fix: always populate user-agent (#708)

This commit is contained in:
cthomas
2026-01-27 15:02:01 -08:00
committed by GitHub
parent a426e4bae3
commit edeb344ad9
10 changed files with 45 additions and 49 deletions

24
src/agent/http-headers.ts Normal file
View File

@@ -0,0 +1,24 @@
import packageJson from "../../package.json";
/**
* Get standard headers for manual HTTP calls to Letta API.
* Use this for any direct fetch() calls (not SDK calls).
*/
export function getLettaCodeHeaders(apiKey?: string): Record<string, string> {
return {
"Content-Type": "application/json",
"User-Agent": `letta-code/${packageJson.version}`,
"X-Letta-Source": "letta-code",
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
};
}
/**
* Get headers for MCP OAuth connections (includes Accept header for SSE).
*/
export function getMcpOAuthHeaders(apiKey: string): Record<string, string> {
return {
...getLettaCodeHeaders(apiKey),
Accept: "text/event-stream",
};
}

View File

@@ -41,6 +41,7 @@ import { getResumeData } from "../agent/check-approval";
import { getClient } from "../agent/client";
import { getCurrentAgentId, setCurrentAgentId } from "../agent/context";
import { type AgentProvenance, createAgent } from "../agent/create";
import { getLettaCodeHeaders } from "../agent/http-headers";
import { ISOLATED_BLOCK_LABELS } from "../agent/memory";
import {
detachMemoryFilesystemBlock,
@@ -1099,11 +1100,7 @@ export default function App({
const apiKey = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY;
const response = await fetch(`${baseURL}/v1/metadata/balance`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"X-Letta-Source": "letta-code",
},
headers: getLettaCodeHeaders(apiKey),
});
if (response.ok) {
@@ -5113,11 +5110,7 @@ export default function App({
const balanceResponse = await fetch(
`${baseURL}/v1/metadata/balance`,
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"X-Letta-Source": "letta-code",
},
headers: getLettaCodeHeaders(apiKey),
},
);
@@ -8754,9 +8747,7 @@ ${SYSTEM_REMINDER_CLOSE}
{
method: "POST",
headers: {
"Content-Type": "application/json",
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
"X-Letta-Source": "letta-code",
...getLettaCodeHeaders(apiKey),
"X-Letta-Code-Device-ID": settingsManager.getOrCreateDeviceId(),
},
body: JSON.stringify({

View File

@@ -4,6 +4,7 @@
*/
import { getServerUrl } from "../../agent/client";
import { getMcpOAuthHeaders } from "../../agent/http-headers";
import { settingsManager } from "../../settings-manager";
// Match backend's OauthStreamEvent enum
@@ -76,12 +77,7 @@ export async function connectMcpServer(
const response = await fetch(`${baseUrl}/v1/tools/mcp/servers/connect`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
Authorization: `Bearer ${apiKey}`,
"X-Letta-Source": "letta-code",
},
headers: getMcpOAuthHeaders(apiKey),
body: JSON.stringify(config),
signal: abortSignal,
});

View File

@@ -11,7 +11,7 @@ import {
setConversationId as setContextConversationId,
} from "./agent/context";
import type { AgentProvenance } from "./agent/create";
import { getLettaCodeHeaders } from "./agent/http-headers";
import { ensureSkillsBlocks, ISOLATED_BLOCK_LABELS } from "./agent/memory";
import { LETTA_CLOUD_API_URL } from "./auth/oauth";
import { ConversationSelector } from "./cli/components/ConversationSelector";
@@ -1478,7 +1478,7 @@ async function main(): Promise<void> {
const apiKey =
process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY;
const response = await fetch(`${baseURL}/v1/metadata/balance`, {
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
headers: getLettaCodeHeaders(apiKey),
});
if (response.ok) {
const data = (await response.json()) as {

View File

@@ -3,6 +3,7 @@
* Unified module for managing custom LLM provider connections
*/
import { getLettaCodeHeaders } from "../agent/http-headers";
import { LETTA_CLOUD_API_URL } from "../auth/oauth";
import { settingsManager } from "../settings-manager";
@@ -113,11 +114,7 @@ async function providersRequest<T>(
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"X-Letta-Source": "letta-code",
},
headers: getLettaCodeHeaders(apiKey),
...(body && { body: JSON.stringify(body) }),
});

View File

@@ -2,6 +2,7 @@
* Direct API calls to Letta for managing MiniMax provider
*/
import { getLettaCodeHeaders } from "../agent/http-headers";
import { LETTA_CLOUD_API_URL } from "../auth/oauth";
import { settingsManager } from "../settings-manager";
@@ -42,11 +43,7 @@ async function providersRequest<T>(
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"X-Letta-Source": "letta-code",
},
headers: getLettaCodeHeaders(apiKey),
...(body && { body: JSON.stringify(body) }),
});

View File

@@ -4,6 +4,7 @@
* (transforms OpenAI API format → ChatGPT backend API format)
*/
import { getLettaCodeHeaders } from "../agent/http-headers";
import { LETTA_CLOUD_API_URL } from "../auth/oauth";
import { settingsManager } from "../settings-manager";
@@ -72,11 +73,7 @@ async function providersRequest<T>(
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"X-Letta-Source": "letta-code",
},
headers: getLettaCodeHeaders(apiKey),
...(body && { body: JSON.stringify(body) }),
});

View File

@@ -2,6 +2,7 @@
* Direct API calls to Letta for managing Zai provider
*/
import { getLettaCodeHeaders } from "../agent/http-headers";
import { LETTA_CLOUD_API_URL } from "../auth/oauth";
import { settingsManager } from "../settings-manager";
@@ -42,11 +43,7 @@ async function providersRequest<T>(
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"X-Letta-Source": "letta-code",
},
headers: getLettaCodeHeaders(apiKey),
...(body && { body: JSON.stringify(body) }),
});

View File

@@ -21,6 +21,7 @@ import { createRequire } from "node:module";
import { homedir } from "node:os";
import { extname, join, relative } from "node:path";
import { getLettaCodeHeaders } from "../../../../agent/http-headers";
import type { BackupManifest } from "./backup-memory";
// Use createRequire for @letta-ai/letta-client so NODE_PATH is respected
@@ -119,7 +120,7 @@ async function restoreMemory(
const blocksResp = await fetch(
`${baseUrl}/v1/agents/${agentId}/core-memory`,
{
headers: { Authorization: `Bearer ${getApiKey()}` },
headers: getLettaCodeHeaders(getApiKey()),
},
);
if (!blocksResp.ok) {
@@ -180,10 +181,7 @@ async function restoreMemory(
const url = `${baseUrl}/v1/blocks/${existingBlock.id}`;
const resp = await fetch(url, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getApiKey()}`,
},
headers: getLettaCodeHeaders(getApiKey()),
body: JSON.stringify({ value: newValue }),
});
if (!resp.ok) {

View File

@@ -1,3 +1,4 @@
import { getLettaCodeHeaders } from "../agent/http-headers";
import { settingsManager } from "../settings-manager";
export interface TelemetryEvent {
@@ -399,9 +400,7 @@ class TelemetryManager {
{
method: "POST",
headers: {
"Content-Type": "application/json",
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
"X-Letta-Source": "letta-code",
...getLettaCodeHeaders(apiKey),
"X-Letta-Code-Device-ID": this.deviceId || "",
},
body: JSON.stringify({