feat(js-sdk): skip auto loading api key when set to null on sdk client create (#1793)

This commit is contained in:
Josh Rogers
2025-11-25 14:49:17 -05:00
committed by GitHub
parent 67de5eb276
commit 5ae7552cfe
3 changed files with 110 additions and 4 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@langchain/langgraph-sdk": patch
---
Adding support to skip auto loading api key when set to null on sdk client create
+17 -4
View File
@@ -122,19 +122,26 @@ function mergeHeaders(
/**
* Get the API key from the environment.
* Precedence:
* 1. explicit argument
* 1. explicit argument (if string)
* 2. LANGGRAPH_API_KEY
* 3. LANGSMITH_API_KEY
* 4. LANGCHAIN_API_KEY
*
* @param apiKey - Optional API key provided as an argument
* @param apiKey - API key provided as an argument. If null, skips environment lookup. If undefined, tries environment.
* @returns The API key if found, otherwise undefined
*/
export function getApiKey(apiKey?: string): string | undefined {
export function getApiKey(apiKey?: string | null): string | undefined {
// If explicitly set to null, skip auto-loading
if (apiKey === null) {
return undefined;
}
// If a string value is provided, use it
if (apiKey) {
return apiKey;
}
// If undefined, try to load from environment
const prefixes = ["LANGGRAPH", "LANGSMITH", "LANGCHAIN"];
for (const prefix of prefixes) {
@@ -173,7 +180,13 @@ export type RequestHook = (
export interface ClientConfig {
apiUrl?: string;
apiKey?: string;
/**
* API key for authentication.
* - If a string is provided, that key will be used
* - If undefined (default), the key will be auto-loaded from environment variables (LANGGRAPH_API_KEY, LANGSMITH_API_KEY, or LANGCHAIN_API_KEY)
* - If null, no API key will be set (skips auto-loading)
*/
apiKey?: string | null;
callerOptions?: AsyncCallerParams;
timeoutMs?: number;
defaultHeaders?: Record<string, HeaderValue>;
+88
View File
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { Client } from "../client.js";
import { overrideFetchImplementation } from "../singletons/fetch.js";
import * as envUtils from "../utils/env.js";
describe.each([["global"], ["mocked"]])(
"Client uses %s fetch",
@@ -197,5 +198,92 @@ describe.each([["global"], ["mocked"]])(
);
});
});
describe("API key auto-load", () => {
it("should auto-load API key from environment when apiKey is undefined", async () => {
const getEnvSpy = vi
.spyOn(envUtils, "getEnvironmentVariable")
.mockImplementation((name: string) => {
if (name === "LANGGRAPH_API_KEY") return "env-api-key";
return undefined;
});
const client = new Client();
await (client.threads as any).fetch("/test");
expect(expectedFetchMock).toHaveBeenNthCalledWith(
1,
expect.any(URL),
expect.objectContaining({
headers: expect.objectContaining({
"x-api-key": "env-api-key",
}),
})
);
const client2 = new Client({ apiKey: undefined });
await (client2.threads as any).fetch("/test");
expect(expectedFetchMock).toHaveBeenNthCalledWith(
2,
expect.any(URL),
expect.objectContaining({
headers: expect.objectContaining({
"x-api-key": "env-api-key",
}),
})
);
getEnvSpy.mockRestore();
});
it("should skip API key auto-load when apiKey is null", async () => {
const getEnvSpy = vi
.spyOn(envUtils, "getEnvironmentVariable")
.mockImplementation((name: string) => {
if (name === "LANGGRAPH_API_KEY") return "env-api-key";
return undefined;
});
const client = new Client({ apiKey: null });
await (client.threads as any).fetch("/test");
expect(expectedFetchMock).toHaveBeenCalledWith(
expect.any(URL),
expect.objectContaining({
headers: expect.not.objectContaining({
"x-api-key": expect.anything(),
}),
})
);
getEnvSpy.mockRestore();
});
it("should use explicit API key when provided as a string", async () => {
const getEnvSpy = vi
.spyOn(envUtils, "getEnvironmentVariable")
.mockImplementation((name: string) => {
if (name === "LANGGRAPH_API_KEY") return "env-api-key";
return undefined;
});
const client = new Client({
apiKey: "explicit-api-key",
});
await (client.threads as any).fetch("/test");
expect(expectedFetchMock).toHaveBeenCalledWith(
expect.any(URL),
expect.objectContaining({
headers: expect.objectContaining({
"x-api-key": "explicit-api-key",
}),
})
);
getEnvSpy.mockRestore();
});
});
}
);