@langchain/langgraph-sdk 1.11.1 → 1.12.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/client/base.cjs +7 -0
  2. package/dist/client/base.cjs.map +1 -1
  3. package/dist/client/base.d.cts.map +1 -1
  4. package/dist/client/base.d.ts.map +1 -1
  5. package/dist/client/base.js +7 -0
  6. package/dist/client/base.js.map +1 -1
  7. package/dist/client/stream/messages.cjs +54 -17
  8. package/dist/client/stream/messages.cjs.map +1 -1
  9. package/dist/client/stream/messages.d.cts +3 -0
  10. package/dist/client/stream/messages.d.cts.map +1 -1
  11. package/dist/client/stream/messages.d.ts +3 -0
  12. package/dist/client/stream/messages.d.ts.map +1 -1
  13. package/dist/client/stream/messages.js +54 -17
  14. package/dist/client/stream/messages.js.map +1 -1
  15. package/dist/stream/assembled-to-message.cjs +42 -8
  16. package/dist/stream/assembled-to-message.cjs.map +1 -1
  17. package/dist/stream/assembled-to-message.d.cts +11 -0
  18. package/dist/stream/assembled-to-message.d.cts.map +1 -1
  19. package/dist/stream/assembled-to-message.d.ts +11 -0
  20. package/dist/stream/assembled-to-message.d.ts.map +1 -1
  21. package/dist/stream/assembled-to-message.js +42 -8
  22. package/dist/stream/assembled-to-message.js.map +1 -1
  23. package/dist/stream/controller.cjs +39 -10
  24. package/dist/stream/controller.cjs.map +1 -1
  25. package/dist/stream/controller.d.cts +7 -6
  26. package/dist/stream/controller.d.cts.map +1 -1
  27. package/dist/stream/controller.d.ts +7 -6
  28. package/dist/stream/controller.d.ts.map +1 -1
  29. package/dist/stream/controller.js +37 -8
  30. package/dist/stream/controller.js.map +1 -1
  31. package/dist/stream/dispatch-config.cjs +23 -0
  32. package/dist/stream/dispatch-config.cjs.map +1 -0
  33. package/dist/stream/dispatch-config.js +23 -0
  34. package/dist/stream/dispatch-config.js.map +1 -0
  35. package/dist/stream/index.d.cts +1 -1
  36. package/dist/stream/index.d.ts +1 -1
  37. package/dist/stream/lifecycle-loading-tracker.cjs +8 -0
  38. package/dist/stream/lifecycle-loading-tracker.cjs.map +1 -1
  39. package/dist/stream/lifecycle-loading-tracker.js +8 -0
  40. package/dist/stream/lifecycle-loading-tracker.js.map +1 -1
  41. package/dist/stream/queue-adapter-agent-server.cjs +204 -0
  42. package/dist/stream/queue-adapter-agent-server.cjs.map +1 -0
  43. package/dist/stream/queue-adapter-agent-server.js +203 -0
  44. package/dist/stream/queue-adapter-agent-server.js.map +1 -0
  45. package/dist/stream/queue-adapter-local.cjs +50 -0
  46. package/dist/stream/queue-adapter-local.cjs.map +1 -0
  47. package/dist/stream/queue-adapter-local.js +49 -0
  48. package/dist/stream/queue-adapter-local.js.map +1 -0
  49. package/dist/stream/queue-adapter.cjs +7 -0
  50. package/dist/stream/queue-adapter.cjs.map +1 -0
  51. package/dist/stream/{submit-coordinator.d.cts → queue-adapter.d.cts} +6 -9
  52. package/dist/stream/queue-adapter.d.cts.map +1 -0
  53. package/dist/stream/queue-adapter.d.ts +24 -0
  54. package/dist/stream/queue-adapter.d.ts.map +1 -0
  55. package/dist/stream/queue-adapter.js +7 -0
  56. package/dist/stream/queue-adapter.js.map +1 -0
  57. package/dist/stream/submit-coordinator.cjs +82 -86
  58. package/dist/stream/submit-coordinator.cjs.map +1 -1
  59. package/dist/stream/submit-coordinator.d.ts +1 -26
  60. package/dist/stream/submit-coordinator.js +82 -85
  61. package/dist/stream/submit-coordinator.js.map +1 -1
  62. package/dist/stream/types.d.cts +25 -4
  63. package/dist/stream/types.d.cts.map +1 -1
  64. package/dist/stream/types.d.ts +25 -4
  65. package/dist/stream/types.d.ts.map +1 -1
  66. package/package.json +1 -1
  67. package/dist/stream/submit-coordinator.d.cts.map +0 -1
  68. package/dist/stream/submit-coordinator.d.ts.map +0 -1
@@ -120,6 +120,13 @@ var BaseClient = class {
120
120
  if (mutatedOptions.params) {
121
121
  for (const [key, value] of Object.entries(mutatedOptions.params)) {
122
122
  if (value == null) continue;
123
+ if (Array.isArray(value)) {
124
+ for (const item of value) {
125
+ if (item == null) continue;
126
+ targetUrl.searchParams.append(key, typeof item === "string" || typeof item === "number" ? item.toString() : JSON.stringify(item));
127
+ }
128
+ continue;
129
+ }
123
130
  const strValue = typeof value === "string" || typeof value === "number" ? value.toString() : JSON.stringify(value);
124
131
  targetUrl.searchParams.append(key, strValue);
125
132
  }
@@ -1 +1 @@
1
- {"version":3,"file":"base.cjs","names":["getEnvironmentVariable","AsyncCaller","mergeSignals","#performFetch","#performFetchWithResponse","BytesLineDecoder","idleReconnectStream","SSEDecoder","streamWithRetry"],"sources":["../../src/client/base.ts"],"sourcesContent":["import { AsyncCaller, AsyncCallerParams } from \"../utils/async_caller.js\";\nimport { getEnvironmentVariable } from \"../utils/env.js\";\nimport { mergeSignals } from \"../utils/signals.js\";\nimport { BytesLineDecoder, SSEDecoder } from \"../utils/sse.js\";\nimport {\n streamWithRetry,\n idleReconnectStream,\n type IdleReconnectMode,\n StreamRequestParams,\n DEFAULT_IDLE_RECONNECT,\n} from \"../utils/stream.js\";\nimport { DEFAULT_MAX_RECONNECT_ATTEMPTS } from \"../utils/reconnect.js\";\nimport type { StreamProtocol } from \"../types.js\";\n\nexport type HeaderValue = string | undefined | null;\n\nexport function* iterateHeaders(\n headers: HeadersInit | Record<string, HeaderValue>\n): IterableIterator<[string, string | null]> {\n let iter: Iterable<(HeaderValue | HeaderValue | null[])[]>;\n let shouldClear = false;\n\n // eslint-disable-next-line no-instanceof/no-instanceof\n if (headers instanceof Headers) {\n const entries: [string, string][] = [];\n headers.forEach((value, name) => {\n entries.push([name, value]);\n });\n iter = entries;\n } else if (Array.isArray(headers)) {\n iter = headers;\n } else {\n shouldClear = true;\n iter = Object.entries(headers ?? {});\n }\n\n for (const item of iter) {\n const name = item[0];\n if (typeof name !== \"string\")\n throw new TypeError(\n `Expected header name to be a string, got ${typeof name}`\n );\n const values = Array.isArray(item[1]) ? item[1] : [item[1]];\n let didClear = false;\n\n for (const value of values) {\n if (value === undefined) continue;\n\n if (shouldClear && !didClear) {\n didClear = true;\n yield [name, null];\n }\n yield [name, value];\n }\n }\n}\n\nexport function mergeHeaders(\n ...headerObjects: (\n | HeadersInit\n | Record<string, HeaderValue>\n | undefined\n | null\n )[]\n) {\n const outputHeaders = new Headers();\n for (const headers of headerObjects) {\n if (!headers) continue;\n for (const [name, value] of iterateHeaders(headers)) {\n if (value === null) outputHeaders.delete(name);\n else outputHeaders.append(name, value);\n }\n }\n const headerEntries: [string, string][] = [];\n outputHeaders.forEach((value, name) => {\n headerEntries.push([name, value]);\n });\n return Object.fromEntries(headerEntries);\n}\n\n/**\n * Get the API key from the environment.\n * Precedence:\n * 1. explicit argument (if string)\n * 2. LANGGRAPH_API_KEY\n * 3. LANGSMITH_API_KEY\n * 4. LANGCHAIN_API_KEY\n *\n * @param apiKey - API key provided as an argument. If null, skips environment lookup. If undefined, tries environment.\n * @returns The API key if found, otherwise undefined\n */\nexport function getApiKey(apiKey?: string | null): string | undefined {\n if (apiKey === null) {\n return undefined;\n }\n\n if (apiKey) {\n return apiKey;\n }\n\n const prefixes = [\"LANGGRAPH\", \"LANGSMITH\", \"LANGCHAIN\"];\n\n for (const prefix of prefixes) {\n const envKey = getEnvironmentVariable(`${prefix}_API_KEY`);\n if (envKey) {\n return envKey.trim().replace(/^[\"']|[\"']$/g, \"\");\n }\n }\n\n return undefined;\n}\n\nexport type RequestHook = (\n url: URL,\n init: RequestInit\n) => Promise<RequestInit> | RequestInit;\n\n/**\n * Configuration for {@link BaseClient} and the exported LangGraph SDK\n * {@link Client}.\n */\nexport interface ClientConfig {\n /**\n * Base URL of the LangGraph API server.\n *\n * Defaults to `http://localhost:8123`, unless the runtime provides a\n * `langgraph_api:url` global override.\n */\n apiUrl?: string;\n /**\n * API key for authentication.\n * - If a string is provided, that key will be used\n * - If undefined (default), the key will be auto-loaded from environment variables (LANGGRAPH_API_KEY, LANGSMITH_API_KEY, or LANGCHAIN_API_KEY)\n * - If null, no API key will be set (skips auto-loading)\n */\n apiKey?: string | null;\n /**\n * Options forwarded to the internal {@link AsyncCaller}, such as retry,\n * concurrency, or custom `fetch` behavior.\n */\n callerOptions?: AsyncCallerParams;\n /**\n * Default timeout, in milliseconds, applied to client requests.\n *\n * Per-request `timeoutMs` values override this default. Passing `null`\n * at the request level disables the configured timeout for that request.\n */\n timeoutMs?: number;\n /**\n * Headers applied to every request.\n *\n * The configured API key, when present, is added as the `x-api-key`\n * header after these defaults are initialized.\n */\n defaultHeaders?: Record<string, HeaderValue>;\n /**\n * Hook for inspecting or mutating a request before it is sent.\n *\n * Receives the resolved URL and prepared `RequestInit`; return the\n * original init or a replacement object to continue the request.\n */\n onRequest?: RequestHook;\n /**\n * Streaming protocol used by stream-capable endpoints.\n *\n * Defaults to `\"legacy\"` for backwards compatibility.\n */\n streamProtocol?: StreamProtocol;\n}\n\nexport class BaseClient {\n protected asyncCaller: AsyncCaller;\n\n protected timeoutMs: number | undefined;\n\n protected apiUrl: string;\n\n protected defaultHeaders: Record<string, HeaderValue>;\n\n protected onRequest?: RequestHook;\n\n protected streamProtocol: StreamProtocol;\n\n constructor(config?: ClientConfig) {\n const callerOptions = {\n maxRetries: 4,\n maxConcurrency: 4,\n ...config?.callerOptions,\n };\n\n let defaultApiUrl = \"http://localhost:8123\";\n if (\n !config?.apiUrl &&\n typeof globalThis === \"object\" &&\n globalThis != null\n ) {\n const fetchSmb = Symbol.for(\"langgraph_api:fetch\");\n const urlSmb = Symbol.for(\"langgraph_api:url\");\n\n const global = globalThis as unknown as {\n [fetchSmb]?: typeof fetch;\n [urlSmb]?: string;\n };\n\n if (global[fetchSmb]) callerOptions.fetch ??= global[fetchSmb];\n if (global[urlSmb]) defaultApiUrl = global[urlSmb];\n }\n\n this.asyncCaller = new AsyncCaller(callerOptions);\n this.timeoutMs = config?.timeoutMs;\n\n this.apiUrl = config?.apiUrl?.replace(/\\/$/, \"\") || defaultApiUrl;\n this.defaultHeaders = config?.defaultHeaders || {};\n this.onRequest = config?.onRequest;\n this.streamProtocol = config?.streamProtocol ?? \"legacy\";\n const apiKey = getApiKey(config?.apiKey);\n if (apiKey) {\n this.defaultHeaders[\"x-api-key\"] = apiKey;\n }\n }\n\n protected prepareFetchOptions(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n withResponse?: boolean;\n dedupe?: boolean;\n }\n ): [url: URL, init: RequestInit] {\n const mutatedOptions = {\n ...options,\n headers: mergeHeaders(this.defaultHeaders, options?.headers),\n };\n\n if (mutatedOptions.json) {\n mutatedOptions.body = JSON.stringify(mutatedOptions.json);\n mutatedOptions.headers = mergeHeaders(mutatedOptions.headers, {\n \"content-type\": \"application/json\",\n });\n delete mutatedOptions.json;\n }\n\n if (mutatedOptions.withResponse) {\n delete mutatedOptions.withResponse;\n }\n\n if (\"dedupe\" in mutatedOptions) {\n delete mutatedOptions.dedupe;\n }\n\n let timeoutSignal: AbortSignal | null = null;\n if (typeof options?.timeoutMs !== \"undefined\") {\n if (options.timeoutMs != null) {\n timeoutSignal = AbortSignal.timeout(options.timeoutMs);\n }\n } else if (this.timeoutMs != null) {\n timeoutSignal = AbortSignal.timeout(this.timeoutMs);\n }\n\n mutatedOptions.signal = mergeSignals(timeoutSignal, mutatedOptions.signal);\n const targetUrl = new URL(`${this.apiUrl}${path}`);\n\n if (mutatedOptions.params) {\n for (const [key, value] of Object.entries(mutatedOptions.params)) {\n if (value == null) continue;\n\n const strValue =\n typeof value === \"string\" || typeof value === \"number\"\n ? value.toString()\n : JSON.stringify(value);\n\n targetUrl.searchParams.append(key, strValue);\n }\n delete mutatedOptions.params;\n }\n\n return [targetUrl, mutatedOptions];\n }\n\n protected async fetch<T>(\n path: string,\n options: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse: true;\n }\n ): Promise<[T, Response]>;\n\n protected async fetch<T>(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse?: false;\n dedupe?: boolean;\n }\n ): Promise<T>;\n\n protected async fetch<T>(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse?: boolean;\n dedupe?: boolean;\n }\n ): Promise<T | [T, Response]> {\n const [url, init] = this.prepareFetchOptions(path, options);\n\n /**\n * Coalesce concurrent, identical idempotent reads onto a single\n * in-flight request. Only engaged when the caller opts in\n * (`dedupe: true`), is not asking for the raw `Response`, did not\n * supply its own `AbortSignal` (sharing a request across consumers\n * must never let one consumer's abort cancel another's), and no\n * `onRequest` hook is configured.\n *\n * `onRequest` is excluded because it can inject per-request headers\n * (e.g. a freshly-minted `Authorization` bearer) that are not\n * visible until *after* it runs — i.e. after the dedupe key is\n * computed — so two requests that look identical here could be sent\n * with different credentials. Coalescing them would let one\n * consumer receive a response fetched with another's auth.\n */\n const canDedupe =\n options?.dedupe === true &&\n options?.withResponse !== true &&\n options?.signal == null &&\n this.onRequest == null;\n\n if (canDedupe) {\n const body = typeof init.body === \"string\" ? init.body : \"\";\n /**\n * The key must capture the FULL request identity, including every\n * prepared header. `inFlightReads` is module-scoped across all\n * `Client` instances, so omitting headers would let two clients\n * pointed at the same URL/thread but using different credentials\n * (Authorization, custom auth headers, tenant-scoping defaults, …)\n * share one in-flight promise — a cross-tenant data leak.\n */\n const headers = serializeHeaders(init.headers);\n const key = `${init.method ?? \"GET\"} ${url.toString()} ${body} ${headers}`;\n const existing = inFlightReads.get(key);\n if (existing != null) return existing as Promise<T>;\n\n const promise = this.#performFetch<T>(url, init);\n inFlightReads.set(key, promise);\n const clear = () => {\n if (inFlightReads.get(key) === promise) inFlightReads.delete(key);\n };\n promise.then(clear, clear);\n return promise;\n }\n\n const [body, response] = await this.#performFetchWithResponse<T>(url, init);\n if (options?.withResponse) {\n return [body, response];\n }\n return body;\n }\n\n /**\n * Issue the prepared request (applying the `onRequest` hook) and\n * resolve the parsed body. Shared by the deduped and direct paths.\n */\n async #performFetch<T>(url: URL, init: RequestInit): Promise<T> {\n const [body] = await this.#performFetchWithResponse<T>(url, init);\n return body;\n }\n\n async #performFetchWithResponse<T>(\n url: URL,\n init: RequestInit\n ): Promise<[T, Response]> {\n let finalInit = init;\n if (this.onRequest) {\n finalInit = await this.onRequest(url, init);\n }\n\n const response = await this.asyncCaller.fetch(url.toString(), finalInit);\n\n const body = await (async () => {\n if (response.status === 202 || response.status === 204) {\n return undefined as T;\n }\n return response.json() as Promise<T>;\n })();\n\n return [body, response];\n }\n\n protected async *streamWithRetry<\n T extends { id?: string; event: string; data: unknown },\n >(config: {\n endpoint: string;\n method?: string;\n signal?: AbortSignal;\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n json?: unknown;\n maxRetries?: number;\n idleReconnect?: IdleReconnectMode;\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n onInitialResponse?: (response: Response) => void | Promise<void>;\n }): AsyncGenerator<T> {\n const makeRequest = async (reconnectParams?: StreamRequestParams) => {\n const requestEndpoint = reconnectParams?.reconnectPath || config.endpoint;\n\n const isReconnect = !!reconnectParams?.reconnectPath;\n const method = isReconnect ? \"GET\" : config.method || \"GET\";\n\n const requestHeaders =\n isReconnect && reconnectParams?.lastEventId\n ? { ...config.headers, \"Last-Event-ID\": reconnectParams.lastEventId }\n : config.headers;\n\n // oxlint-disable-next-line prefer-const -- init is reassigned by onRequest hook\n let [url, init] = this.prepareFetchOptions(requestEndpoint, {\n method,\n timeoutMs: null,\n signal: config.signal,\n headers: requestHeaders,\n params: config.params,\n json: isReconnect ? undefined : config.json,\n });\n\n if (this.onRequest != null) {\n init = await this.onRequest(url, init);\n }\n\n const response = await this.asyncCaller.fetch(url.toString(), init);\n if (!response.body) {\n throw new Error(\"Expected response body from stream endpoint\");\n }\n\n if (!isReconnect && config.onInitialResponse) {\n await config.onInitialResponse(response);\n }\n\n // Insert the idle watchdog on the line stream (between the byte-line\n // decoder and the SSE decoder) so it can both reset on any line and\n // recognise `:` keep-alive heartbeats to drive `\"auto\"` mode. The SSE\n // decoder downstream discards those comment lines.\n const idleMode = config.idleReconnect ?? DEFAULT_IDLE_RECONNECT;\n const enableIdle = idleMode === \"auto\" || idleMode > 0;\n\n const lines = response.body.pipeThrough(BytesLineDecoder());\n const watched = enableIdle\n ? lines.pipeThrough(idleReconnectStream({ mode: idleMode }))\n : lines;\n const stream: ReadableStream<T> = watched.pipeThrough(\n SSEDecoder()\n ) as ReadableStream<T>;\n\n return { response, stream };\n };\n\n yield* streamWithRetry(makeRequest, {\n maxRetries: config.maxRetries ?? DEFAULT_MAX_RECONNECT_ATTEMPTS,\n signal: config.signal,\n onReconnect: config.onReconnect,\n });\n }\n}\n\nexport const REGEX_RUN_METADATA =\n /(\\/threads\\/(?<thread_id>.+))?\\/runs\\/(?<run_id>.+)/;\n\nexport function getRunMetadataFromResponse(\n response: Response\n): { run_id: string; thread_id?: string } | undefined {\n const contentLocation = response.headers.get(\"Content-Location\");\n if (!contentLocation) return undefined;\n\n const match = REGEX_RUN_METADATA.exec(contentLocation);\n\n if (!match?.groups?.run_id) return undefined;\n return {\n run_id: match.groups.run_id,\n thread_id: match.groups.thread_id || undefined,\n };\n}\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * Module-scoped, in-flight-only coalescing map for idempotent reads.\n *\n * Two independently-constructed clients (e.g. a React component that\n * remounts under Suspense / a reachability state flip, each minting a\n * fresh `Client`) can fire the *same* `getState` / `getHistory` read a\n * few milliseconds apart, before the first has resolved. Without\n * coalescing each pays the full round-trip — the duplicate\n * `threads/{id}/state` and `threads/{id}/history` requests seen on\n * reconnect.\n *\n * Keyed by `method + url + body + auth`, entries live only while a\n * request is in flight and are removed the moment it settles. This is\n * deliberately *not* a result cache: there is no TTL and no stored\n * payload, so it cannot serve stale data — it only ever shares a\n * promise that is already on the wire. Opt-in per call via\n * `{ dedupe: true }`, and skipped whenever the caller supplies its own\n * `AbortSignal` (so one consumer aborting can never cancel another's\n * read).\n */\nconst inFlightReads = new Map<string, Promise<unknown>>();\n\n/**\n * Deterministically serialize a prepared request's headers into a\n * stable string for use in the {@link inFlightReads} dedupe key. Header\n * names are normalized and sorted so ordering differences never produce\n * a different key, and every header (not just `x-api-key`) is included\n * so requests carrying different credentials never collide.\n */\nfunction serializeHeaders(headers: RequestInit[\"headers\"]): string {\n const normalized = mergeHeaders(\n headers as Record<string, HeaderValue> | undefined\n );\n return Object.keys(normalized)\n .sort()\n .map((name) => `${name}:${normalized[name]}`)\n .join(\"\\n\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"base.cjs","names":["getEnvironmentVariable","AsyncCaller","mergeSignals","#performFetch","#performFetchWithResponse","BytesLineDecoder","idleReconnectStream","SSEDecoder","streamWithRetry"],"sources":["../../src/client/base.ts"],"sourcesContent":["import { AsyncCaller, AsyncCallerParams } from \"../utils/async_caller.js\";\nimport { getEnvironmentVariable } from \"../utils/env.js\";\nimport { mergeSignals } from \"../utils/signals.js\";\nimport { BytesLineDecoder, SSEDecoder } from \"../utils/sse.js\";\nimport {\n streamWithRetry,\n idleReconnectStream,\n type IdleReconnectMode,\n StreamRequestParams,\n DEFAULT_IDLE_RECONNECT,\n} from \"../utils/stream.js\";\nimport { DEFAULT_MAX_RECONNECT_ATTEMPTS } from \"../utils/reconnect.js\";\nimport type { StreamProtocol } from \"../types.js\";\n\nexport type HeaderValue = string | undefined | null;\n\nexport function* iterateHeaders(\n headers: HeadersInit | Record<string, HeaderValue>\n): IterableIterator<[string, string | null]> {\n let iter: Iterable<(HeaderValue | HeaderValue | null[])[]>;\n let shouldClear = false;\n\n // eslint-disable-next-line no-instanceof/no-instanceof\n if (headers instanceof Headers) {\n const entries: [string, string][] = [];\n headers.forEach((value, name) => {\n entries.push([name, value]);\n });\n iter = entries;\n } else if (Array.isArray(headers)) {\n iter = headers;\n } else {\n shouldClear = true;\n iter = Object.entries(headers ?? {});\n }\n\n for (const item of iter) {\n const name = item[0];\n if (typeof name !== \"string\")\n throw new TypeError(\n `Expected header name to be a string, got ${typeof name}`\n );\n const values = Array.isArray(item[1]) ? item[1] : [item[1]];\n let didClear = false;\n\n for (const value of values) {\n if (value === undefined) continue;\n\n if (shouldClear && !didClear) {\n didClear = true;\n yield [name, null];\n }\n yield [name, value];\n }\n }\n}\n\nexport function mergeHeaders(\n ...headerObjects: (\n | HeadersInit\n | Record<string, HeaderValue>\n | undefined\n | null\n )[]\n) {\n const outputHeaders = new Headers();\n for (const headers of headerObjects) {\n if (!headers) continue;\n for (const [name, value] of iterateHeaders(headers)) {\n if (value === null) outputHeaders.delete(name);\n else outputHeaders.append(name, value);\n }\n }\n const headerEntries: [string, string][] = [];\n outputHeaders.forEach((value, name) => {\n headerEntries.push([name, value]);\n });\n return Object.fromEntries(headerEntries);\n}\n\n/**\n * Get the API key from the environment.\n * Precedence:\n * 1. explicit argument (if string)\n * 2. LANGGRAPH_API_KEY\n * 3. LANGSMITH_API_KEY\n * 4. LANGCHAIN_API_KEY\n *\n * @param apiKey - API key provided as an argument. If null, skips environment lookup. If undefined, tries environment.\n * @returns The API key if found, otherwise undefined\n */\nexport function getApiKey(apiKey?: string | null): string | undefined {\n if (apiKey === null) {\n return undefined;\n }\n\n if (apiKey) {\n return apiKey;\n }\n\n const prefixes = [\"LANGGRAPH\", \"LANGSMITH\", \"LANGCHAIN\"];\n\n for (const prefix of prefixes) {\n const envKey = getEnvironmentVariable(`${prefix}_API_KEY`);\n if (envKey) {\n return envKey.trim().replace(/^[\"']|[\"']$/g, \"\");\n }\n }\n\n return undefined;\n}\n\nexport type RequestHook = (\n url: URL,\n init: RequestInit\n) => Promise<RequestInit> | RequestInit;\n\n/**\n * Configuration for {@link BaseClient} and the exported LangGraph SDK\n * {@link Client}.\n */\nexport interface ClientConfig {\n /**\n * Base URL of the LangGraph API server.\n *\n * Defaults to `http://localhost:8123`, unless the runtime provides a\n * `langgraph_api:url` global override.\n */\n apiUrl?: string;\n /**\n * API key for authentication.\n * - If a string is provided, that key will be used\n * - If undefined (default), the key will be auto-loaded from environment variables (LANGGRAPH_API_KEY, LANGSMITH_API_KEY, or LANGCHAIN_API_KEY)\n * - If null, no API key will be set (skips auto-loading)\n */\n apiKey?: string | null;\n /**\n * Options forwarded to the internal {@link AsyncCaller}, such as retry,\n * concurrency, or custom `fetch` behavior.\n */\n callerOptions?: AsyncCallerParams;\n /**\n * Default timeout, in milliseconds, applied to client requests.\n *\n * Per-request `timeoutMs` values override this default. Passing `null`\n * at the request level disables the configured timeout for that request.\n */\n timeoutMs?: number;\n /**\n * Headers applied to every request.\n *\n * The configured API key, when present, is added as the `x-api-key`\n * header after these defaults are initialized.\n */\n defaultHeaders?: Record<string, HeaderValue>;\n /**\n * Hook for inspecting or mutating a request before it is sent.\n *\n * Receives the resolved URL and prepared `RequestInit`; return the\n * original init or a replacement object to continue the request.\n */\n onRequest?: RequestHook;\n /**\n * Streaming protocol used by stream-capable endpoints.\n *\n * Defaults to `\"legacy\"` for backwards compatibility.\n */\n streamProtocol?: StreamProtocol;\n}\n\nexport class BaseClient {\n protected asyncCaller: AsyncCaller;\n\n protected timeoutMs: number | undefined;\n\n protected apiUrl: string;\n\n protected defaultHeaders: Record<string, HeaderValue>;\n\n protected onRequest?: RequestHook;\n\n protected streamProtocol: StreamProtocol;\n\n constructor(config?: ClientConfig) {\n const callerOptions = {\n maxRetries: 4,\n maxConcurrency: 4,\n ...config?.callerOptions,\n };\n\n let defaultApiUrl = \"http://localhost:8123\";\n if (\n !config?.apiUrl &&\n typeof globalThis === \"object\" &&\n globalThis != null\n ) {\n const fetchSmb = Symbol.for(\"langgraph_api:fetch\");\n const urlSmb = Symbol.for(\"langgraph_api:url\");\n\n const global = globalThis as unknown as {\n [fetchSmb]?: typeof fetch;\n [urlSmb]?: string;\n };\n\n if (global[fetchSmb]) callerOptions.fetch ??= global[fetchSmb];\n if (global[urlSmb]) defaultApiUrl = global[urlSmb];\n }\n\n this.asyncCaller = new AsyncCaller(callerOptions);\n this.timeoutMs = config?.timeoutMs;\n\n this.apiUrl = config?.apiUrl?.replace(/\\/$/, \"\") || defaultApiUrl;\n this.defaultHeaders = config?.defaultHeaders || {};\n this.onRequest = config?.onRequest;\n this.streamProtocol = config?.streamProtocol ?? \"legacy\";\n const apiKey = getApiKey(config?.apiKey);\n if (apiKey) {\n this.defaultHeaders[\"x-api-key\"] = apiKey;\n }\n }\n\n protected prepareFetchOptions(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n withResponse?: boolean;\n dedupe?: boolean;\n }\n ): [url: URL, init: RequestInit] {\n const mutatedOptions = {\n ...options,\n headers: mergeHeaders(this.defaultHeaders, options?.headers),\n };\n\n if (mutatedOptions.json) {\n mutatedOptions.body = JSON.stringify(mutatedOptions.json);\n mutatedOptions.headers = mergeHeaders(mutatedOptions.headers, {\n \"content-type\": \"application/json\",\n });\n delete mutatedOptions.json;\n }\n\n if (mutatedOptions.withResponse) {\n delete mutatedOptions.withResponse;\n }\n\n if (\"dedupe\" in mutatedOptions) {\n delete mutatedOptions.dedupe;\n }\n\n let timeoutSignal: AbortSignal | null = null;\n if (typeof options?.timeoutMs !== \"undefined\") {\n if (options.timeoutMs != null) {\n timeoutSignal = AbortSignal.timeout(options.timeoutMs);\n }\n } else if (this.timeoutMs != null) {\n timeoutSignal = AbortSignal.timeout(this.timeoutMs);\n }\n\n mutatedOptions.signal = mergeSignals(timeoutSignal, mutatedOptions.signal);\n const targetUrl = new URL(`${this.apiUrl}${path}`);\n\n if (mutatedOptions.params) {\n for (const [key, value] of Object.entries(mutatedOptions.params)) {\n if (value == null) continue;\n\n // Send each array element as its own repeated key=value pair.\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item == null) continue;\n targetUrl.searchParams.append(\n key,\n typeof item === \"string\" || typeof item === \"number\"\n ? item.toString()\n : JSON.stringify(item)\n );\n }\n continue;\n }\n\n const strValue =\n typeof value === \"string\" || typeof value === \"number\"\n ? value.toString()\n : JSON.stringify(value);\n\n targetUrl.searchParams.append(key, strValue);\n }\n delete mutatedOptions.params;\n }\n\n return [targetUrl, mutatedOptions];\n }\n\n protected async fetch<T>(\n path: string,\n options: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse: true;\n }\n ): Promise<[T, Response]>;\n\n protected async fetch<T>(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse?: false;\n dedupe?: boolean;\n }\n ): Promise<T>;\n\n protected async fetch<T>(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse?: boolean;\n dedupe?: boolean;\n }\n ): Promise<T | [T, Response]> {\n const [url, init] = this.prepareFetchOptions(path, options);\n\n /**\n * Coalesce concurrent, identical idempotent reads onto a single\n * in-flight request. Only engaged when the caller opts in\n * (`dedupe: true`), is not asking for the raw `Response`, did not\n * supply its own `AbortSignal` (sharing a request across consumers\n * must never let one consumer's abort cancel another's), and no\n * `onRequest` hook is configured.\n *\n * `onRequest` is excluded because it can inject per-request headers\n * (e.g. a freshly-minted `Authorization` bearer) that are not\n * visible until *after* it runs — i.e. after the dedupe key is\n * computed — so two requests that look identical here could be sent\n * with different credentials. Coalescing them would let one\n * consumer receive a response fetched with another's auth.\n */\n const canDedupe =\n options?.dedupe === true &&\n options?.withResponse !== true &&\n options?.signal == null &&\n this.onRequest == null;\n\n if (canDedupe) {\n const body = typeof init.body === \"string\" ? init.body : \"\";\n /**\n * The key must capture the FULL request identity, including every\n * prepared header. `inFlightReads` is module-scoped across all\n * `Client` instances, so omitting headers would let two clients\n * pointed at the same URL/thread but using different credentials\n * (Authorization, custom auth headers, tenant-scoping defaults, …)\n * share one in-flight promise — a cross-tenant data leak.\n */\n const headers = serializeHeaders(init.headers);\n const key = `${init.method ?? \"GET\"} ${url.toString()} ${body} ${headers}`;\n const existing = inFlightReads.get(key);\n if (existing != null) return existing as Promise<T>;\n\n const promise = this.#performFetch<T>(url, init);\n inFlightReads.set(key, promise);\n const clear = () => {\n if (inFlightReads.get(key) === promise) inFlightReads.delete(key);\n };\n promise.then(clear, clear);\n return promise;\n }\n\n const [body, response] = await this.#performFetchWithResponse<T>(url, init);\n if (options?.withResponse) {\n return [body, response];\n }\n return body;\n }\n\n /**\n * Issue the prepared request (applying the `onRequest` hook) and\n * resolve the parsed body. Shared by the deduped and direct paths.\n */\n async #performFetch<T>(url: URL, init: RequestInit): Promise<T> {\n const [body] = await this.#performFetchWithResponse<T>(url, init);\n return body;\n }\n\n async #performFetchWithResponse<T>(\n url: URL,\n init: RequestInit\n ): Promise<[T, Response]> {\n let finalInit = init;\n if (this.onRequest) {\n finalInit = await this.onRequest(url, init);\n }\n\n const response = await this.asyncCaller.fetch(url.toString(), finalInit);\n\n const body = await (async () => {\n if (response.status === 202 || response.status === 204) {\n return undefined as T;\n }\n return response.json() as Promise<T>;\n })();\n\n return [body, response];\n }\n\n protected async *streamWithRetry<\n T extends { id?: string; event: string; data: unknown },\n >(config: {\n endpoint: string;\n method?: string;\n signal?: AbortSignal;\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n json?: unknown;\n maxRetries?: number;\n idleReconnect?: IdleReconnectMode;\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n onInitialResponse?: (response: Response) => void | Promise<void>;\n }): AsyncGenerator<T> {\n const makeRequest = async (reconnectParams?: StreamRequestParams) => {\n const requestEndpoint = reconnectParams?.reconnectPath || config.endpoint;\n\n const isReconnect = !!reconnectParams?.reconnectPath;\n const method = isReconnect ? \"GET\" : config.method || \"GET\";\n\n const requestHeaders =\n isReconnect && reconnectParams?.lastEventId\n ? { ...config.headers, \"Last-Event-ID\": reconnectParams.lastEventId }\n : config.headers;\n\n // oxlint-disable-next-line prefer-const -- init is reassigned by onRequest hook\n let [url, init] = this.prepareFetchOptions(requestEndpoint, {\n method,\n timeoutMs: null,\n signal: config.signal,\n headers: requestHeaders,\n params: config.params,\n json: isReconnect ? undefined : config.json,\n });\n\n if (this.onRequest != null) {\n init = await this.onRequest(url, init);\n }\n\n const response = await this.asyncCaller.fetch(url.toString(), init);\n if (!response.body) {\n throw new Error(\"Expected response body from stream endpoint\");\n }\n\n if (!isReconnect && config.onInitialResponse) {\n await config.onInitialResponse(response);\n }\n\n // Insert the idle watchdog on the line stream (between the byte-line\n // decoder and the SSE decoder) so it can both reset on any line and\n // recognise `:` keep-alive heartbeats to drive `\"auto\"` mode. The SSE\n // decoder downstream discards those comment lines.\n const idleMode = config.idleReconnect ?? DEFAULT_IDLE_RECONNECT;\n const enableIdle = idleMode === \"auto\" || idleMode > 0;\n\n const lines = response.body.pipeThrough(BytesLineDecoder());\n const watched = enableIdle\n ? lines.pipeThrough(idleReconnectStream({ mode: idleMode }))\n : lines;\n const stream: ReadableStream<T> = watched.pipeThrough(\n SSEDecoder()\n ) as ReadableStream<T>;\n\n return { response, stream };\n };\n\n yield* streamWithRetry(makeRequest, {\n maxRetries: config.maxRetries ?? DEFAULT_MAX_RECONNECT_ATTEMPTS,\n signal: config.signal,\n onReconnect: config.onReconnect,\n });\n }\n}\n\nexport const REGEX_RUN_METADATA =\n /(\\/threads\\/(?<thread_id>.+))?\\/runs\\/(?<run_id>.+)/;\n\nexport function getRunMetadataFromResponse(\n response: Response\n): { run_id: string; thread_id?: string } | undefined {\n const contentLocation = response.headers.get(\"Content-Location\");\n if (!contentLocation) return undefined;\n\n const match = REGEX_RUN_METADATA.exec(contentLocation);\n\n if (!match?.groups?.run_id) return undefined;\n return {\n run_id: match.groups.run_id,\n thread_id: match.groups.thread_id || undefined,\n };\n}\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * Module-scoped, in-flight-only coalescing map for idempotent reads.\n *\n * Two independently-constructed clients (e.g. a React component that\n * remounts under Suspense / a reachability state flip, each minting a\n * fresh `Client`) can fire the *same* `getState` / `getHistory` read a\n * few milliseconds apart, before the first has resolved. Without\n * coalescing each pays the full round-trip — the duplicate\n * `threads/{id}/state` and `threads/{id}/history` requests seen on\n * reconnect.\n *\n * Keyed by `method + url + body + auth`, entries live only while a\n * request is in flight and are removed the moment it settles. This is\n * deliberately *not* a result cache: there is no TTL and no stored\n * payload, so it cannot serve stale data — it only ever shares a\n * promise that is already on the wire. Opt-in per call via\n * `{ dedupe: true }`, and skipped whenever the caller supplies its own\n * `AbortSignal` (so one consumer aborting can never cancel another's\n * read).\n */\nconst inFlightReads = new Map<string, Promise<unknown>>();\n\n/**\n * Deterministically serialize a prepared request's headers into a\n * stable string for use in the {@link inFlightReads} dedupe key. Header\n * names are normalized and sorted so ordering differences never produce\n * a different key, and every header (not just `x-api-key`) is included\n * so requests carrying different credentials never collide.\n */\nfunction serializeHeaders(headers: RequestInit[\"headers\"]): string {\n const normalized = mergeHeaders(\n headers as Record<string, HeaderValue> | undefined\n );\n return Object.keys(normalized)\n .sort()\n .map((name) => `${name}:${normalized[name]}`)\n .join(\"\\n\");\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.cts","names":[],"sources":["../../src/client/base.ts"],"mappings":";;;;;KAcY,WAAA;;;AA6EZ;;;;;AAqBA;;;;iBArBgB,SAAA,CAAU,MAAA;AAAA,KAqBd,WAAA,IACV,GAAA,EAAK,GAAA,EACL,IAAA,EAAM,WAAA,KACH,OAAA,CAAQ,WAAA,IAAe,WAAA;;;;;UAMX,YAAA;EARf;;;;;;EAeA,MAAA;EAbqC;AAMvC;;;;;EAcE,MAAA;EA0BY;;;;EArBZ,aAAA,GAAgB,iBAAA;EALhB;;;;;;EAYA,SAAA;EAcA;;;;;;EAPA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAgBX;;;;;;EATrB,SAAA,GAAY,WAAA;EAsBS;;;;;EAhBrB,cAAA,GAAiB,cAAA;AAAA;AAAA,cAGN,UAAA;EAAA;YACD,WAAA,EAAa,WAAA;EAAA,UAEb,SAAA;EAAA,UAEA,MAAA;EAAA,UAEA,cAAA,EAAgB,MAAA,SAAe,WAAA;EAAA,UAE/B,SAAA,GAAY,WAAA;EAAA,UAEZ,cAAA,EAAgB,cAAA;EAE1B,WAAA,CAAY,MAAA,GAAS,YAAA;EAAA,UAsCX,mBAAA,CACR,IAAA,UACA,OAAA,GAAU,WAAA;IACR,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,YAAA;IACA,MAAA;EAAA,KAEA,GAAA,EAAK,GAAA,EAAK,IAAA,EAAM,WAAA;EAAA,UAmDJ,KAAA,GAAA,CACd,IAAA,UACA,OAAA,EAAS,WAAA;IACP,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,MAAA,EAAQ,WAAA;IACR,YAAA;EAAA,IAED,OAAA,EAAS,CAAA,EAAG,QAAA;EAAA,UAEC,KAAA,GAAA,CACd,IAAA,UACA,OAAA,GAAU,WAAA;IACR,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,MAAA,EAAQ,WAAA;IACR,YAAA;IACA,MAAA;EAAA,IAED,OAAA,CAAQ,CAAA;EAAA,UAiGM,eAAA;IACH,EAAA;IAAa,KAAA;IAAe,IAAA;EAAA,EAAA,CACxC,MAAA;IACA,QAAA;IACA,MAAA;IACA,MAAA,GAAS,WAAA;IACT,OAAA,GAAU,MAAA;IACV,MAAA,GAAS,MAAA;IACT,IAAA;IACA,UAAA;IACA,aAAA,GAAgB,iBAAA;IAChB,WAAA,IAAe,OAAA;MACb,OAAA;MACA,WAAA;MACA,KAAA;IAAA;IAEF,iBAAA,IAAqB,QAAA,EAAU,QAAA,YAAoB,OAAA;EAAA,IACjD,cAAA,CAAe,CAAA;AAAA"}
1
+ {"version":3,"file":"base.d.cts","names":[],"sources":["../../src/client/base.ts"],"mappings":";;;;;KAcY,WAAA;;;AA6EZ;;;;;AAqBA;;;;iBArBgB,SAAA,CAAU,MAAA;AAAA,KAqBd,WAAA,IACV,GAAA,EAAK,GAAA,EACL,IAAA,EAAM,WAAA,KACH,OAAA,CAAQ,WAAA,IAAe,WAAA;;;;;UAMX,YAAA;EARf;;;;;;EAeA,MAAA;EAbqC;AAMvC;;;;;EAcE,MAAA;EA0BY;;;;EArBZ,aAAA,GAAgB,iBAAA;EALhB;;;;;;EAYA,SAAA;EAcA;;;;;;EAPA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAgBX;;;;;;EATrB,SAAA,GAAY,WAAA;EAsBS;;;;;EAhBrB,cAAA,GAAiB,cAAA;AAAA;AAAA,cAGN,UAAA;EAAA;YACD,WAAA,EAAa,WAAA;EAAA,UAEb,SAAA;EAAA,UAEA,MAAA;EAAA,UAEA,cAAA,EAAgB,MAAA,SAAe,WAAA;EAAA,UAE/B,SAAA,GAAY,WAAA;EAAA,UAEZ,cAAA,EAAgB,cAAA;EAE1B,WAAA,CAAY,MAAA,GAAS,YAAA;EAAA,UAsCX,mBAAA,CACR,IAAA,UACA,OAAA,GAAU,WAAA;IACR,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,YAAA;IACA,MAAA;EAAA,KAEA,GAAA,EAAK,GAAA,EAAK,IAAA,EAAM,WAAA;EAAA,UAiEJ,KAAA,GAAA,CACd,IAAA,UACA,OAAA,EAAS,WAAA;IACP,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,MAAA,EAAQ,WAAA;IACR,YAAA;EAAA,IAED,OAAA,EAAS,CAAA,EAAG,QAAA;EAAA,UAEC,KAAA,GAAA,CACd,IAAA,UACA,OAAA,GAAU,WAAA;IACR,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,MAAA,EAAQ,WAAA;IACR,YAAA;IACA,MAAA;EAAA,IAED,OAAA,CAAQ,CAAA;EAAA,UAiGM,eAAA;IACH,EAAA;IAAa,KAAA;IAAe,IAAA;EAAA,EAAA,CACxC,MAAA;IACA,QAAA;IACA,MAAA;IACA,MAAA,GAAS,WAAA;IACT,OAAA,GAAU,MAAA;IACV,MAAA,GAAS,MAAA;IACT,IAAA;IACA,UAAA;IACA,aAAA,GAAgB,iBAAA;IAChB,WAAA,IAAe,OAAA;MACb,OAAA;MACA,WAAA;MACA,KAAA;IAAA;IAEF,iBAAA,IAAqB,QAAA,EAAU,QAAA,YAAoB,OAAA;EAAA,IACjD,cAAA,CAAe,CAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","names":[],"sources":["../../src/client/base.ts"],"mappings":";;;;;KAcY,WAAA;;;AA6EZ;;;;;AAqBA;;;;iBArBgB,SAAA,CAAU,MAAA;AAAA,KAqBd,WAAA,IACV,GAAA,EAAK,GAAA,EACL,IAAA,EAAM,WAAA,KACH,OAAA,CAAQ,WAAA,IAAe,WAAA;;;;;UAMX,YAAA;EARf;;;;;;EAeA,MAAA;EAbqC;AAMvC;;;;;EAcE,MAAA;EA0BY;;;;EArBZ,aAAA,GAAgB,iBAAA;EALhB;;;;;;EAYA,SAAA;EAcA;;;;;;EAPA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAgBX;;;;;;EATrB,SAAA,GAAY,WAAA;EAsBS;;;;;EAhBrB,cAAA,GAAiB,cAAA;AAAA;AAAA,cAGN,UAAA;EAAA;YACD,WAAA,EAAa,WAAA;EAAA,UAEb,SAAA;EAAA,UAEA,MAAA;EAAA,UAEA,cAAA,EAAgB,MAAA,SAAe,WAAA;EAAA,UAE/B,SAAA,GAAY,WAAA;EAAA,UAEZ,cAAA,EAAgB,cAAA;EAE1B,WAAA,CAAY,MAAA,GAAS,YAAA;EAAA,UAsCX,mBAAA,CACR,IAAA,UACA,OAAA,GAAU,WAAA;IACR,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,YAAA;IACA,MAAA;EAAA,KAEA,GAAA,EAAK,GAAA,EAAK,IAAA,EAAM,WAAA;EAAA,UAmDJ,KAAA,GAAA,CACd,IAAA,UACA,OAAA,EAAS,WAAA;IACP,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,MAAA,EAAQ,WAAA;IACR,YAAA;EAAA,IAED,OAAA,EAAS,CAAA,EAAG,QAAA;EAAA,UAEC,KAAA,GAAA,CACd,IAAA,UACA,OAAA,GAAU,WAAA;IACR,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,MAAA,EAAQ,WAAA;IACR,YAAA;IACA,MAAA;EAAA,IAED,OAAA,CAAQ,CAAA;EAAA,UAiGM,eAAA;IACH,EAAA;IAAa,KAAA;IAAe,IAAA;EAAA,EAAA,CACxC,MAAA;IACA,QAAA;IACA,MAAA;IACA,MAAA,GAAS,WAAA;IACT,OAAA,GAAU,MAAA;IACV,MAAA,GAAS,MAAA;IACT,IAAA;IACA,UAAA;IACA,aAAA,GAAgB,iBAAA;IAChB,WAAA,IAAe,OAAA;MACb,OAAA;MACA,WAAA;MACA,KAAA;IAAA;IAEF,iBAAA,IAAqB,QAAA,EAAU,QAAA,YAAoB,OAAA;EAAA,IACjD,cAAA,CAAe,CAAA;AAAA"}
1
+ {"version":3,"file":"base.d.ts","names":[],"sources":["../../src/client/base.ts"],"mappings":";;;;;KAcY,WAAA;;;AA6EZ;;;;;AAqBA;;;;iBArBgB,SAAA,CAAU,MAAA;AAAA,KAqBd,WAAA,IACV,GAAA,EAAK,GAAA,EACL,IAAA,EAAM,WAAA,KACH,OAAA,CAAQ,WAAA,IAAe,WAAA;;;;;UAMX,YAAA;EARf;;;;;;EAeA,MAAA;EAbqC;AAMvC;;;;;EAcE,MAAA;EA0BY;;;;EArBZ,aAAA,GAAgB,iBAAA;EALhB;;;;;;EAYA,SAAA;EAcA;;;;;;EAPA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAgBX;;;;;;EATrB,SAAA,GAAY,WAAA;EAsBS;;;;;EAhBrB,cAAA,GAAiB,cAAA;AAAA;AAAA,cAGN,UAAA;EAAA;YACD,WAAA,EAAa,WAAA;EAAA,UAEb,SAAA;EAAA,UAEA,MAAA;EAAA,UAEA,cAAA,EAAgB,MAAA,SAAe,WAAA;EAAA,UAE/B,SAAA,GAAY,WAAA;EAAA,UAEZ,cAAA,EAAgB,cAAA;EAE1B,WAAA,CAAY,MAAA,GAAS,YAAA;EAAA,UAsCX,mBAAA,CACR,IAAA,UACA,OAAA,GAAU,WAAA;IACR,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,YAAA;IACA,MAAA;EAAA,KAEA,GAAA,EAAK,GAAA,EAAK,IAAA,EAAM,WAAA;EAAA,UAiEJ,KAAA,GAAA,CACd,IAAA,UACA,OAAA,EAAS,WAAA;IACP,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,MAAA,EAAQ,WAAA;IACR,YAAA;EAAA,IAED,OAAA,EAAS,CAAA,EAAG,QAAA;EAAA,UAEC,KAAA,GAAA,CACd,IAAA,UACA,OAAA,GAAU,WAAA;IACR,IAAA;IACA,MAAA,GAAS,MAAA;IACT,SAAA;IACA,MAAA,EAAQ,WAAA;IACR,YAAA;IACA,MAAA;EAAA,IAED,OAAA,CAAQ,CAAA;EAAA,UAiGM,eAAA;IACH,EAAA;IAAa,KAAA;IAAe,IAAA;EAAA,EAAA,CACxC,MAAA;IACA,QAAA;IACA,MAAA;IACA,MAAA,GAAS,WAAA;IACT,OAAA,GAAU,MAAA;IACV,MAAA,GAAS,MAAA;IACT,IAAA;IACA,UAAA;IACA,aAAA,GAAgB,iBAAA;IAChB,WAAA,IAAe,OAAA;MACb,OAAA;MACA,WAAA;MACA,KAAA;IAAA;IAEF,iBAAA,IAAqB,QAAA,EAAU,QAAA,YAAoB,OAAA;EAAA,IACjD,cAAA,CAAe,CAAA;AAAA"}
@@ -120,6 +120,13 @@ var BaseClient = class {
120
120
  if (mutatedOptions.params) {
121
121
  for (const [key, value] of Object.entries(mutatedOptions.params)) {
122
122
  if (value == null) continue;
123
+ if (Array.isArray(value)) {
124
+ for (const item of value) {
125
+ if (item == null) continue;
126
+ targetUrl.searchParams.append(key, typeof item === "string" || typeof item === "number" ? item.toString() : JSON.stringify(item));
127
+ }
128
+ continue;
129
+ }
123
130
  const strValue = typeof value === "string" || typeof value === "number" ? value.toString() : JSON.stringify(value);
124
131
  targetUrl.searchParams.append(key, strValue);
125
132
  }
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":["#performFetch","#performFetchWithResponse"],"sources":["../../src/client/base.ts"],"sourcesContent":["import { AsyncCaller, AsyncCallerParams } from \"../utils/async_caller.js\";\nimport { getEnvironmentVariable } from \"../utils/env.js\";\nimport { mergeSignals } from \"../utils/signals.js\";\nimport { BytesLineDecoder, SSEDecoder } from \"../utils/sse.js\";\nimport {\n streamWithRetry,\n idleReconnectStream,\n type IdleReconnectMode,\n StreamRequestParams,\n DEFAULT_IDLE_RECONNECT,\n} from \"../utils/stream.js\";\nimport { DEFAULT_MAX_RECONNECT_ATTEMPTS } from \"../utils/reconnect.js\";\nimport type { StreamProtocol } from \"../types.js\";\n\nexport type HeaderValue = string | undefined | null;\n\nexport function* iterateHeaders(\n headers: HeadersInit | Record<string, HeaderValue>\n): IterableIterator<[string, string | null]> {\n let iter: Iterable<(HeaderValue | HeaderValue | null[])[]>;\n let shouldClear = false;\n\n // eslint-disable-next-line no-instanceof/no-instanceof\n if (headers instanceof Headers) {\n const entries: [string, string][] = [];\n headers.forEach((value, name) => {\n entries.push([name, value]);\n });\n iter = entries;\n } else if (Array.isArray(headers)) {\n iter = headers;\n } else {\n shouldClear = true;\n iter = Object.entries(headers ?? {});\n }\n\n for (const item of iter) {\n const name = item[0];\n if (typeof name !== \"string\")\n throw new TypeError(\n `Expected header name to be a string, got ${typeof name}`\n );\n const values = Array.isArray(item[1]) ? item[1] : [item[1]];\n let didClear = false;\n\n for (const value of values) {\n if (value === undefined) continue;\n\n if (shouldClear && !didClear) {\n didClear = true;\n yield [name, null];\n }\n yield [name, value];\n }\n }\n}\n\nexport function mergeHeaders(\n ...headerObjects: (\n | HeadersInit\n | Record<string, HeaderValue>\n | undefined\n | null\n )[]\n) {\n const outputHeaders = new Headers();\n for (const headers of headerObjects) {\n if (!headers) continue;\n for (const [name, value] of iterateHeaders(headers)) {\n if (value === null) outputHeaders.delete(name);\n else outputHeaders.append(name, value);\n }\n }\n const headerEntries: [string, string][] = [];\n outputHeaders.forEach((value, name) => {\n headerEntries.push([name, value]);\n });\n return Object.fromEntries(headerEntries);\n}\n\n/**\n * Get the API key from the environment.\n * Precedence:\n * 1. explicit argument (if string)\n * 2. LANGGRAPH_API_KEY\n * 3. LANGSMITH_API_KEY\n * 4. LANGCHAIN_API_KEY\n *\n * @param apiKey - API key provided as an argument. If null, skips environment lookup. If undefined, tries environment.\n * @returns The API key if found, otherwise undefined\n */\nexport function getApiKey(apiKey?: string | null): string | undefined {\n if (apiKey === null) {\n return undefined;\n }\n\n if (apiKey) {\n return apiKey;\n }\n\n const prefixes = [\"LANGGRAPH\", \"LANGSMITH\", \"LANGCHAIN\"];\n\n for (const prefix of prefixes) {\n const envKey = getEnvironmentVariable(`${prefix}_API_KEY`);\n if (envKey) {\n return envKey.trim().replace(/^[\"']|[\"']$/g, \"\");\n }\n }\n\n return undefined;\n}\n\nexport type RequestHook = (\n url: URL,\n init: RequestInit\n) => Promise<RequestInit> | RequestInit;\n\n/**\n * Configuration for {@link BaseClient} and the exported LangGraph SDK\n * {@link Client}.\n */\nexport interface ClientConfig {\n /**\n * Base URL of the LangGraph API server.\n *\n * Defaults to `http://localhost:8123`, unless the runtime provides a\n * `langgraph_api:url` global override.\n */\n apiUrl?: string;\n /**\n * API key for authentication.\n * - If a string is provided, that key will be used\n * - If undefined (default), the key will be auto-loaded from environment variables (LANGGRAPH_API_KEY, LANGSMITH_API_KEY, or LANGCHAIN_API_KEY)\n * - If null, no API key will be set (skips auto-loading)\n */\n apiKey?: string | null;\n /**\n * Options forwarded to the internal {@link AsyncCaller}, such as retry,\n * concurrency, or custom `fetch` behavior.\n */\n callerOptions?: AsyncCallerParams;\n /**\n * Default timeout, in milliseconds, applied to client requests.\n *\n * Per-request `timeoutMs` values override this default. Passing `null`\n * at the request level disables the configured timeout for that request.\n */\n timeoutMs?: number;\n /**\n * Headers applied to every request.\n *\n * The configured API key, when present, is added as the `x-api-key`\n * header after these defaults are initialized.\n */\n defaultHeaders?: Record<string, HeaderValue>;\n /**\n * Hook for inspecting or mutating a request before it is sent.\n *\n * Receives the resolved URL and prepared `RequestInit`; return the\n * original init or a replacement object to continue the request.\n */\n onRequest?: RequestHook;\n /**\n * Streaming protocol used by stream-capable endpoints.\n *\n * Defaults to `\"legacy\"` for backwards compatibility.\n */\n streamProtocol?: StreamProtocol;\n}\n\nexport class BaseClient {\n protected asyncCaller: AsyncCaller;\n\n protected timeoutMs: number | undefined;\n\n protected apiUrl: string;\n\n protected defaultHeaders: Record<string, HeaderValue>;\n\n protected onRequest?: RequestHook;\n\n protected streamProtocol: StreamProtocol;\n\n constructor(config?: ClientConfig) {\n const callerOptions = {\n maxRetries: 4,\n maxConcurrency: 4,\n ...config?.callerOptions,\n };\n\n let defaultApiUrl = \"http://localhost:8123\";\n if (\n !config?.apiUrl &&\n typeof globalThis === \"object\" &&\n globalThis != null\n ) {\n const fetchSmb = Symbol.for(\"langgraph_api:fetch\");\n const urlSmb = Symbol.for(\"langgraph_api:url\");\n\n const global = globalThis as unknown as {\n [fetchSmb]?: typeof fetch;\n [urlSmb]?: string;\n };\n\n if (global[fetchSmb]) callerOptions.fetch ??= global[fetchSmb];\n if (global[urlSmb]) defaultApiUrl = global[urlSmb];\n }\n\n this.asyncCaller = new AsyncCaller(callerOptions);\n this.timeoutMs = config?.timeoutMs;\n\n this.apiUrl = config?.apiUrl?.replace(/\\/$/, \"\") || defaultApiUrl;\n this.defaultHeaders = config?.defaultHeaders || {};\n this.onRequest = config?.onRequest;\n this.streamProtocol = config?.streamProtocol ?? \"legacy\";\n const apiKey = getApiKey(config?.apiKey);\n if (apiKey) {\n this.defaultHeaders[\"x-api-key\"] = apiKey;\n }\n }\n\n protected prepareFetchOptions(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n withResponse?: boolean;\n dedupe?: boolean;\n }\n ): [url: URL, init: RequestInit] {\n const mutatedOptions = {\n ...options,\n headers: mergeHeaders(this.defaultHeaders, options?.headers),\n };\n\n if (mutatedOptions.json) {\n mutatedOptions.body = JSON.stringify(mutatedOptions.json);\n mutatedOptions.headers = mergeHeaders(mutatedOptions.headers, {\n \"content-type\": \"application/json\",\n });\n delete mutatedOptions.json;\n }\n\n if (mutatedOptions.withResponse) {\n delete mutatedOptions.withResponse;\n }\n\n if (\"dedupe\" in mutatedOptions) {\n delete mutatedOptions.dedupe;\n }\n\n let timeoutSignal: AbortSignal | null = null;\n if (typeof options?.timeoutMs !== \"undefined\") {\n if (options.timeoutMs != null) {\n timeoutSignal = AbortSignal.timeout(options.timeoutMs);\n }\n } else if (this.timeoutMs != null) {\n timeoutSignal = AbortSignal.timeout(this.timeoutMs);\n }\n\n mutatedOptions.signal = mergeSignals(timeoutSignal, mutatedOptions.signal);\n const targetUrl = new URL(`${this.apiUrl}${path}`);\n\n if (mutatedOptions.params) {\n for (const [key, value] of Object.entries(mutatedOptions.params)) {\n if (value == null) continue;\n\n const strValue =\n typeof value === \"string\" || typeof value === \"number\"\n ? value.toString()\n : JSON.stringify(value);\n\n targetUrl.searchParams.append(key, strValue);\n }\n delete mutatedOptions.params;\n }\n\n return [targetUrl, mutatedOptions];\n }\n\n protected async fetch<T>(\n path: string,\n options: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse: true;\n }\n ): Promise<[T, Response]>;\n\n protected async fetch<T>(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse?: false;\n dedupe?: boolean;\n }\n ): Promise<T>;\n\n protected async fetch<T>(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse?: boolean;\n dedupe?: boolean;\n }\n ): Promise<T | [T, Response]> {\n const [url, init] = this.prepareFetchOptions(path, options);\n\n /**\n * Coalesce concurrent, identical idempotent reads onto a single\n * in-flight request. Only engaged when the caller opts in\n * (`dedupe: true`), is not asking for the raw `Response`, did not\n * supply its own `AbortSignal` (sharing a request across consumers\n * must never let one consumer's abort cancel another's), and no\n * `onRequest` hook is configured.\n *\n * `onRequest` is excluded because it can inject per-request headers\n * (e.g. a freshly-minted `Authorization` bearer) that are not\n * visible until *after* it runs — i.e. after the dedupe key is\n * computed — so two requests that look identical here could be sent\n * with different credentials. Coalescing them would let one\n * consumer receive a response fetched with another's auth.\n */\n const canDedupe =\n options?.dedupe === true &&\n options?.withResponse !== true &&\n options?.signal == null &&\n this.onRequest == null;\n\n if (canDedupe) {\n const body = typeof init.body === \"string\" ? init.body : \"\";\n /**\n * The key must capture the FULL request identity, including every\n * prepared header. `inFlightReads` is module-scoped across all\n * `Client` instances, so omitting headers would let two clients\n * pointed at the same URL/thread but using different credentials\n * (Authorization, custom auth headers, tenant-scoping defaults, …)\n * share one in-flight promise — a cross-tenant data leak.\n */\n const headers = serializeHeaders(init.headers);\n const key = `${init.method ?? \"GET\"} ${url.toString()} ${body} ${headers}`;\n const existing = inFlightReads.get(key);\n if (existing != null) return existing as Promise<T>;\n\n const promise = this.#performFetch<T>(url, init);\n inFlightReads.set(key, promise);\n const clear = () => {\n if (inFlightReads.get(key) === promise) inFlightReads.delete(key);\n };\n promise.then(clear, clear);\n return promise;\n }\n\n const [body, response] = await this.#performFetchWithResponse<T>(url, init);\n if (options?.withResponse) {\n return [body, response];\n }\n return body;\n }\n\n /**\n * Issue the prepared request (applying the `onRequest` hook) and\n * resolve the parsed body. Shared by the deduped and direct paths.\n */\n async #performFetch<T>(url: URL, init: RequestInit): Promise<T> {\n const [body] = await this.#performFetchWithResponse<T>(url, init);\n return body;\n }\n\n async #performFetchWithResponse<T>(\n url: URL,\n init: RequestInit\n ): Promise<[T, Response]> {\n let finalInit = init;\n if (this.onRequest) {\n finalInit = await this.onRequest(url, init);\n }\n\n const response = await this.asyncCaller.fetch(url.toString(), finalInit);\n\n const body = await (async () => {\n if (response.status === 202 || response.status === 204) {\n return undefined as T;\n }\n return response.json() as Promise<T>;\n })();\n\n return [body, response];\n }\n\n protected async *streamWithRetry<\n T extends { id?: string; event: string; data: unknown },\n >(config: {\n endpoint: string;\n method?: string;\n signal?: AbortSignal;\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n json?: unknown;\n maxRetries?: number;\n idleReconnect?: IdleReconnectMode;\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n onInitialResponse?: (response: Response) => void | Promise<void>;\n }): AsyncGenerator<T> {\n const makeRequest = async (reconnectParams?: StreamRequestParams) => {\n const requestEndpoint = reconnectParams?.reconnectPath || config.endpoint;\n\n const isReconnect = !!reconnectParams?.reconnectPath;\n const method = isReconnect ? \"GET\" : config.method || \"GET\";\n\n const requestHeaders =\n isReconnect && reconnectParams?.lastEventId\n ? { ...config.headers, \"Last-Event-ID\": reconnectParams.lastEventId }\n : config.headers;\n\n // oxlint-disable-next-line prefer-const -- init is reassigned by onRequest hook\n let [url, init] = this.prepareFetchOptions(requestEndpoint, {\n method,\n timeoutMs: null,\n signal: config.signal,\n headers: requestHeaders,\n params: config.params,\n json: isReconnect ? undefined : config.json,\n });\n\n if (this.onRequest != null) {\n init = await this.onRequest(url, init);\n }\n\n const response = await this.asyncCaller.fetch(url.toString(), init);\n if (!response.body) {\n throw new Error(\"Expected response body from stream endpoint\");\n }\n\n if (!isReconnect && config.onInitialResponse) {\n await config.onInitialResponse(response);\n }\n\n // Insert the idle watchdog on the line stream (between the byte-line\n // decoder and the SSE decoder) so it can both reset on any line and\n // recognise `:` keep-alive heartbeats to drive `\"auto\"` mode. The SSE\n // decoder downstream discards those comment lines.\n const idleMode = config.idleReconnect ?? DEFAULT_IDLE_RECONNECT;\n const enableIdle = idleMode === \"auto\" || idleMode > 0;\n\n const lines = response.body.pipeThrough(BytesLineDecoder());\n const watched = enableIdle\n ? lines.pipeThrough(idleReconnectStream({ mode: idleMode }))\n : lines;\n const stream: ReadableStream<T> = watched.pipeThrough(\n SSEDecoder()\n ) as ReadableStream<T>;\n\n return { response, stream };\n };\n\n yield* streamWithRetry(makeRequest, {\n maxRetries: config.maxRetries ?? DEFAULT_MAX_RECONNECT_ATTEMPTS,\n signal: config.signal,\n onReconnect: config.onReconnect,\n });\n }\n}\n\nexport const REGEX_RUN_METADATA =\n /(\\/threads\\/(?<thread_id>.+))?\\/runs\\/(?<run_id>.+)/;\n\nexport function getRunMetadataFromResponse(\n response: Response\n): { run_id: string; thread_id?: string } | undefined {\n const contentLocation = response.headers.get(\"Content-Location\");\n if (!contentLocation) return undefined;\n\n const match = REGEX_RUN_METADATA.exec(contentLocation);\n\n if (!match?.groups?.run_id) return undefined;\n return {\n run_id: match.groups.run_id,\n thread_id: match.groups.thread_id || undefined,\n };\n}\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * Module-scoped, in-flight-only coalescing map for idempotent reads.\n *\n * Two independently-constructed clients (e.g. a React component that\n * remounts under Suspense / a reachability state flip, each minting a\n * fresh `Client`) can fire the *same* `getState` / `getHistory` read a\n * few milliseconds apart, before the first has resolved. Without\n * coalescing each pays the full round-trip — the duplicate\n * `threads/{id}/state` and `threads/{id}/history` requests seen on\n * reconnect.\n *\n * Keyed by `method + url + body + auth`, entries live only while a\n * request is in flight and are removed the moment it settles. This is\n * deliberately *not* a result cache: there is no TTL and no stored\n * payload, so it cannot serve stale data — it only ever shares a\n * promise that is already on the wire. Opt-in per call via\n * `{ dedupe: true }`, and skipped whenever the caller supplies its own\n * `AbortSignal` (so one consumer aborting can never cancel another's\n * read).\n */\nconst inFlightReads = new Map<string, Promise<unknown>>();\n\n/**\n * Deterministically serialize a prepared request's headers into a\n * stable string for use in the {@link inFlightReads} dedupe key. Header\n * names are normalized and sorted so ordering differences never produce\n * a different key, and every header (not just `x-api-key`) is included\n * so requests carrying different credentials never collide.\n */\nfunction serializeHeaders(headers: RequestInit[\"headers\"]): string {\n const normalized = mergeHeaders(\n headers as Record<string, HeaderValue> | undefined\n );\n return Object.keys(normalized)\n .sort()\n .map((name) => `${name}:${normalized[name]}`)\n .join(\"\\n\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"base.js","names":["#performFetch","#performFetchWithResponse"],"sources":["../../src/client/base.ts"],"sourcesContent":["import { AsyncCaller, AsyncCallerParams } from \"../utils/async_caller.js\";\nimport { getEnvironmentVariable } from \"../utils/env.js\";\nimport { mergeSignals } from \"../utils/signals.js\";\nimport { BytesLineDecoder, SSEDecoder } from \"../utils/sse.js\";\nimport {\n streamWithRetry,\n idleReconnectStream,\n type IdleReconnectMode,\n StreamRequestParams,\n DEFAULT_IDLE_RECONNECT,\n} from \"../utils/stream.js\";\nimport { DEFAULT_MAX_RECONNECT_ATTEMPTS } from \"../utils/reconnect.js\";\nimport type { StreamProtocol } from \"../types.js\";\n\nexport type HeaderValue = string | undefined | null;\n\nexport function* iterateHeaders(\n headers: HeadersInit | Record<string, HeaderValue>\n): IterableIterator<[string, string | null]> {\n let iter: Iterable<(HeaderValue | HeaderValue | null[])[]>;\n let shouldClear = false;\n\n // eslint-disable-next-line no-instanceof/no-instanceof\n if (headers instanceof Headers) {\n const entries: [string, string][] = [];\n headers.forEach((value, name) => {\n entries.push([name, value]);\n });\n iter = entries;\n } else if (Array.isArray(headers)) {\n iter = headers;\n } else {\n shouldClear = true;\n iter = Object.entries(headers ?? {});\n }\n\n for (const item of iter) {\n const name = item[0];\n if (typeof name !== \"string\")\n throw new TypeError(\n `Expected header name to be a string, got ${typeof name}`\n );\n const values = Array.isArray(item[1]) ? item[1] : [item[1]];\n let didClear = false;\n\n for (const value of values) {\n if (value === undefined) continue;\n\n if (shouldClear && !didClear) {\n didClear = true;\n yield [name, null];\n }\n yield [name, value];\n }\n }\n}\n\nexport function mergeHeaders(\n ...headerObjects: (\n | HeadersInit\n | Record<string, HeaderValue>\n | undefined\n | null\n )[]\n) {\n const outputHeaders = new Headers();\n for (const headers of headerObjects) {\n if (!headers) continue;\n for (const [name, value] of iterateHeaders(headers)) {\n if (value === null) outputHeaders.delete(name);\n else outputHeaders.append(name, value);\n }\n }\n const headerEntries: [string, string][] = [];\n outputHeaders.forEach((value, name) => {\n headerEntries.push([name, value]);\n });\n return Object.fromEntries(headerEntries);\n}\n\n/**\n * Get the API key from the environment.\n * Precedence:\n * 1. explicit argument (if string)\n * 2. LANGGRAPH_API_KEY\n * 3. LANGSMITH_API_KEY\n * 4. LANGCHAIN_API_KEY\n *\n * @param apiKey - API key provided as an argument. If null, skips environment lookup. If undefined, tries environment.\n * @returns The API key if found, otherwise undefined\n */\nexport function getApiKey(apiKey?: string | null): string | undefined {\n if (apiKey === null) {\n return undefined;\n }\n\n if (apiKey) {\n return apiKey;\n }\n\n const prefixes = [\"LANGGRAPH\", \"LANGSMITH\", \"LANGCHAIN\"];\n\n for (const prefix of prefixes) {\n const envKey = getEnvironmentVariable(`${prefix}_API_KEY`);\n if (envKey) {\n return envKey.trim().replace(/^[\"']|[\"']$/g, \"\");\n }\n }\n\n return undefined;\n}\n\nexport type RequestHook = (\n url: URL,\n init: RequestInit\n) => Promise<RequestInit> | RequestInit;\n\n/**\n * Configuration for {@link BaseClient} and the exported LangGraph SDK\n * {@link Client}.\n */\nexport interface ClientConfig {\n /**\n * Base URL of the LangGraph API server.\n *\n * Defaults to `http://localhost:8123`, unless the runtime provides a\n * `langgraph_api:url` global override.\n */\n apiUrl?: string;\n /**\n * API key for authentication.\n * - If a string is provided, that key will be used\n * - If undefined (default), the key will be auto-loaded from environment variables (LANGGRAPH_API_KEY, LANGSMITH_API_KEY, or LANGCHAIN_API_KEY)\n * - If null, no API key will be set (skips auto-loading)\n */\n apiKey?: string | null;\n /**\n * Options forwarded to the internal {@link AsyncCaller}, such as retry,\n * concurrency, or custom `fetch` behavior.\n */\n callerOptions?: AsyncCallerParams;\n /**\n * Default timeout, in milliseconds, applied to client requests.\n *\n * Per-request `timeoutMs` values override this default. Passing `null`\n * at the request level disables the configured timeout for that request.\n */\n timeoutMs?: number;\n /**\n * Headers applied to every request.\n *\n * The configured API key, when present, is added as the `x-api-key`\n * header after these defaults are initialized.\n */\n defaultHeaders?: Record<string, HeaderValue>;\n /**\n * Hook for inspecting or mutating a request before it is sent.\n *\n * Receives the resolved URL and prepared `RequestInit`; return the\n * original init or a replacement object to continue the request.\n */\n onRequest?: RequestHook;\n /**\n * Streaming protocol used by stream-capable endpoints.\n *\n * Defaults to `\"legacy\"` for backwards compatibility.\n */\n streamProtocol?: StreamProtocol;\n}\n\nexport class BaseClient {\n protected asyncCaller: AsyncCaller;\n\n protected timeoutMs: number | undefined;\n\n protected apiUrl: string;\n\n protected defaultHeaders: Record<string, HeaderValue>;\n\n protected onRequest?: RequestHook;\n\n protected streamProtocol: StreamProtocol;\n\n constructor(config?: ClientConfig) {\n const callerOptions = {\n maxRetries: 4,\n maxConcurrency: 4,\n ...config?.callerOptions,\n };\n\n let defaultApiUrl = \"http://localhost:8123\";\n if (\n !config?.apiUrl &&\n typeof globalThis === \"object\" &&\n globalThis != null\n ) {\n const fetchSmb = Symbol.for(\"langgraph_api:fetch\");\n const urlSmb = Symbol.for(\"langgraph_api:url\");\n\n const global = globalThis as unknown as {\n [fetchSmb]?: typeof fetch;\n [urlSmb]?: string;\n };\n\n if (global[fetchSmb]) callerOptions.fetch ??= global[fetchSmb];\n if (global[urlSmb]) defaultApiUrl = global[urlSmb];\n }\n\n this.asyncCaller = new AsyncCaller(callerOptions);\n this.timeoutMs = config?.timeoutMs;\n\n this.apiUrl = config?.apiUrl?.replace(/\\/$/, \"\") || defaultApiUrl;\n this.defaultHeaders = config?.defaultHeaders || {};\n this.onRequest = config?.onRequest;\n this.streamProtocol = config?.streamProtocol ?? \"legacy\";\n const apiKey = getApiKey(config?.apiKey);\n if (apiKey) {\n this.defaultHeaders[\"x-api-key\"] = apiKey;\n }\n }\n\n protected prepareFetchOptions(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n withResponse?: boolean;\n dedupe?: boolean;\n }\n ): [url: URL, init: RequestInit] {\n const mutatedOptions = {\n ...options,\n headers: mergeHeaders(this.defaultHeaders, options?.headers),\n };\n\n if (mutatedOptions.json) {\n mutatedOptions.body = JSON.stringify(mutatedOptions.json);\n mutatedOptions.headers = mergeHeaders(mutatedOptions.headers, {\n \"content-type\": \"application/json\",\n });\n delete mutatedOptions.json;\n }\n\n if (mutatedOptions.withResponse) {\n delete mutatedOptions.withResponse;\n }\n\n if (\"dedupe\" in mutatedOptions) {\n delete mutatedOptions.dedupe;\n }\n\n let timeoutSignal: AbortSignal | null = null;\n if (typeof options?.timeoutMs !== \"undefined\") {\n if (options.timeoutMs != null) {\n timeoutSignal = AbortSignal.timeout(options.timeoutMs);\n }\n } else if (this.timeoutMs != null) {\n timeoutSignal = AbortSignal.timeout(this.timeoutMs);\n }\n\n mutatedOptions.signal = mergeSignals(timeoutSignal, mutatedOptions.signal);\n const targetUrl = new URL(`${this.apiUrl}${path}`);\n\n if (mutatedOptions.params) {\n for (const [key, value] of Object.entries(mutatedOptions.params)) {\n if (value == null) continue;\n\n // Send each array element as its own repeated key=value pair.\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item == null) continue;\n targetUrl.searchParams.append(\n key,\n typeof item === \"string\" || typeof item === \"number\"\n ? item.toString()\n : JSON.stringify(item)\n );\n }\n continue;\n }\n\n const strValue =\n typeof value === \"string\" || typeof value === \"number\"\n ? value.toString()\n : JSON.stringify(value);\n\n targetUrl.searchParams.append(key, strValue);\n }\n delete mutatedOptions.params;\n }\n\n return [targetUrl, mutatedOptions];\n }\n\n protected async fetch<T>(\n path: string,\n options: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse: true;\n }\n ): Promise<[T, Response]>;\n\n protected async fetch<T>(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse?: false;\n dedupe?: boolean;\n }\n ): Promise<T>;\n\n protected async fetch<T>(\n path: string,\n options?: RequestInit & {\n json?: unknown;\n params?: Record<string, unknown>;\n timeoutMs?: number | null;\n signal: AbortSignal | undefined;\n withResponse?: boolean;\n dedupe?: boolean;\n }\n ): Promise<T | [T, Response]> {\n const [url, init] = this.prepareFetchOptions(path, options);\n\n /**\n * Coalesce concurrent, identical idempotent reads onto a single\n * in-flight request. Only engaged when the caller opts in\n * (`dedupe: true`), is not asking for the raw `Response`, did not\n * supply its own `AbortSignal` (sharing a request across consumers\n * must never let one consumer's abort cancel another's), and no\n * `onRequest` hook is configured.\n *\n * `onRequest` is excluded because it can inject per-request headers\n * (e.g. a freshly-minted `Authorization` bearer) that are not\n * visible until *after* it runs — i.e. after the dedupe key is\n * computed — so two requests that look identical here could be sent\n * with different credentials. Coalescing them would let one\n * consumer receive a response fetched with another's auth.\n */\n const canDedupe =\n options?.dedupe === true &&\n options?.withResponse !== true &&\n options?.signal == null &&\n this.onRequest == null;\n\n if (canDedupe) {\n const body = typeof init.body === \"string\" ? init.body : \"\";\n /**\n * The key must capture the FULL request identity, including every\n * prepared header. `inFlightReads` is module-scoped across all\n * `Client` instances, so omitting headers would let two clients\n * pointed at the same URL/thread but using different credentials\n * (Authorization, custom auth headers, tenant-scoping defaults, …)\n * share one in-flight promise — a cross-tenant data leak.\n */\n const headers = serializeHeaders(init.headers);\n const key = `${init.method ?? \"GET\"} ${url.toString()} ${body} ${headers}`;\n const existing = inFlightReads.get(key);\n if (existing != null) return existing as Promise<T>;\n\n const promise = this.#performFetch<T>(url, init);\n inFlightReads.set(key, promise);\n const clear = () => {\n if (inFlightReads.get(key) === promise) inFlightReads.delete(key);\n };\n promise.then(clear, clear);\n return promise;\n }\n\n const [body, response] = await this.#performFetchWithResponse<T>(url, init);\n if (options?.withResponse) {\n return [body, response];\n }\n return body;\n }\n\n /**\n * Issue the prepared request (applying the `onRequest` hook) and\n * resolve the parsed body. Shared by the deduped and direct paths.\n */\n async #performFetch<T>(url: URL, init: RequestInit): Promise<T> {\n const [body] = await this.#performFetchWithResponse<T>(url, init);\n return body;\n }\n\n async #performFetchWithResponse<T>(\n url: URL,\n init: RequestInit\n ): Promise<[T, Response]> {\n let finalInit = init;\n if (this.onRequest) {\n finalInit = await this.onRequest(url, init);\n }\n\n const response = await this.asyncCaller.fetch(url.toString(), finalInit);\n\n const body = await (async () => {\n if (response.status === 202 || response.status === 204) {\n return undefined as T;\n }\n return response.json() as Promise<T>;\n })();\n\n return [body, response];\n }\n\n protected async *streamWithRetry<\n T extends { id?: string; event: string; data: unknown },\n >(config: {\n endpoint: string;\n method?: string;\n signal?: AbortSignal;\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n json?: unknown;\n maxRetries?: number;\n idleReconnect?: IdleReconnectMode;\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n onInitialResponse?: (response: Response) => void | Promise<void>;\n }): AsyncGenerator<T> {\n const makeRequest = async (reconnectParams?: StreamRequestParams) => {\n const requestEndpoint = reconnectParams?.reconnectPath || config.endpoint;\n\n const isReconnect = !!reconnectParams?.reconnectPath;\n const method = isReconnect ? \"GET\" : config.method || \"GET\";\n\n const requestHeaders =\n isReconnect && reconnectParams?.lastEventId\n ? { ...config.headers, \"Last-Event-ID\": reconnectParams.lastEventId }\n : config.headers;\n\n // oxlint-disable-next-line prefer-const -- init is reassigned by onRequest hook\n let [url, init] = this.prepareFetchOptions(requestEndpoint, {\n method,\n timeoutMs: null,\n signal: config.signal,\n headers: requestHeaders,\n params: config.params,\n json: isReconnect ? undefined : config.json,\n });\n\n if (this.onRequest != null) {\n init = await this.onRequest(url, init);\n }\n\n const response = await this.asyncCaller.fetch(url.toString(), init);\n if (!response.body) {\n throw new Error(\"Expected response body from stream endpoint\");\n }\n\n if (!isReconnect && config.onInitialResponse) {\n await config.onInitialResponse(response);\n }\n\n // Insert the idle watchdog on the line stream (between the byte-line\n // decoder and the SSE decoder) so it can both reset on any line and\n // recognise `:` keep-alive heartbeats to drive `\"auto\"` mode. The SSE\n // decoder downstream discards those comment lines.\n const idleMode = config.idleReconnect ?? DEFAULT_IDLE_RECONNECT;\n const enableIdle = idleMode === \"auto\" || idleMode > 0;\n\n const lines = response.body.pipeThrough(BytesLineDecoder());\n const watched = enableIdle\n ? lines.pipeThrough(idleReconnectStream({ mode: idleMode }))\n : lines;\n const stream: ReadableStream<T> = watched.pipeThrough(\n SSEDecoder()\n ) as ReadableStream<T>;\n\n return { response, stream };\n };\n\n yield* streamWithRetry(makeRequest, {\n maxRetries: config.maxRetries ?? DEFAULT_MAX_RECONNECT_ATTEMPTS,\n signal: config.signal,\n onReconnect: config.onReconnect,\n });\n }\n}\n\nexport const REGEX_RUN_METADATA =\n /(\\/threads\\/(?<thread_id>.+))?\\/runs\\/(?<run_id>.+)/;\n\nexport function getRunMetadataFromResponse(\n response: Response\n): { run_id: string; thread_id?: string } | undefined {\n const contentLocation = response.headers.get(\"Content-Location\");\n if (!contentLocation) return undefined;\n\n const match = REGEX_RUN_METADATA.exec(contentLocation);\n\n if (!match?.groups?.run_id) return undefined;\n return {\n run_id: match.groups.run_id,\n thread_id: match.groups.thread_id || undefined,\n };\n}\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * Module-scoped, in-flight-only coalescing map for idempotent reads.\n *\n * Two independently-constructed clients (e.g. a React component that\n * remounts under Suspense / a reachability state flip, each minting a\n * fresh `Client`) can fire the *same* `getState` / `getHistory` read a\n * few milliseconds apart, before the first has resolved. Without\n * coalescing each pays the full round-trip — the duplicate\n * `threads/{id}/state` and `threads/{id}/history` requests seen on\n * reconnect.\n *\n * Keyed by `method + url + body + auth`, entries live only while a\n * request is in flight and are removed the moment it settles. This is\n * deliberately *not* a result cache: there is no TTL and no stored\n * payload, so it cannot serve stale data — it only ever shares a\n * promise that is already on the wire. Opt-in per call via\n * `{ dedupe: true }`, and skipped whenever the caller supplies its own\n * `AbortSignal` (so one consumer aborting can never cancel another's\n * read).\n */\nconst inFlightReads = new Map<string, Promise<unknown>>();\n\n/**\n * Deterministically serialize a prepared request's headers into a\n * stable string for use in the {@link inFlightReads} dedupe key. Header\n * names are normalized and sorted so ordering differences never produce\n * a different key, and every header (not just `x-api-key`) is included\n * so requests carrying different credentials never collide.\n */\nfunction serializeHeaders(headers: RequestInit[\"headers\"]): string {\n const normalized = mergeHeaders(\n headers as Record<string, HeaderValue> | undefined\n );\n return Object.keys(normalized)\n .sort()\n .map((name) => `${name}:${normalized[name]}`)\n .join(\"\\n\");\n}\n"],"mappings":"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"}
@@ -89,6 +89,23 @@ function normalizeUsage(usage) {
89
89
  total_tokens: usage.total_tokens ?? 0
90
90
  };
91
91
  }
92
+ function readRunId(data) {
93
+ const record = data;
94
+ return typeof record.run_id === "string" ? record.run_id : void 0;
95
+ }
96
+ function readUsage(data) {
97
+ const usage = data.usage;
98
+ return usage != null && typeof usage === "object" && !Array.isArray(usage) ? usage : void 0;
99
+ }
100
+ function readFinishMetadata(data) {
101
+ const record = data;
102
+ const isRecord = (value) => value != null && typeof value === "object" && !Array.isArray(value);
103
+ const merged = {
104
+ ...isRecord(record.responseMetadata) ? record.responseMetadata : {},
105
+ ...isRecord(record.metadata) ? record.metadata : {}
106
+ };
107
+ return Object.keys(merged).length > 0 ? merged : void 0;
108
+ }
92
109
  /**
93
110
  * Symbol keys for assembler → StreamingMessage communication.
94
111
  * Module-private: invisible to external consumers, accessible to
@@ -291,10 +308,10 @@ var StreamingMessage = class {
291
308
  }
292
309
  async #assembleMessage() {
293
310
  const contentBlocks = [];
294
- let id;
295
- let usage;
296
- let metadata = {};
297
- let finishReason;
311
+ let id = this.id;
312
+ let usage = normalizeUsage(this.assembled.usage);
313
+ let metadata = { ...this.assembled.finishMetadata ?? {} };
314
+ let finishReason = this.assembled.finishReason;
298
315
  for await (const event of this.#events) switch (event.event) {
299
316
  case "message-start":
300
317
  id = event.id ?? id;
@@ -311,23 +328,32 @@ var StreamingMessage = class {
311
328
  case "content-block-finish":
312
329
  contentBlocks[event.index] = event.content;
313
330
  break;
314
- case "message-finish":
331
+ case "message-finish": {
315
332
  finishReason = event.reason;
316
333
  if (event.usage) usage = normalizeUsage(event.usage);
317
- if (event.responseMetadata) metadata = {
334
+ const finishMetadata = readFinishMetadata(event);
335
+ if (finishMetadata != null) metadata = {
318
336
  ...metadata,
319
- ...event.responseMetadata
337
+ ...finishMetadata
320
338
  };
321
339
  break;
340
+ }
322
341
  default: break;
323
342
  }
324
343
  return new _langchain_core_messages.AIMessage({
325
344
  id,
326
345
  content: contentBlocks.filter((block) => block != null),
327
346
  usage_metadata: usage,
347
+ additional_kwargs: {
348
+ namespace: this.namespace,
349
+ ...this.node != null ? { node: this.node } : {},
350
+ ...this.assembled.runId != null ? { run_id: this.assembled.runId } : {},
351
+ ...this.metadata != null ? { metadata: this.metadata } : {},
352
+ ...this.assembled.usage != null ? { usage: this.assembled.usage } : {}
353
+ },
328
354
  response_metadata: {
355
+ ...finishReason != null ? { finish_reason: finishReason } : {},
329
356
  ...metadata,
330
- ...finishReason ? { finish_reason: finishReason } : {},
331
357
  output_version: "v1"
332
358
  }
333
359
  });
@@ -415,8 +441,9 @@ function applyContentDelta(target, delta) {
415
441
  function messageKeyFor(event) {
416
442
  const { namespace, node, data } = event.params;
417
443
  const namespaceKey = namespace.join("/");
444
+ const runId = readRunId(data);
418
445
  const messageId = data.event === "message-start" ? data.id ?? "" : "";
419
- return `${namespaceKey}::${node ?? ""}::${messageId}`;
446
+ return `${namespaceKey}::${node ?? ""}::${runId ?? messageId}`;
420
447
  }
421
448
  function toChatModelStreamEvent(event) {
422
449
  return event.params.data;
@@ -436,6 +463,7 @@ var MessageAssembler = class {
436
463
  consume(event) {
437
464
  const data = event.params.data;
438
465
  const namespaceNodeKey = `${event.params.namespace.join("/")}::${event.params.node ?? ""}`;
466
+ const runId = readRunId(data);
439
467
  if (data.event === "message-start") {
440
468
  const key = messageKeyFor(event);
441
469
  this.activeByNamespaceNode.set(namespaceNodeKey, key);
@@ -443,6 +471,8 @@ var MessageAssembler = class {
443
471
  id: data.id,
444
472
  namespace: [...event.params.namespace],
445
473
  node: event.params.node,
474
+ runId,
475
+ usage: readUsage(data),
446
476
  metadata: data.metadata,
447
477
  blocks: []
448
478
  };
@@ -454,14 +484,18 @@ var MessageAssembler = class {
454
484
  event
455
485
  };
456
486
  }
457
- const activeKey = this.activeByNamespaceNode.get(namespaceNodeKey);
487
+ const runKey = runId != null ? `${namespaceNodeKey}::${runId}` : void 0;
488
+ const fallbackKey = this.activeByNamespaceNode.get(namespaceNodeKey);
489
+ const fallbackMessage = fallbackKey != null ? this.activeMessages.get(fallbackKey) : void 0;
490
+ const activeKey = (runKey != null && this.activeMessages.has(runKey) ? runKey : void 0) ?? (runId == null || fallbackMessage?.runId == null ? fallbackKey : void 0);
458
491
  if (!activeKey) {
459
- const syntheticKey = `${namespaceNodeKey}::`;
492
+ const syntheticKey = runKey ?? `${namespaceNodeKey}::`;
460
493
  this.activeByNamespaceNode.set(namespaceNodeKey, syntheticKey);
461
494
  const synthetic = {
462
495
  id: data.id,
463
496
  namespace: [...event.params.namespace],
464
497
  node: event.params.node,
498
+ runId,
465
499
  blocks: []
466
500
  };
467
501
  this.activeMessages.set(syntheticKey, synthetic);
@@ -519,10 +553,10 @@ var MessageAssembler = class {
519
553
  };
520
554
  }
521
555
  case "message-finish":
522
- message.usage = data.usage;
523
- message.finishMetadata = data.responseMetadata;
524
- this.activeMessages.delete(activeKey);
525
- this.activeByNamespaceNode.delete(namespaceNodeKey);
556
+ message.usage = readUsage(data) ?? message.usage;
557
+ message.finishMetadata = readFinishMetadata(data);
558
+ message.finishReason = data.reason;
559
+ this.clearActiveMessage(activeKey, namespaceNodeKey);
526
560
  this.clearBlockIndexAliases(activeKey);
527
561
  return {
528
562
  kind: "message-finish",
@@ -535,8 +569,7 @@ var MessageAssembler = class {
535
569
  message: data.message,
536
570
  code: data.code
537
571
  };
538
- this.activeMessages.delete(activeKey);
539
- this.activeByNamespaceNode.delete(namespaceNodeKey);
572
+ this.clearActiveMessage(activeKey, namespaceNodeKey);
540
573
  this.clearBlockIndexAliases(activeKey);
541
574
  return {
542
575
  kind: "message-error",
@@ -546,6 +579,10 @@ var MessageAssembler = class {
546
579
  };
547
580
  }
548
581
  }
582
+ clearActiveMessage(activeKey, namespaceNodeKey) {
583
+ this.activeMessages.delete(activeKey);
584
+ if (this.activeByNamespaceNode.get(namespaceNodeKey) === activeKey) this.activeByNamespaceNode.delete(namespaceNodeKey);
585
+ }
549
586
  resolveBlockIndex(activeKey, blocks, protocolIndex, blockType) {
550
587
  const current = blocks[protocolIndex];
551
588
  if (current == null || current.type === blockType || areCompatibleBlockTypes(current.type, blockType)) {