@langchain/langgraph-api 0.0.19 → 0.0.21

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.
@@ -7,6 +7,28 @@ import { Assistants } from "../storage/ops.mjs";
7
7
  import * as schemas from "../schemas.mjs";
8
8
  import { HTTPException } from "hono/http-exception";
9
9
  const api = new Hono();
10
+ const RunnableConfigSchema = z.object({
11
+ tags: z.array(z.string()).optional(),
12
+ metadata: z.record(z.unknown()).optional(),
13
+ run_name: z.string().optional(),
14
+ max_concurrency: z.number().optional(),
15
+ recursion_limit: z.number().optional(),
16
+ configurable: z.record(z.unknown()).optional(),
17
+ run_id: z.string().uuid().optional(),
18
+ });
19
+ const getRunnableConfig = (userConfig) => {
20
+ if (!userConfig)
21
+ return {};
22
+ return {
23
+ configurable: userConfig.configurable,
24
+ tags: userConfig.tags,
25
+ metadata: userConfig.metadata,
26
+ runName: userConfig.run_name,
27
+ maxConcurrency: userConfig.max_concurrency,
28
+ recursionLimit: userConfig.recursion_limit,
29
+ runId: userConfig.run_id,
30
+ };
31
+ };
10
32
  api.post("/assistants", zValidator("json", schemas.AssistantCreate), async (c) => {
11
33
  // Create Assistant
12
34
  const payload = c.req.valid("json");
@@ -16,7 +38,7 @@ api.post("/assistants", zValidator("json", schemas.AssistantCreate), async (c) =
16
38
  metadata: payload.metadata ?? {},
17
39
  if_exists: payload.if_exists ?? "raise",
18
40
  name: payload.name ?? "Untitled",
19
- });
41
+ }, c.var.auth);
20
42
  return c.json(assistant);
21
43
  });
22
44
  api.post("/assistants/search", zValidator("json", schemas.AssistantSearchRequest), async (c) => {
@@ -28,7 +50,7 @@ api.post("/assistants/search", zValidator("json", schemas.AssistantSearchRequest
28
50
  metadata: payload.metadata,
29
51
  limit: payload.limit ?? 10,
30
52
  offset: payload.offset ?? 0,
31
- })) {
53
+ }, c.var.auth)) {
32
54
  result.push(item);
33
55
  }
34
56
  return c.json(result);
@@ -36,45 +58,23 @@ api.post("/assistants/search", zValidator("json", schemas.AssistantSearchRequest
36
58
  api.get("/assistants/:assistant_id", async (c) => {
37
59
  // Get Assistant
38
60
  const assistantId = getAssistantId(c.req.param("assistant_id"));
39
- return c.json(await Assistants.get(assistantId));
61
+ return c.json(await Assistants.get(assistantId, c.var.auth));
40
62
  });
41
63
  api.delete("/assistants/:assistant_id", async (c) => {
42
64
  // Delete Assistant
43
65
  const assistantId = getAssistantId(c.req.param("assistant_id"));
44
- return c.json(await Assistants.delete(assistantId));
66
+ return c.json(await Assistants.delete(assistantId, c.var.auth));
45
67
  });
46
68
  api.patch("/assistants/:assistant_id", zValidator("json", schemas.AssistantPatch), async (c) => {
47
69
  // Patch Assistant
48
70
  const assistantId = getAssistantId(c.req.param("assistant_id"));
49
71
  const payload = c.req.valid("json");
50
- return c.json(await Assistants.patch(assistantId, payload));
72
+ return c.json(await Assistants.patch(assistantId, payload, c.var.auth));
51
73
  });
52
- const RunnableConfigSchema = z.object({
53
- tags: z.array(z.string()).optional(),
54
- metadata: z.record(z.unknown()).optional(),
55
- run_name: z.string().optional(),
56
- max_concurrency: z.number().optional(),
57
- recursion_limit: z.number().optional(),
58
- configurable: z.record(z.unknown()).optional(),
59
- run_id: z.string().uuid().optional(),
60
- });
61
- const getRunnableConfig = (userConfig) => {
62
- if (!userConfig)
63
- return {};
64
- return {
65
- configurable: userConfig.configurable,
66
- tags: userConfig.tags,
67
- metadata: userConfig.metadata,
68
- runName: userConfig.run_name,
69
- maxConcurrency: userConfig.max_concurrency,
70
- recursionLimit: userConfig.recursion_limit,
71
- runId: userConfig.run_id,
72
- };
73
- };
74
74
  api.get("/assistants/:assistant_id/graph", zValidator("query", z.object({ xray: schemas.coercedBoolean.optional() })), async (c) => {
75
75
  // Get Assistant Graph
76
76
  const assistantId = getAssistantId(c.req.param("assistant_id"));
77
- const assistant = await Assistants.get(assistantId);
77
+ const assistant = await Assistants.get(assistantId, c.var.auth);
78
78
  const { xray } = c.req.valid("query");
79
79
  const config = getRunnableConfig(assistant.config);
80
80
  const graph = await getGraph(assistant.graph_id, config);
@@ -87,7 +87,7 @@ api.get("/assistants/:assistant_id/graph", zValidator("query", z.object({ xray:
87
87
  api.get("/assistants/:assistant_id/schemas", async (c) => {
88
88
  // Get Assistant Schemas
89
89
  const assistantId = getAssistantId(c.req.param("assistant_id"));
90
- const assistant = await Assistants.get(assistantId);
90
+ const assistant = await Assistants.get(assistantId, c.var.auth);
91
91
  const graphSchema = await getGraphSchema(assistant.graph_id);
92
92
  const rootGraphId = Object.keys(graphSchema).find((i) => !i.includes("|"));
93
93
  if (!rootGraphId)
@@ -106,7 +106,7 @@ api.get("/assistants/:assistant_id/subgraphs/:namespace?", zValidator("param", z
106
106
  const { assistant_id, namespace } = c.req.valid("param");
107
107
  const { recurse } = c.req.valid("query");
108
108
  const assistantId = getAssistantId(assistant_id);
109
- const assistant = await Assistants.get(assistantId);
109
+ const assistant = await Assistants.get(assistantId, c.var.auth);
110
110
  const config = getRunnableConfig(assistant.config);
111
111
  const graph = await getGraph(assistant.graph_id, config);
112
112
  const graphSchema = await getGraphSchema(assistant.graph_id);
@@ -131,7 +131,7 @@ api.post("/assistants/:assistant_id/latest", zValidator("json", schemas.Assistan
131
131
  // Set Latest Assistant Version
132
132
  const assistantId = getAssistantId(c.req.param("assistant_id"));
133
133
  const { version } = c.req.valid("json");
134
- return c.json(await Assistants.setLatest(assistantId, version));
134
+ return c.json(await Assistants.setLatest(assistantId, version, c.var.auth));
135
135
  });
136
136
  api.post("/assistants/:assistant_id/versions", zValidator("json", z.object({
137
137
  limit: z.number().min(1).max(1000).optional().default(10),
@@ -141,10 +141,12 @@ api.post("/assistants/:assistant_id/versions", zValidator("json", z.object({
141
141
  // Get Assistant Versions
142
142
  const assistantId = getAssistantId(c.req.param("assistant_id"));
143
143
  const { limit, offset, metadata } = c.req.valid("json");
144
- return c.json(await Assistants.getVersions(assistantId, {
145
- limit,
146
- offset,
147
- metadata,
148
- }));
144
+ const versions = await Assistants.getVersions(assistantId, { limit, offset, metadata }, c.var.auth);
145
+ if (!versions?.length) {
146
+ throw new HTTPException(404, {
147
+ message: `Assistant "${assistantId}" not found.`,
148
+ });
149
+ }
150
+ return c.json(versions);
149
151
  });
150
152
  export default api;
package/dist/api/runs.mjs CHANGED
@@ -11,8 +11,9 @@ import { getDisconnectAbortSignal, jsonExtra, waitKeepAlive, } from "../utils/ho
11
11
  import { logError, logger } from "../logging.mjs";
12
12
  import { v4 as uuid4 } from "uuid";
13
13
  const api = new Hono();
14
- const createValidRun = async (threadId, payload) => {
14
+ const createValidRun = async (threadId, payload, kwargs) => {
15
15
  const { assistant_id: assistantId, ...run } = payload;
16
+ const { auth, headers } = kwargs;
16
17
  const runId = uuid4();
17
18
  const streamMode = Array.isArray(payload.stream_mode)
18
19
  ? payload.stream_mode
@@ -32,6 +33,30 @@ const createValidRun = async (threadId, payload) => {
32
33
  config.configurable ??= {};
33
34
  Object.assign(config.configurable, run.checkpoint);
34
35
  }
36
+ if (headers) {
37
+ for (const [rawKey, value] of headers.entries()) {
38
+ const key = rawKey.toLowerCase();
39
+ if (key.startsWith("x-")) {
40
+ if (["x-api-key", "x-tenant-id", "x-service-key"].includes(key)) {
41
+ continue;
42
+ }
43
+ config.configurable ??= {};
44
+ config.configurable[key] = value;
45
+ }
46
+ else if (key === "user-agent") {
47
+ config.configurable ??= {};
48
+ config.configurable[key] = value;
49
+ }
50
+ }
51
+ }
52
+ let userId;
53
+ if (auth) {
54
+ userId = auth.user.identity ?? auth.user.id;
55
+ config.configurable ??= {};
56
+ config.configurable["langgraph_auth_user"] = auth.user;
57
+ config.configurable["langgraph_auth_user_id"] = userId;
58
+ config.configurable["langgraph_auth_permissions"] = auth.scopes;
59
+ }
35
60
  let feedbackKeys = run.feedback_keys != null
36
61
  ? Array.isArray(run.feedback_keys)
37
62
  ? run.feedback_keys
@@ -52,19 +77,20 @@ const createValidRun = async (threadId, payload) => {
52
77
  subgraphs: run.stream_subgraphs ?? false,
53
78
  }, {
54
79
  threadId,
80
+ userId,
55
81
  metadata: run.metadata,
56
82
  status: "pending",
57
83
  multitaskStrategy,
58
84
  preventInsertInInflight,
59
85
  afterSeconds: payload.after_seconds,
60
86
  ifNotExists: payload.if_not_exists,
61
- });
87
+ }, auth);
62
88
  if (first?.run_id === runId) {
63
89
  logger.info("Created run", { run_id: runId, thread_id: threadId });
64
90
  if ((multitaskStrategy === "interrupt" || multitaskStrategy === "rollback") &&
65
91
  inflight.length > 0) {
66
92
  try {
67
- await Runs.cancel(threadId, inflight.map((run) => run.run_id), { action: multitaskStrategy });
93
+ await Runs.cancel(threadId, inflight.map((run) => run.run_id), { action: multitaskStrategy }, auth);
68
94
  }
69
95
  catch (error) {
70
96
  logger.warn("Failed to cancel inflight runs, might be already cancelled", {
@@ -104,13 +130,16 @@ api.post("/threads/:thread_id/runs/crons", zValidator("param", z.object({ thread
104
130
  api.post("/runs/stream", zValidator("json", schemas.RunCreate), async (c) => {
105
131
  // Stream Stateless Run
106
132
  const payload = c.req.valid("json");
107
- const run = await createValidRun(undefined, payload);
133
+ const run = await createValidRun(undefined, payload, {
134
+ auth: c.var.auth,
135
+ headers: c.req.raw.headers,
136
+ });
108
137
  return streamSSE(c, async (stream) => {
109
138
  const cancelOnDisconnect = payload.on_disconnect === "cancel"
110
139
  ? getDisconnectAbortSignal(c, stream)
111
140
  : undefined;
112
141
  try {
113
- for await (const { event, data } of Runs.Stream.join(run.run_id, undefined, { cancelOnDisconnect, ignore404: true })) {
142
+ for await (const { event, data } of Runs.Stream.join(run.run_id, undefined, { cancelOnDisconnect, ignore404: true }, c.var.auth)) {
114
143
  await stream.writeSSE({ data: serialiseAsDict(data), event });
115
144
  }
116
145
  }
@@ -122,19 +151,28 @@ api.post("/runs/stream", zValidator("json", schemas.RunCreate), async (c) => {
122
151
  api.post("/runs/wait", zValidator("json", schemas.RunCreate), async (c) => {
123
152
  // Wait Stateless Run
124
153
  const payload = c.req.valid("json");
125
- const run = await createValidRun(undefined, payload);
126
- return waitKeepAlive(c, Runs.wait(run.run_id, undefined));
154
+ const run = await createValidRun(undefined, payload, {
155
+ auth: c.var.auth,
156
+ headers: c.req.raw.headers,
157
+ });
158
+ return waitKeepAlive(c, Runs.wait(run.run_id, undefined, c.var.auth));
127
159
  });
128
160
  api.post("/runs", zValidator("json", schemas.RunCreate), async (c) => {
129
161
  // Create Stateless Run
130
162
  const payload = c.req.valid("json");
131
- const run = await createValidRun(undefined, payload);
163
+ const run = await createValidRun(undefined, payload, {
164
+ auth: c.var.auth,
165
+ headers: c.req.raw.headers,
166
+ });
132
167
  return jsonExtra(c, run);
133
168
  });
134
169
  api.post("/runs/batch", zValidator("json", schemas.RunBatchCreate), async (c) => {
135
170
  // Batch Runs
136
171
  const payload = c.req.valid("json");
137
- const runs = await Promise.all(payload.map((run) => createValidRun(undefined, run)));
172
+ const runs = await Promise.all(payload.map((run) => createValidRun(undefined, run, {
173
+ auth: c.var.auth,
174
+ headers: c.req.raw.headers,
175
+ })));
138
176
  return jsonExtra(c, runs);
139
177
  });
140
178
  api.get("/threads/:thread_id/runs", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("query", z.object({
@@ -147,13 +185,8 @@ api.get("/threads/:thread_id/runs", zValidator("param", z.object({ thread_id: z.
147
185
  const { thread_id } = c.req.valid("param");
148
186
  const { limit, offset, status, metadata } = c.req.valid("query");
149
187
  const [runs] = await Promise.all([
150
- Runs.search(thread_id, {
151
- limit,
152
- offset,
153
- status,
154
- metadata,
155
- }),
156
- Threads.get(thread_id),
188
+ Runs.search(thread_id, { limit, offset, status, metadata }, c.var.auth),
189
+ Threads.get(thread_id, c.var.auth),
157
190
  ]);
158
191
  return jsonExtra(c, runs);
159
192
  });
@@ -161,20 +194,26 @@ api.post("/threads/:thread_id/runs", zValidator("param", z.object({ thread_id: z
161
194
  // Create Run
162
195
  const { thread_id } = c.req.valid("param");
163
196
  const payload = c.req.valid("json");
164
- const run = await createValidRun(thread_id, payload);
197
+ const run = await createValidRun(thread_id, payload, {
198
+ auth: c.var.auth,
199
+ headers: c.req.raw.headers,
200
+ });
165
201
  return jsonExtra(c, run);
166
202
  });
167
203
  api.post("/threads/:thread_id/runs/stream", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.RunCreate), async (c) => {
168
204
  // Stream Run
169
205
  const { thread_id } = c.req.valid("param");
170
206
  const payload = c.req.valid("json");
171
- const run = await createValidRun(thread_id, payload);
207
+ const run = await createValidRun(thread_id, payload, {
208
+ auth: c.var.auth,
209
+ headers: c.req.raw.headers,
210
+ });
172
211
  return streamSSE(c, async (stream) => {
173
212
  const cancelOnDisconnect = payload.on_disconnect === "cancel"
174
213
  ? getDisconnectAbortSignal(c, stream)
175
214
  : undefined;
176
215
  try {
177
- for await (const { event, data } of Runs.Stream.join(run.run_id, thread_id, { cancelOnDisconnect })) {
216
+ for await (const { event, data } of Runs.Stream.join(run.run_id, thread_id, { cancelOnDisconnect }, c.var.auth)) {
178
217
  await stream.writeSSE({ data: serialiseAsDict(data), event });
179
218
  }
180
219
  }
@@ -187,27 +226,32 @@ api.post("/threads/:thread_id/runs/wait", zValidator("param", z.object({ thread_
187
226
  // Wait Run
188
227
  const { thread_id } = c.req.valid("param");
189
228
  const payload = c.req.valid("json");
190
- const run = await createValidRun(thread_id, payload);
191
- return waitKeepAlive(c, Runs.join(run.run_id, thread_id));
229
+ const run = await createValidRun(thread_id, payload, {
230
+ auth: c.var.auth,
231
+ headers: c.req.raw.headers,
232
+ });
233
+ return waitKeepAlive(c, Runs.join(run.run_id, thread_id, c.var.auth));
192
234
  });
193
235
  api.get("/threads/:thread_id/runs/:run_id", zValidator("param", z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })), async (c) => {
194
236
  const { thread_id, run_id } = c.req.valid("param");
195
237
  const [run] = await Promise.all([
196
- Runs.get(run_id, thread_id),
197
- Threads.get(thread_id),
238
+ Runs.get(run_id, thread_id, c.var.auth),
239
+ Threads.get(thread_id, c.var.auth),
198
240
  ]);
241
+ if (run == null)
242
+ throw new HTTPException(404, { message: "Run not found" });
199
243
  return jsonExtra(c, run);
200
244
  });
201
245
  api.delete("/threads/:thread_id/runs/:run_id", zValidator("param", z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })), async (c) => {
202
246
  // Delete Run
203
247
  const { thread_id, run_id } = c.req.valid("param");
204
- await Runs.delete(run_id, thread_id);
248
+ await Runs.delete(run_id, thread_id, c.var.auth);
205
249
  return c.body(null, 204);
206
250
  });
207
251
  api.get("/threads/:thread_id/runs/:run_id/join", zValidator("param", z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })), async (c) => {
208
252
  // Join Run Http
209
253
  const { thread_id, run_id } = c.req.valid("param");
210
- return jsonExtra(c, await Runs.join(run_id, thread_id));
254
+ return jsonExtra(c, await Runs.join(run_id, thread_id, c.var.auth));
211
255
  });
212
256
  api.get("/threads/:thread_id/runs/:run_id/stream", zValidator("param", z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })), zValidator("query", z.object({ cancel_on_disconnect: schemas.coercedBoolean.optional() })), async (c) => {
213
257
  // Stream Run Http
@@ -217,9 +261,7 @@ api.get("/threads/:thread_id/runs/:run_id/stream", zValidator("param", z.object(
217
261
  const signal = cancel_on_disconnect
218
262
  ? getDisconnectAbortSignal(c, stream)
219
263
  : undefined;
220
- for await (const { event, data } of Runs.Stream.join(run_id, thread_id, {
221
- cancelOnDisconnect: signal,
222
- })) {
264
+ for await (const { event, data } of Runs.Stream.join(run_id, thread_id, { cancelOnDisconnect: signal }, c.var.auth)) {
223
265
  await stream.writeSSE({ data: serialiseAsDict(data), event });
224
266
  }
225
267
  });
@@ -231,9 +273,9 @@ api.post("/threads/:thread_id/runs/:run_id/cancel", zValidator("param", z.object
231
273
  // Cancel Run Http
232
274
  const { thread_id, run_id } = c.req.valid("param");
233
275
  const { wait, action } = c.req.valid("query");
234
- await Runs.cancel(thread_id, [run_id], { action });
276
+ await Runs.cancel(thread_id, [run_id], { action }, c.var.auth);
235
277
  if (wait)
236
- await Runs.join(run_id, thread_id);
278
+ await Runs.join(run_id, thread_id, c.var.auth);
237
279
  return c.body(null, wait ? 204 : 202);
238
280
  });
239
281
  export default api;
@@ -3,6 +3,7 @@ import { zValidator } from "@hono/zod-validator";
3
3
  import * as schemas from "../schemas.mjs";
4
4
  import { HTTPException } from "hono/http-exception";
5
5
  import { store as storageStore } from "../storage/store.mjs";
6
+ import { handleAuthEvent } from "../auth/custom.mjs";
6
7
  const api = new Hono();
7
8
  const validateNamespace = (namespace) => {
8
9
  if (!namespace || namespace.length === 0) {
@@ -34,6 +35,13 @@ api.post("/store/namespaces", zValidator("json", schemas.StoreListNamespaces), a
34
35
  validateNamespace(payload.prefix);
35
36
  if (payload.suffix)
36
37
  validateNamespace(payload.suffix);
38
+ await handleAuthEvent(c.var.auth, "store:list_namespaces", {
39
+ namespace: payload.prefix,
40
+ suffix: payload.suffix,
41
+ max_depth: payload.max_depth,
42
+ limit: payload.limit,
43
+ offset: payload.offset,
44
+ });
37
45
  return c.json({
38
46
  namespaces: await storageStore.listNamespaces({
39
47
  limit: payload.limit ?? 100,
@@ -49,6 +57,13 @@ api.post("/store/items/search", zValidator("json", schemas.StoreSearchItems), as
49
57
  const payload = c.req.valid("json");
50
58
  if (payload.namespace_prefix)
51
59
  validateNamespace(payload.namespace_prefix);
60
+ await handleAuthEvent(c.var.auth, "store:search", {
61
+ namespace: payload.namespace_prefix,
62
+ filter: payload.filter,
63
+ limit: payload.limit,
64
+ offset: payload.offset,
65
+ query: payload.query,
66
+ });
52
67
  const items = await storageStore.search(payload.namespace_prefix, {
53
68
  filter: payload.filter,
54
69
  limit: payload.limit ?? 10,
@@ -62,6 +77,11 @@ api.put("/store/items", zValidator("json", schemas.StorePutItem), async (c) => {
62
77
  const payload = c.req.valid("json");
63
78
  if (payload.namespace)
64
79
  validateNamespace(payload.namespace);
80
+ await handleAuthEvent(c.var.auth, "store:put", {
81
+ namespace: payload.namespace,
82
+ key: payload.key,
83
+ value: payload.value,
84
+ });
65
85
  await storageStore.put(payload.namespace, payload.key, payload.value);
66
86
  return c.body(null, 204);
67
87
  });
@@ -70,12 +90,20 @@ api.delete("/store/items", zValidator("json", schemas.StoreDeleteItem), async (c
70
90
  const payload = c.req.valid("json");
71
91
  if (payload.namespace)
72
92
  validateNamespace(payload.namespace);
93
+ await handleAuthEvent(c.var.auth, "store:delete", {
94
+ namespace: payload.namespace,
95
+ key: payload.key,
96
+ });
73
97
  await storageStore.delete(payload.namespace ?? [], payload.key);
74
98
  return c.body(null, 204);
75
99
  });
76
100
  api.get("/store/items", zValidator("query", schemas.StoreGetItem), async (c) => {
77
101
  // Get Item
78
102
  const payload = c.req.valid("query");
103
+ await handleAuthEvent(c.var.auth, "store:get", {
104
+ namespace: payload.namespace,
105
+ key: payload.key,
106
+ });
79
107
  const key = payload.key;
80
108
  const namespace = payload.namespace;
81
109
  return c.json(mapItemsToApi(await storageStore.get(namespace, key)));
@@ -11,12 +11,9 @@ const api = new Hono();
11
11
  api.post("/threads", zValidator("json", schemas.ThreadCreate), async (c) => {
12
12
  // Create Thread
13
13
  const payload = c.req.valid("json");
14
- const thread = await Threads.put(payload.thread_id || uuid4(), {
15
- metadata: payload.metadata,
16
- if_exists: payload.if_exists ?? "raise",
17
- });
14
+ const thread = await Threads.put(payload.thread_id || uuid4(), { metadata: payload.metadata, if_exists: payload.if_exists ?? "raise" }, c.var.auth);
18
15
  if (payload.supersteps?.length) {
19
- await Threads.State.bulk({ configurable: { thread_id: thread.thread_id } }, payload.supersteps);
16
+ await Threads.State.bulk({ configurable: { thread_id: thread.thread_id } }, payload.supersteps, c.var.auth);
20
17
  }
21
18
  return jsonExtra(c, thread);
22
19
  });
@@ -30,7 +27,7 @@ api.post("/threads/search", zValidator("json", schemas.ThreadSearchRequest), asy
30
27
  metadata: payload.metadata,
31
28
  limit: payload.limit ?? 10,
32
29
  offset: payload.offset ?? 0,
33
- })) {
30
+ }, c.var.auth)) {
34
31
  result.push({
35
32
  ...item,
36
33
  created_at: item.created_at.toISOString(),
@@ -43,7 +40,7 @@ api.get("/threads/:thread_id/state", zValidator("param", z.object({ thread_id: z
43
40
  // Get Latest Thread State
44
41
  const { thread_id } = c.req.valid("param");
45
42
  const { subgraphs } = c.req.valid("query");
46
- const state = stateSnapshotToThreadState(await Threads.State.get({ configurable: { thread_id } }, { subgraphs }));
43
+ const state = stateSnapshotToThreadState(await Threads.State.get({ configurable: { thread_id } }, { subgraphs }, c.var.auth));
47
44
  return jsonExtra(c, state);
48
45
  });
49
46
  api.post("/threads/:thread_id/state", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", z.object({
@@ -69,7 +66,7 @@ api.post("/threads/:thread_id/state", zValidator("param", z.object({ thread_id:
69
66
  config.configurable ??= {};
70
67
  Object.assign(config.configurable, payload.checkpoint);
71
68
  }
72
- const inserted = await Threads.State.post(config, payload.values, payload.as_node);
69
+ const inserted = await Threads.State.post(config, payload.values, payload.as_node, c.var.auth);
73
70
  return jsonExtra(c, inserted);
74
71
  });
75
72
  api.get("/threads/:thread_id/state/:checkpoint_id", zValidator("param", z.object({
@@ -79,7 +76,7 @@ api.get("/threads/:thread_id/state/:checkpoint_id", zValidator("param", z.object
79
76
  // Get Thread State At Checkpoint
80
77
  const { thread_id, checkpoint_id } = c.req.valid("param");
81
78
  const { subgraphs } = c.req.valid("query");
82
- const state = stateSnapshotToThreadState(await Threads.State.get({ configurable: { thread_id, checkpoint_id } }, { subgraphs }));
79
+ const state = stateSnapshotToThreadState(await Threads.State.get({ configurable: { thread_id, checkpoint_id } }, { subgraphs }, c.var.auth));
83
80
  return jsonExtra(c, state);
84
81
  });
85
82
  api.post("/threads/:thread_id/state/checkpoint", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", z.object({
@@ -89,7 +86,7 @@ api.post("/threads/:thread_id/state/checkpoint", zValidator("param", z.object({
89
86
  // Get Thread State At Checkpoint Post
90
87
  const { thread_id } = c.req.valid("param");
91
88
  const { checkpoint, subgraphs } = c.req.valid("json");
92
- const state = stateSnapshotToThreadState(await Threads.State.get({ configurable: { thread_id, ...checkpoint } }, { subgraphs }));
89
+ const state = stateSnapshotToThreadState(await Threads.State.get({ configurable: { thread_id, ...checkpoint } }, { subgraphs }, c.var.auth));
93
90
  return jsonExtra(c, state);
94
91
  });
95
92
  api.get("/threads/:thread_id/history", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("query", z.object({
@@ -103,7 +100,7 @@ api.get("/threads/:thread_id/history", zValidator("param", z.object({ thread_id:
103
100
  // Get Thread History
104
101
  const { thread_id } = c.req.valid("param");
105
102
  const { limit, before } = c.req.valid("query");
106
- const states = await Threads.State.list({ configurable: { thread_id, checkpoint_ns: "" } }, { limit, before });
103
+ const states = await Threads.State.list({ configurable: { thread_id, checkpoint_ns: "" } }, { limit, before }, c.var.auth);
107
104
  return jsonExtra(c, states.map(stateSnapshotToThreadState));
108
105
  });
109
106
  api.post("/threads/:thread_id/history", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", z.object({
@@ -121,29 +118,29 @@ api.post("/threads/:thread_id/history", zValidator("param", z.object({ thread_id
121
118
  // Get Thread History Post
122
119
  const { thread_id } = c.req.valid("param");
123
120
  const { limit, before, metadata, checkpoint } = c.req.valid("json");
124
- const states = await Threads.State.list({ configurable: { thread_id, checkpoint_ns: "", ...checkpoint } }, { limit, before, metadata });
121
+ const states = await Threads.State.list({ configurable: { thread_id, checkpoint_ns: "", ...checkpoint } }, { limit, before, metadata }, c.var.auth);
125
122
  return jsonExtra(c, states.map(stateSnapshotToThreadState));
126
123
  });
127
124
  api.get("/threads/:thread_id", zValidator("param", z.object({ thread_id: z.string().uuid() })), async (c) => {
128
125
  // Get Thread
129
126
  const { thread_id } = c.req.valid("param");
130
- return jsonExtra(c, await Threads.get(thread_id));
127
+ return jsonExtra(c, await Threads.get(thread_id, c.var.auth));
131
128
  });
132
129
  api.delete("/threads/:thread_id", zValidator("param", z.object({ thread_id: z.string().uuid() })), async (c) => {
133
130
  // Delete Thread
134
131
  const { thread_id } = c.req.valid("param");
135
- await Threads.delete(thread_id);
132
+ await Threads.delete(thread_id, c.var.auth);
136
133
  return new Response(null, { status: 204 });
137
134
  });
138
135
  api.patch("/threads/:thread_id", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", z.object({ metadata: z.record(z.string(), z.unknown()) })), async (c) => {
139
136
  // Patch Thread
140
137
  const { thread_id } = c.req.valid("param");
141
138
  const { metadata } = c.req.valid("json");
142
- return jsonExtra(c, await Threads.patch(thread_id, { metadata }));
139
+ return jsonExtra(c, await Threads.patch(thread_id, { metadata }, c.var.auth));
143
140
  });
144
141
  api.post("/threads/:thread_id/copy", zValidator("param", z.object({ thread_id: z.string().uuid() })), async (c) => {
145
142
  // Copy Thread
146
143
  const { thread_id } = c.req.valid("param");
147
- return jsonExtra(c, await Threads.copy(thread_id));
144
+ return jsonExtra(c, await Threads.copy(thread_id, c.var.auth));
148
145
  });
149
146
  export default api;
@@ -0,0 +1,58 @@
1
+ import { authorize, authenticate, isAuthRegistered, isStudioAuthDisabled, } from "./index.mjs";
2
+ export function isAuthMatching(metadata, filters) {
3
+ if (filters == null)
4
+ return true;
5
+ for (const [key, value] of Object.entries(filters)) {
6
+ if (typeof value === "object" && value != null) {
7
+ if (value.$eq) {
8
+ if (metadata?.[key] !== value.$eq)
9
+ return false;
10
+ }
11
+ else if (value.$contains) {
12
+ if (!Array.isArray(metadata?.[key]) ||
13
+ !metadata?.[key].includes(value.$contains)) {
14
+ return false;
15
+ }
16
+ }
17
+ }
18
+ else {
19
+ if (metadata?.[key] !== value)
20
+ return false;
21
+ }
22
+ }
23
+ return true;
24
+ }
25
+ export const handleAuthEvent = async (context, event, value) => {
26
+ const [resource, action] = event.split(":");
27
+ const result = await authorize({
28
+ resource,
29
+ action,
30
+ context,
31
+ value,
32
+ });
33
+ return [result.filters, result.value];
34
+ };
35
+ const STUDIO_USER = {
36
+ display_name: "langgraph-studio-user",
37
+ identity: "langgraph-studio-user",
38
+ permissions: [],
39
+ is_authenticated: true,
40
+ };
41
+ export const auth = () => {
42
+ return async (c, next) => {
43
+ if (!isAuthRegistered())
44
+ return next();
45
+ if (!isStudioAuthDisabled() &&
46
+ c.req.header("x-auth-scheme") === "langsmith") {
47
+ c.set("auth", {
48
+ user: STUDIO_USER,
49
+ scopes: STUDIO_USER.permissions.slice(),
50
+ });
51
+ return next();
52
+ }
53
+ const auth = await authenticate(c.req.raw);
54
+ c.set("auth", auth);
55
+ return next();
56
+ };
57
+ };
58
+ export { registerAuth } from "./index.mjs";
@@ -0,0 +1,40 @@
1
+ export declare const isAuthRegistered: () => boolean;
2
+ export declare const isStudioAuthDisabled: () => boolean;
3
+ export type AuthFilters = Record<string, string | {
4
+ $eq?: string;
5
+ $contains?: string;
6
+ }> | undefined;
7
+ export interface AuthContext {
8
+ user: {
9
+ identity: string;
10
+ permissions: string[];
11
+ display_name: string;
12
+ is_authenticated: boolean;
13
+ [key: string]: unknown;
14
+ };
15
+ scopes: string[];
16
+ }
17
+ export declare function authorize(payload: {
18
+ resource: string;
19
+ action: string;
20
+ value: unknown;
21
+ context: AuthContext | undefined | null;
22
+ }): Promise<{
23
+ filters: AuthFilters;
24
+ value: unknown;
25
+ }>;
26
+ export declare function authenticate(request: Request): Promise<{
27
+ scopes: string[];
28
+ user: {
29
+ permissions: string[];
30
+ is_authenticated: boolean;
31
+ display_name: string;
32
+ identity: string;
33
+ };
34
+ }>;
35
+ export declare function registerAuth(auth: {
36
+ path?: string;
37
+ disable_studio_auth?: boolean;
38
+ }, options: {
39
+ cwd: string;
40
+ }): Promise<void>;