@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.
- package/dist/api/assistants.mjs +38 -36
- package/dist/api/runs.mjs +72 -30
- package/dist/api/store.mjs +28 -0
- package/dist/api/threads.mjs +13 -16
- package/dist/auth/custom.mjs +58 -0
- package/dist/auth/index.d.mts +40 -0
- package/dist/auth/index.mjs +120 -0
- package/dist/cli/spawn.d.mts +4 -0
- package/dist/cli/spawn.mjs +1 -0
- package/dist/graph/load.mjs +2 -2
- package/dist/queue.mjs +1 -1
- package/dist/schemas.mjs +1 -0
- package/dist/server.mjs +17 -4
- package/dist/storage/ops.mjs +318 -115
- package/dist/ui/load.mjs +7 -8
- package/package.json +15 -9
- package/dist/ui/bundler.d.mts +0 -21
- package/dist/ui/bundler.mjs +0 -86
- package/dist/ui/render.template.mts +0 -27
package/dist/api/assistants.mjs
CHANGED
|
@@ -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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/api/store.mjs
CHANGED
|
@@ -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)));
|
package/dist/api/threads.mjs
CHANGED
|
@@ -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>;
|