@langchain/langgraph-api 1.4.0 → 1.4.2
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/api/runs.mjs +3 -24
- package/dist/cli/spawn-args.d.mts +35 -0
- package/dist/cli/spawn-args.mjs +85 -0
- package/dist/cli/spawn.d.mts +4 -17
- package/dist/cli/spawn.mjs +22 -21
- package/dist/protocol/service.mjs +3 -0
- package/dist/server.d.mts +2 -1
- package/dist/utils/run-auth.d.mts +13 -0
- package/dist/utils/run-auth.mjs +42 -0
- package/package.json +13 -8
package/dist/api/runs.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { logError, logger } from "../logging.mjs";
|
|
|
9
9
|
import * as schemas from "../schemas.mjs";
|
|
10
10
|
import { runs, threads } from "../storage/context.mjs";
|
|
11
11
|
import { getDisconnectAbortSignal, jsonExtra, waitKeepAlive, } from "../utils/hono.mjs";
|
|
12
|
+
import { applyAuthToRunConfig, applyRequestHeadersToRunConfig, } from "../utils/run-auth.mjs";
|
|
12
13
|
import { serialiseAsDict } from "../utils/serde.mjs";
|
|
13
14
|
const api = new Hono();
|
|
14
15
|
const createValidRun = async (threadId, payload, kwargs) => {
|
|
@@ -40,30 +41,8 @@ const createValidRun = async (threadId, payload, kwargs) => {
|
|
|
40
41
|
langsmith_example_id: run.langsmith_tracer.example_id,
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const key = rawKey.toLowerCase();
|
|
46
|
-
if (key.startsWith("x-")) {
|
|
47
|
-
if (["x-api-key", "x-tenant-id", "x-service-key"].includes(key)) {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
config.configurable ??= {};
|
|
51
|
-
config.configurable[key] = value;
|
|
52
|
-
}
|
|
53
|
-
else if (key === "user-agent") {
|
|
54
|
-
config.configurable ??= {};
|
|
55
|
-
config.configurable[key] = value;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
let userId;
|
|
60
|
-
if (auth) {
|
|
61
|
-
userId = auth.user.identity ?? auth.user.id;
|
|
62
|
-
config.configurable ??= {};
|
|
63
|
-
config.configurable["langgraph_auth_user"] = auth.user;
|
|
64
|
-
config.configurable["langgraph_auth_user_id"] = userId;
|
|
65
|
-
config.configurable["langgraph_auth_permissions"] = auth.scopes;
|
|
66
|
-
}
|
|
44
|
+
applyRequestHeadersToRunConfig(config, headers);
|
|
45
|
+
const userId = applyAuthToRunConfig(config, auth);
|
|
67
46
|
let feedbackKeys = run.feedback_keys != null
|
|
68
47
|
? Array.isArray(run.feedback_keys)
|
|
69
48
|
? run.feedback_keys
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { StartServerOptions } from "../server.mjs";
|
|
2
|
+
/** Default loader: tsx CLI with built-in watch. */
|
|
3
|
+
export declare const DEFAULT_NODE_LOADER = "tsx";
|
|
4
|
+
type LoaderRegistration = {
|
|
5
|
+
specifier: string;
|
|
6
|
+
flag: "--loader" | "--import";
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Shorthand loader names mapped to Node registration hooks.
|
|
10
|
+
* Use `"tsx"` (not `"tsx/esm"`) to spawn via the tsx CLI instead.
|
|
11
|
+
*
|
|
12
|
+
* `ts-node` uses `--loader` (not `--import`) because `ts-node/esm` is a
|
|
13
|
+
* custom ESM loader hook. Node 20+ deprecates `--loader` in favor of
|
|
14
|
+
* `module.register()`, but ts-node still documents this entrypoint.
|
|
15
|
+
*/
|
|
16
|
+
export declare const LOADER_REGISTRATIONS: Record<string, LoaderRegistration>;
|
|
17
|
+
export declare function resolveNodeLoader(configLoader: string | undefined, env?: NodeJS.ProcessEnv): string;
|
|
18
|
+
export declare function resolveLoaderRegistration(loader: string, resolve: (specifier: string) => string): LoaderRegistration & {
|
|
19
|
+
path: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function resolveLoaderPath(specifier: string, loader: string, resolve: (specifier: string) => string): string;
|
|
22
|
+
/** Convert a resolved filesystem path to a Node `--loader`/`--import` URL. */
|
|
23
|
+
export declare function toNodeModuleUrl(path: string): string;
|
|
24
|
+
export declare function usesTsxCli(loader: string): boolean;
|
|
25
|
+
export declare function buildSpawnArgs(options: {
|
|
26
|
+
nodeLoader: string;
|
|
27
|
+
reload: boolean;
|
|
28
|
+
pid: number;
|
|
29
|
+
payload: StartServerOptions;
|
|
30
|
+
resolve: (specifier: string) => string;
|
|
31
|
+
}): {
|
|
32
|
+
command: string;
|
|
33
|
+
args: string[];
|
|
34
|
+
};
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
+
/** Default loader: tsx CLI with built-in watch. */
|
|
4
|
+
export const DEFAULT_NODE_LOADER = "tsx";
|
|
5
|
+
/**
|
|
6
|
+
* Shorthand loader names mapped to Node registration hooks.
|
|
7
|
+
* Use `"tsx"` (not `"tsx/esm"`) to spawn via the tsx CLI instead.
|
|
8
|
+
*
|
|
9
|
+
* `ts-node` uses `--loader` (not `--import`) because `ts-node/esm` is a
|
|
10
|
+
* custom ESM loader hook. Node 20+ deprecates `--loader` in favor of
|
|
11
|
+
* `module.register()`, but ts-node still documents this entrypoint.
|
|
12
|
+
*/
|
|
13
|
+
export const LOADER_REGISTRATIONS = {
|
|
14
|
+
"ts-node": { specifier: "ts-node/esm", flag: "--loader" },
|
|
15
|
+
"ts-node/esm": { specifier: "ts-node/esm", flag: "--loader" },
|
|
16
|
+
};
|
|
17
|
+
export function resolveNodeLoader(configLoader, env = process.env) {
|
|
18
|
+
const envLoader = env.LANGGRAPH_NODE_LOADER?.trim();
|
|
19
|
+
if (envLoader)
|
|
20
|
+
return envLoader;
|
|
21
|
+
return configLoader ?? DEFAULT_NODE_LOADER;
|
|
22
|
+
}
|
|
23
|
+
export function resolveLoaderRegistration(loader, resolve) {
|
|
24
|
+
const registration = LOADER_REGISTRATIONS[loader] ?? {
|
|
25
|
+
specifier: loader,
|
|
26
|
+
flag: "--import",
|
|
27
|
+
};
|
|
28
|
+
const path = resolveLoaderPath(registration.specifier, loader, resolve);
|
|
29
|
+
return { ...registration, path };
|
|
30
|
+
}
|
|
31
|
+
export function resolveLoaderPath(specifier, loader, resolve) {
|
|
32
|
+
if (specifier.startsWith("file://")) {
|
|
33
|
+
return fileURLToPath(specifier);
|
|
34
|
+
}
|
|
35
|
+
if (isAbsolute(specifier)) {
|
|
36
|
+
return specifier;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return fileURLToPath(new URL(resolve(specifier)));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
const hint = loader === "ts-node" || loader.startsWith("ts-node/")
|
|
43
|
+
? ' Install "ts-node" and enable "emitDecoratorMetadata" in tsconfig when using reflect-metadata.'
|
|
44
|
+
: "";
|
|
45
|
+
throw new Error(`node_loader "${loader}" could not be resolved.${hint} Install the package or provide a valid module specifier, file path, or file:// URL.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Convert a resolved filesystem path to a Node `--loader`/`--import` URL. */
|
|
49
|
+
export function toNodeModuleUrl(path) {
|
|
50
|
+
if (path.startsWith("file://"))
|
|
51
|
+
return path;
|
|
52
|
+
return pathToFileURL(path).href;
|
|
53
|
+
}
|
|
54
|
+
export function usesTsxCli(loader) {
|
|
55
|
+
return loader === DEFAULT_NODE_LOADER;
|
|
56
|
+
}
|
|
57
|
+
export function buildSpawnArgs(options) {
|
|
58
|
+
const payloadArg = JSON.stringify(options.payload);
|
|
59
|
+
const sharedArgs = [options.pid.toString(), payloadArg];
|
|
60
|
+
const preloadUrl = new URL(options.resolve("../preload.mjs")).toString();
|
|
61
|
+
const entrypointPath = fileURLToPath(new URL(options.resolve("./entrypoint.mjs")));
|
|
62
|
+
if (usesTsxCli(options.nodeLoader)) {
|
|
63
|
+
const args = [
|
|
64
|
+
fileURLToPath(new URL("../../cli.mjs", options.resolve("tsx/esm/api"))),
|
|
65
|
+
...(options.reload ? ["watch"] : []),
|
|
66
|
+
"--clear-screen=false",
|
|
67
|
+
"--import",
|
|
68
|
+
preloadUrl,
|
|
69
|
+
entrypointPath,
|
|
70
|
+
...sharedArgs,
|
|
71
|
+
];
|
|
72
|
+
return { command: process.execPath, args };
|
|
73
|
+
}
|
|
74
|
+
const loader = resolveLoaderRegistration(options.nodeLoader, options.resolve);
|
|
75
|
+
const args = [
|
|
76
|
+
...(options.reload ? ["--watch"] : []),
|
|
77
|
+
loader.flag,
|
|
78
|
+
toNodeModuleUrl(loader.path),
|
|
79
|
+
"--import",
|
|
80
|
+
preloadUrl,
|
|
81
|
+
entrypointPath,
|
|
82
|
+
...sharedArgs,
|
|
83
|
+
];
|
|
84
|
+
return { command: process.execPath, args };
|
|
85
|
+
}
|
package/dist/cli/spawn.d.mts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { type StartServerOptions } from "../server.mjs";
|
|
1
2
|
export declare function spawnServer(args: {
|
|
2
3
|
host: string;
|
|
3
4
|
port: string;
|
|
4
5
|
nJobsPerWorker: string;
|
|
6
|
+
reload?: boolean;
|
|
5
7
|
}, context: {
|
|
6
8
|
config: {
|
|
7
9
|
graphs: Record<string, string | {
|
|
@@ -16,23 +18,8 @@ export declare function spawnServer(args: {
|
|
|
16
18
|
path?: string;
|
|
17
19
|
disable_studio_auth?: boolean;
|
|
18
20
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
disable_assistants?: boolean;
|
|
22
|
-
disable_threads?: boolean;
|
|
23
|
-
disable_runs?: boolean;
|
|
24
|
-
disable_store?: boolean;
|
|
25
|
-
disable_meta?: boolean;
|
|
26
|
-
cors?: {
|
|
27
|
-
allow_origins?: string[];
|
|
28
|
-
allow_methods?: string[];
|
|
29
|
-
allow_headers?: string[];
|
|
30
|
-
allow_credentials?: boolean;
|
|
31
|
-
allow_origin_regex?: string;
|
|
32
|
-
expose_headers?: string[];
|
|
33
|
-
max_age?: number;
|
|
34
|
-
};
|
|
35
|
-
};
|
|
21
|
+
node_loader?: string;
|
|
22
|
+
http?: StartServerOptions["http"];
|
|
36
23
|
};
|
|
37
24
|
env: NodeJS.ProcessEnv;
|
|
38
25
|
hostUrl: string;
|
package/dist/cli/spawn.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { fileURLToPath } from "node:url";
|
|
2
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { StartServerSchema } from "../server.mjs";
|
|
3
|
+
import { buildSpawnArgs, resolveNodeLoader } from "./spawn-args.mjs";
|
|
3
4
|
export async function spawnServer(args, context, options) {
|
|
4
5
|
const localUrl = `http://${args.host}:${args.port}`;
|
|
5
6
|
const studioUrl = `${context.hostUrl}/studio?baseUrl=${localUrl}`;
|
|
@@ -17,26 +18,26 @@ This in-memory server is designed for development and testing.
|
|
|
17
18
|
For production use, please use LangSmith Deployment.
|
|
18
19
|
|
|
19
20
|
`);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
21
|
+
const nodeLoader = resolveNodeLoader(context.config.node_loader, context.env);
|
|
22
|
+
const payload = StartServerSchema.parse({
|
|
23
|
+
port: Number.parseInt(args.port, 10),
|
|
24
|
+
nWorkers: Number.parseInt(args.nJobsPerWorker, 10),
|
|
25
|
+
host: args.host,
|
|
26
|
+
graphs: context.config.graphs,
|
|
27
|
+
auth: context.config.auth,
|
|
28
|
+
ui: context.config.ui,
|
|
29
|
+
ui_config: context.config.ui_config,
|
|
30
|
+
cwd: options.projectCwd,
|
|
31
|
+
http: context.config.http,
|
|
32
|
+
});
|
|
33
|
+
const { command, args: spawnArgs } = buildSpawnArgs({
|
|
34
|
+
nodeLoader,
|
|
35
|
+
reload: args.reload ?? true,
|
|
36
|
+
pid: options.pid,
|
|
37
|
+
payload,
|
|
38
|
+
resolve: (specifier) => import.meta.resolve(specifier),
|
|
39
|
+
});
|
|
40
|
+
return spawn(command, spawnArgs, {
|
|
40
41
|
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
41
42
|
env: {
|
|
42
43
|
...context.env,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { inferChannel, isPrefixMatch } from "@langchain/langgraph/stream";
|
|
2
2
|
import { v7 as uuid7 } from "@langchain/core/utils/uuid";
|
|
3
3
|
import { getAssistantId } from "../graph/load.mjs";
|
|
4
|
+
import { applyAuthToRunConfig } from "../utils/run-auth.mjs";
|
|
4
5
|
import { PROTOCOL_STREAM_RUN_KEY } from "./constants.mjs";
|
|
5
6
|
import { RunProtocolSession } from "./session/index.mjs";
|
|
6
7
|
const DEFAULT_RUN_STREAM_MODES = [
|
|
@@ -326,6 +327,7 @@ export class ProtocolService {
|
|
|
326
327
|
thread_id: record.threadId,
|
|
327
328
|
},
|
|
328
329
|
};
|
|
330
|
+
const userId = applyAuthToRunConfig(runConfig, record.auth);
|
|
329
331
|
const runPayload = {
|
|
330
332
|
assistant_id: assistantId,
|
|
331
333
|
input: isResume ? null : params.input,
|
|
@@ -366,6 +368,7 @@ export class ProtocolService {
|
|
|
366
368
|
[PROTOCOL_STREAM_RUN_KEY]: true,
|
|
367
369
|
}, {
|
|
368
370
|
threadId: record.threadId,
|
|
371
|
+
userId,
|
|
369
372
|
metadata: runPayload.metadata,
|
|
370
373
|
status: "pending",
|
|
371
374
|
multitaskStrategy: runPayload.multitask_strategy,
|
package/dist/server.d.mts
CHANGED
|
@@ -167,7 +167,8 @@ export declare const StartServerSchema: z.ZodObject<{
|
|
|
167
167
|
disable_meta?: boolean | undefined;
|
|
168
168
|
} | undefined;
|
|
169
169
|
}>;
|
|
170
|
-
export
|
|
170
|
+
export type StartServerOptions = z.infer<typeof StartServerSchema>;
|
|
171
|
+
export declare function startServer(options: StartServerOptions, storage?: {
|
|
171
172
|
ops?: Ops;
|
|
172
173
|
}): Promise<{
|
|
173
174
|
host: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AuthContext } from "../auth/index.mjs";
|
|
2
|
+
import type { RunnableConfig } from "../storage/types.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Copy allowed request headers into `config.configurable`, matching the REST
|
|
5
|
+
* runs API (`createValidRun`). Used by both REST and protocol-v2 run creation.
|
|
6
|
+
*/
|
|
7
|
+
export declare function applyRequestHeadersToRunConfig(config: RunnableConfig, headers: Headers | undefined): void;
|
|
8
|
+
/**
|
|
9
|
+
* Stamp the authenticated user onto `config.configurable` so graph nodes and
|
|
10
|
+
* tools can read `langgraph_auth_user`. Returns the resolved user id for run
|
|
11
|
+
* metadata when auth is present.
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyAuthToRunConfig(config: RunnableConfig, auth: AuthContext | undefined): string | undefined;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const BLOCKED_CONFIGURABLE_HEADERS = new Set([
|
|
2
|
+
"x-api-key",
|
|
3
|
+
"x-tenant-id",
|
|
4
|
+
"x-service-key",
|
|
5
|
+
]);
|
|
6
|
+
/**
|
|
7
|
+
* Copy allowed request headers into `config.configurable`, matching the REST
|
|
8
|
+
* runs API (`createValidRun`). Used by both REST and protocol-v2 run creation.
|
|
9
|
+
*/
|
|
10
|
+
export function applyRequestHeadersToRunConfig(config, headers) {
|
|
11
|
+
if (!headers)
|
|
12
|
+
return;
|
|
13
|
+
for (const [rawKey, value] of headers.entries()) {
|
|
14
|
+
const key = rawKey.toLowerCase();
|
|
15
|
+
if (key.startsWith("x-")) {
|
|
16
|
+
if (BLOCKED_CONFIGURABLE_HEADERS.has(key))
|
|
17
|
+
continue;
|
|
18
|
+
config.configurable ??= {};
|
|
19
|
+
config.configurable[key] = value;
|
|
20
|
+
}
|
|
21
|
+
else if (key === "user-agent") {
|
|
22
|
+
config.configurable ??= {};
|
|
23
|
+
config.configurable[key] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Stamp the authenticated user onto `config.configurable` so graph nodes and
|
|
29
|
+
* tools can read `langgraph_auth_user`. Returns the resolved user id for run
|
|
30
|
+
* metadata when auth is present.
|
|
31
|
+
*/
|
|
32
|
+
export function applyAuthToRunConfig(config, auth) {
|
|
33
|
+
if (!auth)
|
|
34
|
+
return undefined;
|
|
35
|
+
const userId = auth.user.identity ??
|
|
36
|
+
(typeof auth.user.id === "string" ? auth.user.id : undefined);
|
|
37
|
+
config.configurable ??= {};
|
|
38
|
+
config.configurable.langgraph_auth_user = auth.user;
|
|
39
|
+
config.configurable.langgraph_auth_user_id = userId;
|
|
40
|
+
config.configurable.langgraph_auth_permissions = auth.scopes;
|
|
41
|
+
return userId;
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/langgraph-api",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^18.19.0 || >=20.16.0"
|
|
@@ -71,37 +71,42 @@
|
|
|
71
71
|
"winston": "^3.17.0",
|
|
72
72
|
"winston-console-format": "^1.0.8",
|
|
73
73
|
"zod": "^3.25.76 || ^4",
|
|
74
|
-
"@langchain/langgraph-ui": "1.4.
|
|
74
|
+
"@langchain/langgraph-ui": "1.4.2"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"@langchain/core": "^1.1.48",
|
|
78
78
|
"@langchain/langgraph": "^1.3.6",
|
|
79
79
|
"@langchain/langgraph-checkpoint": "~0.0.16 || ^0.1.0 || ^1.0.0",
|
|
80
80
|
"@langchain/langgraph-sdk": "^1.9.3-rc.0",
|
|
81
|
+
"ts-node": "^10.9.2",
|
|
81
82
|
"typescript": "^5.5.4"
|
|
82
83
|
},
|
|
83
84
|
"peerDependenciesMeta": {
|
|
84
85
|
"@langchain/langgraph-sdk": {
|
|
85
86
|
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"ts-node": {
|
|
89
|
+
"optional": true
|
|
86
90
|
}
|
|
87
91
|
},
|
|
88
92
|
"devDependencies": {
|
|
89
93
|
"@langchain/core": "^1.2.1",
|
|
90
94
|
"@types/babel__code-frame": "^7.0.6",
|
|
91
95
|
"@types/node": "^18.15.11",
|
|
92
|
-
"@types/react": "^19.2.
|
|
96
|
+
"@types/react": "^19.2.17",
|
|
93
97
|
"@types/react-dom": "^19.0.3",
|
|
94
98
|
"@types/semver": "^7.7.0",
|
|
95
99
|
"deepagents": "^1.10.5",
|
|
96
100
|
"jose": "^6.0.10",
|
|
97
|
-
"langchain": "^1.5.
|
|
101
|
+
"langchain": "^1.5.2",
|
|
98
102
|
"postgres": "^3.4.5",
|
|
103
|
+
"ts-node": "^10.9.2",
|
|
99
104
|
"typescript": "^4.9.5 || ^5.4.5",
|
|
100
|
-
"vitest": "^4.1.
|
|
105
|
+
"vitest": "^4.1.9",
|
|
101
106
|
"wait-port": "^1.1.0",
|
|
102
|
-
"@langchain/langgraph": "1.4.
|
|
103
|
-
"@langchain/langgraph-
|
|
104
|
-
"@langchain/langgraph-
|
|
107
|
+
"@langchain/langgraph": "1.4.7",
|
|
108
|
+
"@langchain/langgraph-sdk": "1.9.25",
|
|
109
|
+
"@langchain/langgraph-checkpoint": "1.1.3"
|
|
105
110
|
},
|
|
106
111
|
"scripts": {
|
|
107
112
|
"clean": "rm -rf dist/ .turbo/ ./tests/graphs/.langgraph_api/ ./tests/protocol-v2/graphs/.langgraph_api/",
|