@langchain/langgraph-sdk 1.11.2 → 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.
- package/dist/client/base.cjs +7 -0
- package/dist/client/base.cjs.map +1 -1
- package/dist/client/base.d.cts.map +1 -1
- package/dist/client/base.d.ts.map +1 -1
- package/dist/client/base.js +7 -0
- package/dist/client/base.js.map +1 -1
- package/dist/stream/controller.cjs +39 -10
- package/dist/stream/controller.cjs.map +1 -1
- package/dist/stream/controller.d.cts +7 -6
- package/dist/stream/controller.d.cts.map +1 -1
- package/dist/stream/controller.d.ts +7 -6
- package/dist/stream/controller.d.ts.map +1 -1
- package/dist/stream/controller.js +37 -8
- package/dist/stream/controller.js.map +1 -1
- package/dist/stream/dispatch-config.cjs +23 -0
- package/dist/stream/dispatch-config.cjs.map +1 -0
- package/dist/stream/dispatch-config.js +23 -0
- package/dist/stream/dispatch-config.js.map +1 -0
- package/dist/stream/index.d.cts +1 -1
- package/dist/stream/index.d.ts +1 -1
- package/dist/stream/lifecycle-loading-tracker.cjs +8 -0
- package/dist/stream/lifecycle-loading-tracker.cjs.map +1 -1
- package/dist/stream/lifecycle-loading-tracker.js +8 -0
- package/dist/stream/lifecycle-loading-tracker.js.map +1 -1
- package/dist/stream/queue-adapter-agent-server.cjs +204 -0
- package/dist/stream/queue-adapter-agent-server.cjs.map +1 -0
- package/dist/stream/queue-adapter-agent-server.js +203 -0
- package/dist/stream/queue-adapter-agent-server.js.map +1 -0
- package/dist/stream/queue-adapter-local.cjs +50 -0
- package/dist/stream/queue-adapter-local.cjs.map +1 -0
- package/dist/stream/queue-adapter-local.js +49 -0
- package/dist/stream/queue-adapter-local.js.map +1 -0
- package/dist/stream/queue-adapter.cjs +7 -0
- package/dist/stream/queue-adapter.cjs.map +1 -0
- package/dist/stream/{submit-coordinator.d.cts → queue-adapter.d.cts} +6 -9
- package/dist/stream/queue-adapter.d.cts.map +1 -0
- package/dist/stream/queue-adapter.d.ts +24 -0
- package/dist/stream/queue-adapter.d.ts.map +1 -0
- package/dist/stream/queue-adapter.js +7 -0
- package/dist/stream/queue-adapter.js.map +1 -0
- package/dist/stream/submit-coordinator.cjs +82 -86
- package/dist/stream/submit-coordinator.cjs.map +1 -1
- package/dist/stream/submit-coordinator.d.ts +1 -26
- package/dist/stream/submit-coordinator.js +82 -85
- package/dist/stream/submit-coordinator.js.map +1 -1
- package/dist/stream/types.d.cts +25 -4
- package/dist/stream/types.d.cts.map +1 -1
- package/dist/stream/types.d.ts +25 -4
- package/dist/stream/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/stream/submit-coordinator.d.cts.map +0 -1
- package/dist/stream/submit-coordinator.d.ts.map +0 -1
package/dist/client/base.cjs
CHANGED
|
@@ -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
|
}
|
package/dist/client/base.cjs.map
CHANGED
|
@@ -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,
|
|
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,
|
|
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"}
|
package/dist/client/base.js
CHANGED
|
@@ -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
|
}
|
package/dist/client/base.js.map
CHANGED
|
@@ -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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"}
|
|
@@ -16,6 +16,7 @@ const require_message_metadata_tracker = require("./message-metadata-tracker.cjs
|
|
|
16
16
|
const require_lifecycle_loading_tracker = require("./lifecycle-loading-tracker.cjs");
|
|
17
17
|
const require_root_message_projection = require("./root-message-projection.cjs");
|
|
18
18
|
const require_optimistic_input = require("./optimistic-input.cjs");
|
|
19
|
+
const require_queue_adapter = require("./queue-adapter.cjs");
|
|
19
20
|
const require_submit_coordinator = require("./submit-coordinator.cjs");
|
|
20
21
|
const require_tool_calls = require("./tool-calls.cjs");
|
|
21
22
|
let _langchain_core_utils_uuid = require("@langchain/core/utils/uuid");
|
|
@@ -300,10 +301,11 @@ var StreamController = class {
|
|
|
300
301
|
});
|
|
301
302
|
this.#lifecycleLoading = new require_lifecycle_loading_tracker.LifecycleLoadingTracker({
|
|
302
303
|
store: this.rootStore,
|
|
303
|
-
isDisposed: () => this.#disposed
|
|
304
|
+
isDisposed: () => this.#disposed,
|
|
305
|
+
onSettled: () => this.#submitter.scheduleQueueDrainOnObservedIdle()
|
|
304
306
|
});
|
|
305
307
|
this.messageMetadataStore = this.#messageMetadata.store;
|
|
306
|
-
this.queueStore = new require_store.StreamStore(
|
|
308
|
+
this.queueStore = new require_store.StreamStore(require_queue_adapter.EMPTY_QUEUE);
|
|
307
309
|
this.#submitter = new require_submit_coordinator.SubmitCoordinator({
|
|
308
310
|
options: this.#options,
|
|
309
311
|
rootStore: this.rootStore,
|
|
@@ -454,7 +456,7 @@ var StreamController = class {
|
|
|
454
456
|
* thread so dispatching them against the new thread would be
|
|
455
457
|
* surprising. Mirrors the legacy `StreamOrchestrator` behaviour.
|
|
456
458
|
*/
|
|
457
|
-
this.queueStore.setState(() =>
|
|
459
|
+
this.queueStore.setState(() => require_queue_adapter.EMPTY_QUEUE);
|
|
458
460
|
await teardown;
|
|
459
461
|
}
|
|
460
462
|
if (this.#currentThreadId == null) {
|
|
@@ -488,6 +490,7 @@ var StreamController = class {
|
|
|
488
490
|
let threadExists = false;
|
|
489
491
|
let threadMissing = false;
|
|
490
492
|
let threadActive = true;
|
|
493
|
+
let hasActiveInterrupts = false;
|
|
491
494
|
try {
|
|
492
495
|
const state = await this.#fetchHydrationState();
|
|
493
496
|
if (this.#disposed || this.#currentThreadId !== hydratedThreadId) return;
|
|
@@ -575,6 +578,7 @@ var StreamController = class {
|
|
|
575
578
|
if (Array.isArray(state?.tasks)) {
|
|
576
579
|
const generationAtFetch = this.#submitGeneration;
|
|
577
580
|
const { activeInterrupts, activeIds } = collectActiveInterruptsFromTasks(state.tasks);
|
|
581
|
+
hasActiveInterrupts = activeInterrupts.length > 0;
|
|
578
582
|
for (const id of activeIds) this.#resolvedInterrupts.delete(id);
|
|
579
583
|
this.rootStore.setState((s) => ({
|
|
580
584
|
...s,
|
|
@@ -631,6 +635,15 @@ var StreamController = class {
|
|
|
631
635
|
*/
|
|
632
636
|
const thread = this.#ensureThread(hydratedThreadId, !threadActive);
|
|
633
637
|
/**
|
|
638
|
+
* Picks up runs pending from before this page load, or another tab.
|
|
639
|
+
* Fire-and-forget. Must run after `#ensureThread` above, not before:
|
|
640
|
+
* `AgentServerQueueAdapter`'s own `getThread` callback also calls
|
|
641
|
+
* `#ensureThread(threadId, true)`, which would otherwise create
|
|
642
|
+
* `#thread` with the pump deferred before the line above decides it
|
|
643
|
+
* should be eager.
|
|
644
|
+
*/
|
|
645
|
+
this.#submitter.hydrateQueue(hydratedThreadId);
|
|
646
|
+
/**
|
|
634
647
|
* Start the wildcard lifecycle watcher up-front for existing,
|
|
635
648
|
* active threads. The root content pump runs at `depth: 1`, which
|
|
636
649
|
* covers root-namespace and one-deep events but not arbitrarily-
|
|
@@ -645,7 +658,21 @@ var StreamController = class {
|
|
|
645
658
|
* against a not-yet-existent thread; `submitRun` / `respondInput`
|
|
646
659
|
* call `startLifecycleWatcher` on first submission instead.
|
|
647
660
|
*/
|
|
648
|
-
if (threadExists && threadActive)
|
|
661
|
+
if (threadExists && threadActive) {
|
|
662
|
+
thread.startLifecycleWatcher();
|
|
663
|
+
/**
|
|
664
|
+
* Seed `isLoading` from the checkpoint itself, not just a live
|
|
665
|
+
* lifecycle event: a subscriber that joins after a foreign run's
|
|
666
|
+
* own `running` event already fired would otherwise never observe
|
|
667
|
+
* it. Excludes a thread merely parked at an interrupt — that's
|
|
668
|
+
* waiting on a human, not "loading", matching
|
|
669
|
+
* `LifecycleLoadingTracker`'s own `interrupted → isLoading = false`.
|
|
670
|
+
*/
|
|
671
|
+
if (threadActive && !hasActiveInterrupts) this.rootStore.setState((s) => s.isLoading ? s : {
|
|
672
|
+
...s,
|
|
673
|
+
isLoading: true
|
|
674
|
+
});
|
|
675
|
+
}
|
|
649
676
|
if (threadExists) {
|
|
650
677
|
/**
|
|
651
678
|
* Seed subgraph discovery and promote subagent execution
|
|
@@ -937,10 +964,10 @@ var StreamController = class {
|
|
|
937
964
|
* Cancel a queued submission by id. Returns `true` when the entry
|
|
938
965
|
* was found and removed, `false` otherwise.
|
|
939
966
|
*
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
967
|
+
* Removes the client-side entry either way; also issues a server-side
|
|
968
|
+
* cancel when the configured queue adapter is server-backed (see
|
|
969
|
+
* `AgentServerAdapter.serverQueue`). A no-op for the default,
|
|
970
|
+
* client-only adapter.
|
|
944
971
|
*
|
|
945
972
|
* @param id - Client-side queue entry id to remove.
|
|
946
973
|
*/
|
|
@@ -948,7 +975,8 @@ var StreamController = class {
|
|
|
948
975
|
return this.#submitter.cancelQueued(id);
|
|
949
976
|
}
|
|
950
977
|
/**
|
|
951
|
-
* Drop every queued submission.
|
|
978
|
+
* Drop every queued submission. Cancels the underlying server-side
|
|
979
|
+
* runs too when the configured queue adapter is server-backed.
|
|
952
980
|
*/
|
|
953
981
|
async clearQueue() {
|
|
954
982
|
await this.#submitter.clearQueue();
|
|
@@ -1323,7 +1351,8 @@ var StreamController = class {
|
|
|
1323
1351
|
this.#interruptReplayThroughSeq = void 0;
|
|
1324
1352
|
this.#pendingHydrationInterruptEvents = [];
|
|
1325
1353
|
this.#discardPendingInterruptEvents();
|
|
1326
|
-
this.queueStore.setState(() =>
|
|
1354
|
+
this.queueStore.setState(() => require_queue_adapter.EMPTY_QUEUE);
|
|
1355
|
+
this.#submitter.detachQueue();
|
|
1327
1356
|
try {
|
|
1328
1357
|
await subscription?.unsubscribe();
|
|
1329
1358
|
} catch {}
|