From 21ef411a00124b5ac95cab54566cb2ec4a7cf6f3 Mon Sep 17 00:00:00 2001 From: Sudip Date: Wed, 5 Aug 2026 11:14:32 +0100 Subject: [PATCH] feat: add LangSmith APAC region to setup and allowlist Offer APAC alongside US/EU in the init wizard, persist apac.api.smith.langchain.com in committed workspace config, and allowlist that host in sanitizeLangSmithApiBaseUrl. --- .changeset/langsmith-apac-region.md | 5 ++++ README.md | 4 +-- .../sources/langsmith/repo-config.ts | 3 +- src/connectors/sources/langsmith/setup.ts | 29 +++++++++++++++---- src/credentials.tsx | 6 ++++ test/langsmith-repo-config.test.ts | 3 ++ test/langsmith-setup.test.ts | 23 ++++++++++++++- 7 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 .changeset/langsmith-apac-region.md diff --git a/.changeset/langsmith-apac-region.md b/.changeset/langsmith-apac-region.md new file mode 100644 index 00000000..596ff841 --- /dev/null +++ b/.changeset/langsmith-apac-region.md @@ -0,0 +1,5 @@ +--- +"openwiki": patch +--- + +Add LangSmith APAC region (`apac.api.smith.langchain.com`) to setup and allowlisted hosts. diff --git a/README.md b/README.md index 9520a702..bf58781a 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ Connector secrets are referenced by env var name and stored in `~/.openwiki/.env The connectors above feed a `personal` wiki. The **LangSmith** connector instead enriches a `code` wiki: it pulls recent LangSmith traces (tool calls, outcomes, and latency) for the projects you choose through the official LangSmith SDK, so a repository's docs reflect how its code actually behaves at runtime, not just what the source says. -Configure it during `openwiki --init` in `code` mode. From the source menu, add LangSmith, pick your workspace region (US or EU), and list the projects to document. OpenWiki writes a committed `openwiki/.langsmith.json` that names the workspaces and projects (never the key itself), so every teammate and CI run documents the same set. The API key is read from the environment: +Configure it during `openwiki --init` in `code` mode. From the source menu, add LangSmith, pick your workspace region (US, EU, or APAC), and list the projects to document. OpenWiki writes a committed `openwiki/.langsmith.json` that names the workspaces and projects (never the key itself), so every teammate and CI run documents the same set. The API key is read from the environment: ```sh OPENWIKI_LANGSMITH_API_KEY="" @@ -136,7 +136,7 @@ OPENWIKI_LANGSMITH_API_KEY="" Locally the setup wizard saves this to `~/.openwiki/.env`. In CI, set it as a repository secret and export it for the run. > [!NOTE] -> A LangSmith key is workspace- and region-bound. To document projects across more than one workspace, add an entry per workspace, each with its own key named `OPENWIKI_LANGSMITH_API_KEY_2`, `OPENWIKI_LANGSMITH_API_KEY_3`, and so on. The connector only talks to the official US (`api.smith.langchain.com`) and EU (`eu.api.smith.langchain.com`) hosts. +> A LangSmith key is workspace- and region-bound. To document projects across more than one workspace, add an entry per workspace, each with its own key named `OPENWIKI_LANGSMITH_API_KEY_2`, `OPENWIKI_LANGSMITH_API_KEY_3`, and so on. The connector only talks to the official US (`api.smith.langchain.com`), EU (`eu.api.smith.langchain.com`), and APAC (`apac.api.smith.langchain.com`) hosts. ## How it stays yours diff --git a/src/connectors/sources/langsmith/repo-config.ts b/src/connectors/sources/langsmith/repo-config.ts index aea9ccce..cec3ad53 100644 --- a/src/connectors/sources/langsmith/repo-config.ts +++ b/src/connectors/sources/langsmith/repo-config.ts @@ -23,7 +23,7 @@ export interface LangSmithWorkspaceConfig { projects: LangSmithProjectConfig[]; /** - * Non-default API host for EU workspaces. + * Non-default API host for EU/APAC workspaces. * * @default the connector's default host (https://api.smith.langchain.com) */ @@ -49,6 +49,7 @@ export interface LangSmithRepoConfig { const ALLOWED_API_HOSTS = new Set([ "api.smith.langchain.com", "eu.api.smith.langchain.com", + "apac.api.smith.langchain.com", ]); /** diff --git a/src/connectors/sources/langsmith/setup.ts b/src/connectors/sources/langsmith/setup.ts index 5c9fc5e6..11f7aeaf 100644 --- a/src/connectors/sources/langsmith/setup.ts +++ b/src/connectors/sources/langsmith/setup.ts @@ -5,17 +5,32 @@ import { } from "./repo-config.js"; /** - * LangSmith workspace region. Maps to the two official API hosts the connector + * LangSmith workspace region. Maps to the official API hosts the connector * is allowlisted to talk to; the wizard offers this instead of a raw URL. */ -export type LangSmithRegion = "us" | "eu"; +export type LangSmithRegion = "us" | "eu" | "apac"; /** - * EU host, written to apiBaseUrl for EU workspaces. The US host is the connector - * default, so it is left out of the file entirely. + * Non-US hosts, written to apiBaseUrl for that region. The US host is the + * connector default, so it is left out of the file entirely. */ const EU_API_BASE_URL = "https://eu.api.smith.langchain.com"; +const APAC_API_BASE_URL = "https://apac.api.smith.langchain.com"; +const API_BASE_URL_BY_REGION: Record, string> = { + apac: APAC_API_BASE_URL, + eu: EU_API_BASE_URL, +}; + +function regionFromApiBaseUrl(apiBaseUrl: string | undefined): LangSmithRegion { + if (apiBaseUrl === EU_API_BASE_URL) { + return "eu"; + } + if (apiBaseUrl === APAC_API_BASE_URL) { + return "apac"; + } + return "us"; +} /** * Base env var name for the first workspace's key. Additional workspaces get * OPENWIKI_LANGSMITH_API_KEY_. @@ -54,7 +69,7 @@ export async function loadLangSmithSetup( return (config?.workspaces ?? []).map((workspace) => ({ apiKeyEnv: workspace.apiKeyEnv, projects: workspace.projects.map((project) => project.name), - region: workspace.apiBaseUrl === EU_API_BASE_URL ? "eu" : "us", + region: regionFromApiBaseUrl(workspace.apiBaseUrl), })); } @@ -87,7 +102,9 @@ export async function saveLangSmithSetup( cleaned.push({ apiKeyEnv: workspace.apiKeyEnv, projects, - ...(workspace.region === "eu" ? { apiBaseUrl: EU_API_BASE_URL } : {}), + ...(workspace.region === "us" + ? {} + : { apiBaseUrl: API_BASE_URL_BY_REGION[workspace.region] }), }); } if (cleaned.length === 0 && !existing) { diff --git a/src/credentials.tsx b/src/credentials.tsx index 6ad83b12..42f648b8 100644 --- a/src/credentials.tsx +++ b/src/credentials.tsx @@ -289,6 +289,12 @@ const LANGSMITH_REGION_OPTIONS = [ id: "eu", name: "EU", }, + { + description: "APAC workspaces.", + host: "https://apac.api.smith.langchain.com", + id: "apac", + name: "APAC", + }, ] as const satisfies readonly { description: string; host: string; diff --git a/test/langsmith-repo-config.test.ts b/test/langsmith-repo-config.test.ts index 761c2a0c..9532abea 100644 --- a/test/langsmith-repo-config.test.ts +++ b/test/langsmith-repo-config.test.ts @@ -192,6 +192,9 @@ describe("sanitizeLangSmithApiBaseUrl", () => { expect( sanitizeLangSmithApiBaseUrl("https://eu.api.smith.langchain.com"), ).toBe("https://eu.api.smith.langchain.com"); + expect( + sanitizeLangSmithApiBaseUrl("https://apac.api.smith.langchain.com"), + ).toBe("https://apac.api.smith.langchain.com"); }); test.each([ diff --git a/test/langsmith-setup.test.ts b/test/langsmith-setup.test.ts index 91b909d5..85290282 100644 --- a/test/langsmith-setup.test.ts +++ b/test/langsmith-setup.test.ts @@ -17,6 +17,7 @@ import { const REPO = "/repo"; const EU = "https://eu.api.smith.langchain.com"; +const APAC = "https://apac.api.smith.langchain.com"; beforeEach(() => { vi.clearAllMocks(); @@ -35,6 +36,11 @@ describe("loadLangSmithSetup", () => { apiKeyEnv: "OPENWIKI_LANGSMITH_API_KEY_2", projects: [{ name: "c" }], }, + { + apiBaseUrl: APAC, + apiKeyEnv: "OPENWIKI_LANGSMITH_API_KEY_3", + projects: [{ name: "d" }], + }, ], }); @@ -49,6 +55,11 @@ describe("loadLangSmithSetup", () => { projects: ["c"], region: "eu", }, + { + apiKeyEnv: "OPENWIKI_LANGSMITH_API_KEY_3", + projects: ["d"], + region: "apac", + }, ]); }); @@ -60,7 +71,7 @@ describe("loadLangSmithSetup", () => { }); describe("saveLangSmithSetup", () => { - test("writes workspaces, US omitting apiBaseUrl and EU including it", async () => { + test("writes workspaces, US omitting apiBaseUrl and EU/APAC including it", async () => { vi.mocked(readLangSmithRepoConfig).mockResolvedValue(undefined); await saveLangSmithSetup(REPO, [ @@ -74,6 +85,11 @@ describe("saveLangSmithSetup", () => { projects: ["b"], region: "eu", }, + { + apiKeyEnv: "OPENWIKI_LANGSMITH_API_KEY_3", + projects: ["c"], + region: "apac", + }, ]); expect(writeLangSmithRepoConfig).toHaveBeenCalledWith(REPO, { @@ -84,6 +100,11 @@ describe("saveLangSmithSetup", () => { apiKeyEnv: "OPENWIKI_LANGSMITH_API_KEY_2", projects: [{ name: "b" }], }, + { + apiBaseUrl: APAC, + apiKeyEnv: "OPENWIKI_LANGSMITH_API_KEY_3", + projects: [{ name: "c" }], + }, ], }); });