@langchain/langgraph-api 1.2.2 → 1.2.3
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/experimental/embed/protocol.mjs +86 -15
- package/dist/protocol/service.d.mts +9 -1
- package/dist/protocol/service.mjs +65 -25
- package/package.json +6 -6
- package/dist/src/api/assistants.d.mts +0 -3
- package/dist/src/api/assistants.mjs +0 -194
- package/dist/src/api/meta.d.mts +0 -3
- package/dist/src/api/meta.mjs +0 -65
- package/dist/src/api/protocol.d.mts +0 -7
- package/dist/src/api/protocol.mjs +0 -157
- package/dist/src/api/runs.d.mts +0 -3
- package/dist/src/api/runs.mjs +0 -335
- package/dist/src/api/store.d.mts +0 -3
- package/dist/src/api/store.mjs +0 -111
- package/dist/src/api/threads.d.mts +0 -3
- package/dist/src/api/threads.mjs +0 -143
- package/dist/src/graph/load.utils.d.mts +0 -22
- package/dist/src/graph/load.utils.mjs +0 -59
- package/dist/src/protocol/service.d.mts +0 -101
- package/dist/src/protocol/service.mjs +0 -568
- package/dist/src/protocol/session/event-normalizers.d.mts +0 -52
- package/dist/src/protocol/session/index.d.mts +0 -261
- package/dist/src/protocol/session/index.mjs +0 -826
- package/dist/src/protocol/session/namespace.d.mts +0 -47
- package/dist/src/protocol/session/namespace.mjs +0 -62
- package/dist/src/protocol/types.d.mts +0 -121
- package/dist/src/protocol/types.mjs +0 -1
- package/dist/src/schemas.d.mts +0 -1552
- package/dist/src/semver/index.d.mts +0 -15
- package/dist/src/semver/index.mjs +0 -46
- package/dist/src/semver/satisfiesPeerRange.d.mts +0 -1
- package/dist/src/semver/satisfiesPeerRange.mjs +0 -19
- package/dist/src/state.d.mts +0 -3
- package/dist/src/state.mjs +0 -30
- package/dist/src/storage/context.d.mts +0 -3
- package/dist/src/storage/context.mjs +0 -11
- package/dist/src/storage/ops.mjs +0 -1281
- package/dist/src/stream.d.mts +0 -64
- package/dist/src/stream.mjs +0 -427
- package/dist/src/utils/hono.d.mts +0 -5
- package/dist/src/utils/hono.mjs +0 -24
- package/dist/src/utils/runnableConfig.d.mts +0 -3
- package/dist/src/utils/runnableConfig.mjs +0 -45
- package/dist/src/webhook.d.mts +0 -11
- package/dist/src/webhook.mjs +0 -30
|
@@ -7,7 +7,7 @@ import { serialiseAsDict } from "../../utils/serde.mjs";
|
|
|
7
7
|
import { jsonExtra } from "../../utils/hono.mjs";
|
|
8
8
|
import { RunProtocolSession } from "../../protocol/session/index.mjs";
|
|
9
9
|
import { PROTOCOL_STREAM_RUN_KEY } from "../../protocol/constants.mjs";
|
|
10
|
-
import { matchesSinkFilter } from "../../protocol/service.mjs";
|
|
10
|
+
import { matchesSinkFilter, normalizeMultitaskStrategy, DEFAULT_MULTITASK_STRATEGY, } from "../../protocol/service.mjs";
|
|
11
11
|
import { ProtocolCommandSchema, ThreadIdSchema, isRecord, createStubRun, } from "./utils.mjs";
|
|
12
12
|
import { DEFAULT_PROTOCOL_STREAM_MODES } from "./constants.mjs";
|
|
13
13
|
const EventsFilterSchema = z
|
|
@@ -112,6 +112,19 @@ export function registerProtocolRoutes(api, context, upgradeWebSocket) {
|
|
|
112
112
|
thread.currentRun = run;
|
|
113
113
|
return protocolSession;
|
|
114
114
|
}
|
|
115
|
+
async function hasPendingInterruptsForThread(thread) {
|
|
116
|
+
const assistantId = thread.assistantId;
|
|
117
|
+
if (assistantId == null)
|
|
118
|
+
return false;
|
|
119
|
+
try {
|
|
120
|
+
const graph = await context.getGraph(assistantId);
|
|
121
|
+
const snapshot = await graph.getState({ configurable: { thread_id: thread.threadId } }, { subgraphs: true });
|
|
122
|
+
return (snapshot.tasks ?? []).some((task) => Array.isArray(task.interrupts) && task.interrupts.length > 0);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
115
128
|
async function handleRunStart(thread, command) {
|
|
116
129
|
const params = isRecord(command.params)
|
|
117
130
|
? command.params
|
|
@@ -165,21 +178,39 @@ export function registerProtocolRoutes(api, context, upgradeWebSocket) {
|
|
|
165
178
|
},
|
|
166
179
|
});
|
|
167
180
|
}
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
181
|
+
// Fork/time-travel replays from `config.configurable.checkpoint_id`
|
|
182
|
+
// — the single legacy-compliant fork field. The SDK folds its
|
|
183
|
+
// `forkFrom` convenience option into this field client-side, so the
|
|
184
|
+
// server reads it from there rather than a top-level `forkFrom`.
|
|
185
|
+
// `createStubRun` only reliably forwards a fork target via the
|
|
186
|
+
// top-level `checkpoint_id`, so lift it out of `configurable` below.
|
|
173
187
|
const forkCheckpointId = (() => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
const configurable = isRecord(params.config) && isRecord(params.config.configurable)
|
|
189
|
+
? params.config.configurable
|
|
190
|
+
: undefined;
|
|
191
|
+
const checkpointId = configurable?.checkpoint_id;
|
|
192
|
+
return typeof checkpointId === "string" && checkpointId.length > 0
|
|
193
|
+
? checkpointId
|
|
194
|
+
: undefined;
|
|
178
195
|
})();
|
|
196
|
+
const currentRun = thread.currentRun;
|
|
197
|
+
const currentStatus = currentRun?.status;
|
|
198
|
+
const hasPendingInterrupts = params.input != null
|
|
199
|
+
? await hasPendingInterruptsForThread(thread)
|
|
200
|
+
: false;
|
|
201
|
+
const isResume = params.input != null &&
|
|
202
|
+
((currentRun != null && currentStatus === "interrupted") ||
|
|
203
|
+
hasPendingInterrupts);
|
|
204
|
+
if (isResume) {
|
|
205
|
+
// Drop stale lifecycle events from the paused run so late-attaching
|
|
206
|
+
// sinks do not replay `lifecycle.interrupted` after resume.
|
|
207
|
+
thread.queuedEvents.length = 0;
|
|
208
|
+
}
|
|
179
209
|
const run = createStubRun(thread.threadId, {
|
|
180
210
|
assistant_id: assistantId,
|
|
181
211
|
on_disconnect: "cancel",
|
|
182
|
-
input: params.input ?? null,
|
|
212
|
+
input: isResume ? null : (params.input ?? null),
|
|
213
|
+
command: isResume ? { resume: params.input } : undefined,
|
|
183
214
|
config: {
|
|
184
215
|
configurable: {
|
|
185
216
|
...(isRecord(params.config) && isRecord(params.config.configurable)
|
|
@@ -192,7 +223,13 @@ export function registerProtocolRoutes(api, context, upgradeWebSocket) {
|
|
|
192
223
|
// fact, *replaces* any inline configurable the caller passed),
|
|
193
224
|
// so this is the only reliable way to reach the engine with a
|
|
194
225
|
// fork target.
|
|
195
|
-
...(forkCheckpointId != null
|
|
226
|
+
...(forkCheckpointId != null && !isResume
|
|
227
|
+
? { checkpoint_id: forkCheckpointId }
|
|
228
|
+
: {}),
|
|
229
|
+
// Honor the caller's `multitaskStrategy`, falling back to `enqueue`
|
|
230
|
+
// to match the non-embed protocol-v2 server and the Python default.
|
|
231
|
+
multitask_strategy: normalizeMultitaskStrategy(params.multitaskStrategy) ??
|
|
232
|
+
DEFAULT_MULTITASK_STRATEGY,
|
|
196
233
|
metadata: Object.keys(runMetadata).length > 0 ? runMetadata : undefined,
|
|
197
234
|
stream_mode: DEFAULT_PROTOCOL_STREAM_MODES,
|
|
198
235
|
stream_subgraphs: true,
|
|
@@ -214,8 +251,37 @@ export function registerProtocolRoutes(api, context, upgradeWebSocket) {
|
|
|
214
251
|
const params = isRecord(command.params)
|
|
215
252
|
? command.params
|
|
216
253
|
: {};
|
|
217
|
-
|
|
218
|
-
|
|
254
|
+
// Build the resume input map. The SDK sends either a single
|
|
255
|
+
// `interrupt_id` / `response` or a `responses` batch (several
|
|
256
|
+
// interrupts at the same checkpoint, resumed in one command) — the
|
|
257
|
+
// protocol's `InputRespondOne` / `InputRespondMany` variants. Read
|
|
258
|
+
// leniently to tolerate clients pinned to older bindings.
|
|
259
|
+
const resumeInput = {};
|
|
260
|
+
if (Array.isArray(params.responses)) {
|
|
261
|
+
for (const entry of params.responses) {
|
|
262
|
+
if (!isRecord(entry) || typeof entry.interrupt_id !== "string") {
|
|
263
|
+
return jsonResponse({
|
|
264
|
+
type: "error",
|
|
265
|
+
id: command.id,
|
|
266
|
+
error: "invalid_argument",
|
|
267
|
+
message: "input.respond responses entries require an interrupt_id.",
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
resumeInput[entry.interrupt_id] = entry.response;
|
|
271
|
+
}
|
|
272
|
+
if (Object.keys(resumeInput).length === 0) {
|
|
273
|
+
return jsonResponse({
|
|
274
|
+
type: "error",
|
|
275
|
+
id: command.id,
|
|
276
|
+
error: "invalid_argument",
|
|
277
|
+
message: "input.respond requires at least one response.",
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else if (typeof params.interrupt_id === "string") {
|
|
282
|
+
resumeInput[params.interrupt_id] = params.response;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
219
285
|
return jsonResponse({
|
|
220
286
|
type: "error",
|
|
221
287
|
id: command.id,
|
|
@@ -236,7 +302,12 @@ export function registerProtocolRoutes(api, context, upgradeWebSocket) {
|
|
|
236
302
|
assistant_id: assistantId,
|
|
237
303
|
on_disconnect: "cancel",
|
|
238
304
|
input: null,
|
|
239
|
-
command: { resume:
|
|
305
|
+
command: { resume: resumeInput },
|
|
306
|
+
// Carry the SDK's `respond({ config, metadata })` onto the resumed
|
|
307
|
+
// run so it applies the same run config / metadata a fresh
|
|
308
|
+
// `run.start` would (both are part of the `input.respond` params).
|
|
309
|
+
config: isRecord(params.config) ? params.config : undefined,
|
|
310
|
+
metadata: isRecord(params.metadata) ? params.metadata : undefined,
|
|
240
311
|
stream_mode: DEFAULT_PROTOCOL_STREAM_MODES,
|
|
241
312
|
stream_subgraphs: true,
|
|
242
313
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RunsRepo, ThreadsRepo } from "../storage/types.mjs";
|
|
1
|
+
import type { RunsRepo, ThreadsRepo, MultitaskStrategy } from "../storage/types.mjs";
|
|
2
2
|
import type { EventSinkEntry, EventSinkFilter, ProtocolCommand, ProtocolError, ProtocolEvent, ProtocolSuccess, ThreadRecord, ProtocolTransportName } from "./types.mjs";
|
|
3
3
|
type ServiceBindings = {
|
|
4
4
|
runs: RunsRepo;
|
|
@@ -9,6 +9,14 @@ type ServiceBindings = {
|
|
|
9
9
|
* concrete delivery mechanism such as WebSocket or SSE.
|
|
10
10
|
*/
|
|
11
11
|
type EventSink = (message: ProtocolEvent) => Promise<void> | void;
|
|
12
|
+
export declare const DEFAULT_MULTITASK_STRATEGY: MultitaskStrategy;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the per-run `multitaskStrategy`. Honor it when it is one of the
|
|
15
|
+
* recognized strategies, otherwise return `undefined` so the caller falls
|
|
16
|
+
* back to {@link DEFAULT_MULTITASK_STRATEGY}. Lenient by design (matches
|
|
17
|
+
* the rest of `normalizeRunStart` and the Python reference server).
|
|
18
|
+
*/
|
|
19
|
+
export declare const normalizeMultitaskStrategy: (value: unknown) => MultitaskStrategy | undefined;
|
|
12
20
|
/**
|
|
13
21
|
* Thread-scoped connection registry and command dispatcher.
|
|
14
22
|
*
|
|
@@ -12,14 +12,28 @@ const DEFAULT_RUN_STREAM_MODES = [
|
|
|
12
12
|
"tasks",
|
|
13
13
|
];
|
|
14
14
|
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
// Concurrency strategies accepted on `run.start`, matching the four values
|
|
16
|
+
// the SDK's `multitaskStrategy` option can take.
|
|
17
|
+
const VALID_MULTITASK_STRATEGIES = [
|
|
18
|
+
"reject",
|
|
19
|
+
"rollback",
|
|
20
|
+
"interrupt",
|
|
21
|
+
"enqueue",
|
|
22
|
+
];
|
|
23
|
+
// Legacy stream-endpoint default: queue new runs behind active ones instead
|
|
24
|
+
// of interrupting. Used when the caller omits `multitaskStrategy`.
|
|
25
|
+
export const DEFAULT_MULTITASK_STRATEGY = "enqueue";
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the per-run `multitaskStrategy`. Honor it when it is one of the
|
|
28
|
+
* recognized strategies, otherwise return `undefined` so the caller falls
|
|
29
|
+
* back to {@link DEFAULT_MULTITASK_STRATEGY}. Lenient by design (matches
|
|
30
|
+
* the rest of `normalizeRunStart` and the Python reference server).
|
|
31
|
+
*/
|
|
32
|
+
export const normalizeMultitaskStrategy = (value) => {
|
|
33
|
+
return typeof value === "string" &&
|
|
34
|
+
VALID_MULTITASK_STRATEGIES.includes(value)
|
|
35
|
+
? value
|
|
36
|
+
: undefined;
|
|
23
37
|
};
|
|
24
38
|
const normalizeRunStart = (value) => {
|
|
25
39
|
if (isRecord(value)) {
|
|
@@ -28,7 +42,7 @@ const normalizeRunStart = (value) => {
|
|
|
28
42
|
input: value.input,
|
|
29
43
|
config: isRecord(value.config) ? value.config : undefined,
|
|
30
44
|
metadata: isRecord(value.metadata) ? value.metadata : undefined,
|
|
31
|
-
|
|
45
|
+
multitaskStrategy: normalizeMultitaskStrategy(value.multitaskStrategy),
|
|
32
46
|
};
|
|
33
47
|
}
|
|
34
48
|
return {
|
|
@@ -206,10 +220,31 @@ export class ProtocolService {
|
|
|
206
220
|
};
|
|
207
221
|
}
|
|
208
222
|
async handleInputRespond(record, command) {
|
|
209
|
-
|
|
223
|
+
// Build the resume input map (`{ [interrupt_id]: response }`). The
|
|
224
|
+
// SDK sends either a single `interrupt_id` / `response` or a
|
|
225
|
+
// `responses` batch (several interrupts at the same checkpoint, which
|
|
226
|
+
// must be resumed in one command) — the `InputRespondOne` /
|
|
227
|
+
// `InputRespondMany` variants of `InputRespondParams`. Read leniently
|
|
228
|
+
// to tolerate clients pinned to older bindings.
|
|
229
|
+
const rawParams = isRecord(command.params)
|
|
210
230
|
? command.params
|
|
211
231
|
: {};
|
|
212
|
-
|
|
232
|
+
const resumeInput = {};
|
|
233
|
+
if (Array.isArray(rawParams.responses)) {
|
|
234
|
+
for (const entry of rawParams.responses) {
|
|
235
|
+
if (!isRecord(entry) || typeof entry.interrupt_id !== "string") {
|
|
236
|
+
return this.error(command.id, "invalid_argument", "input.respond responses entries require an interrupt_id.");
|
|
237
|
+
}
|
|
238
|
+
resumeInput[entry.interrupt_id] = entry.response;
|
|
239
|
+
}
|
|
240
|
+
if (Object.keys(resumeInput).length === 0) {
|
|
241
|
+
return this.error(command.id, "invalid_argument", "input.respond requires at least one response.");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else if (typeof rawParams.interrupt_id === "string") {
|
|
245
|
+
resumeInput[rawParams.interrupt_id] = rawParams.response;
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
213
248
|
return this.error(command.id, "invalid_argument", "input.respond requires an interrupt_id.");
|
|
214
249
|
}
|
|
215
250
|
if (record.assistantId == null) {
|
|
@@ -225,11 +260,18 @@ export class ProtocolService {
|
|
|
225
260
|
if (!hasPendingInterrupts) {
|
|
226
261
|
return this.error(command.id, "invalid_argument", "input.respond can only be used while the run is interrupted.");
|
|
227
262
|
}
|
|
263
|
+
// `config` / `metadata` are part of `InputRespondParams` (both the
|
|
264
|
+
// `InputRespondOne` and `InputRespondMany` variants). The SDK's
|
|
265
|
+
// `respond({ config, metadata })` forwards them on the `input.respond`
|
|
266
|
+
// command so a resumed run can carry the same run config (model, user
|
|
267
|
+
// context, …) and metadata (trigger source, test flags, …) a fresh
|
|
268
|
+
// `run.start` would. Read them leniently — same posture as
|
|
269
|
+
// `normalizeRunStart`.
|
|
228
270
|
await this.createOrResumeRun(record, {
|
|
229
271
|
assistant_id: record.assistantId,
|
|
230
|
-
input:
|
|
231
|
-
config: undefined,
|
|
232
|
-
metadata: undefined,
|
|
272
|
+
input: resumeInput,
|
|
273
|
+
config: isRecord(rawParams.config) ? rawParams.config : undefined,
|
|
274
|
+
metadata: isRecord(rawParams.metadata) ? rawParams.metadata : undefined,
|
|
233
275
|
});
|
|
234
276
|
return {
|
|
235
277
|
type: "success",
|
|
@@ -254,22 +296,17 @@ export class ProtocolService {
|
|
|
254
296
|
((currentRun != null && currentStatus === "interrupted") ||
|
|
255
297
|
hasPendingInterrupts);
|
|
256
298
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* promotion because a resume must follow the thread's active
|
|
263
|
-
* checkpoint, not a historical fork.
|
|
299
|
+
* Fork/time-travel replays from `config.configurable.checkpoint_id`
|
|
300
|
+
* when the caller supplies it (the SDK folds its `forkFrom`
|
|
301
|
+
* convenience option into this field client-side). Forwarded as-is so
|
|
302
|
+
* the engine starts from the requested checkpoint instead of the
|
|
303
|
+
* thread's latest state.
|
|
264
304
|
*/
|
|
265
305
|
const runConfig = {
|
|
266
306
|
...params.config,
|
|
267
307
|
configurable: {
|
|
268
308
|
...params.config?.configurable,
|
|
269
309
|
thread_id: record.threadId,
|
|
270
|
-
...(!isResume && params.forkFrom?.checkpointId != null
|
|
271
|
-
? { checkpoint_id: params.forkFrom.checkpointId }
|
|
272
|
-
: {}),
|
|
273
310
|
},
|
|
274
311
|
};
|
|
275
312
|
const runPayload = {
|
|
@@ -284,7 +321,10 @@ export class ProtocolService {
|
|
|
284
321
|
stream_subgraphs: true,
|
|
285
322
|
stream_resumable: true,
|
|
286
323
|
if_not_exists: "create",
|
|
287
|
-
|
|
324
|
+
// Honor the caller's `multitaskStrategy` (the SDK sends it on every
|
|
325
|
+
// run.start), falling back to `enqueue` — the legacy stream-endpoint
|
|
326
|
+
// default — when omitted. Matches the Python protocol-v2 server.
|
|
327
|
+
multitask_strategy: params.multitaskStrategy ?? DEFAULT_MULTITASK_STRATEGY,
|
|
288
328
|
};
|
|
289
329
|
const [run] = await this.bindings.runs.put(uuid7(), assistantId, {
|
|
290
330
|
input: runPayload.input,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/langgraph-api",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^18.19.0 || >=20.16.0"
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@hono/node-server": "^1.19.13",
|
|
56
56
|
"@hono/node-ws": "^1.3.0",
|
|
57
57
|
"@hono/zod-validator": "^0.7.6",
|
|
58
|
-
"@langchain/protocol": "^0.0.
|
|
58
|
+
"@langchain/protocol": "^0.0.16",
|
|
59
59
|
"@types/json-schema": "^7.0.15",
|
|
60
60
|
"@typescript/vfs": "^1.6.0",
|
|
61
61
|
"dedent": "^1.5.3",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"winston": "^3.17.0",
|
|
73
73
|
"winston-console-format": "^1.0.8",
|
|
74
74
|
"zod": "^3.25.76 || ^4",
|
|
75
|
-
"@langchain/langgraph-ui": "1.2.
|
|
75
|
+
"@langchain/langgraph-ui": "1.2.3"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
78
|
"@langchain/core": "^1.1.44",
|
|
@@ -101,9 +101,9 @@
|
|
|
101
101
|
"typescript": "^4.9.5 || ^5.4.5",
|
|
102
102
|
"vitest": "^3.2.4",
|
|
103
103
|
"wait-port": "^1.1.0",
|
|
104
|
-
"@langchain/langgraph": "1.3.
|
|
105
|
-
"@langchain/langgraph-checkpoint": "1.0.
|
|
106
|
-
"@langchain/langgraph-sdk": "1.9.
|
|
104
|
+
"@langchain/langgraph": "1.3.2",
|
|
105
|
+
"@langchain/langgraph-checkpoint": "1.0.3",
|
|
106
|
+
"@langchain/langgraph-sdk": "1.9.10"
|
|
107
107
|
},
|
|
108
108
|
"scripts": {
|
|
109
109
|
"clean": "rm -rf dist/ .turbo/ ./tests/graphs/.langgraph_api/ ./tests/protocol-v2/graphs/.langgraph_api/",
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { zValidator } from "@hono/zod-validator";
|
|
2
|
-
import { Hono } from "hono";
|
|
3
|
-
import { v7 as uuid } from "uuid";
|
|
4
|
-
import { z } from "zod/v3";
|
|
5
|
-
import { getAssistantId, getCachedStaticGraphSchema, getGraph, } from "../graph/load.mjs";
|
|
6
|
-
import { getRuntimeGraphSchema } from "../graph/parser/index.mjs";
|
|
7
|
-
import { HTTPException } from "hono/http-exception";
|
|
8
|
-
import * as schemas from "../schemas.mjs";
|
|
9
|
-
import { assistants } from "../storage/context.mjs";
|
|
10
|
-
const api = new Hono();
|
|
11
|
-
const RunnableConfigSchema = z.object({
|
|
12
|
-
tags: z.array(z.string()).optional(),
|
|
13
|
-
metadata: z.record(z.unknown()).optional(),
|
|
14
|
-
run_name: z.string().optional(),
|
|
15
|
-
max_concurrency: z.number().optional(),
|
|
16
|
-
recursion_limit: z.number().optional(),
|
|
17
|
-
configurable: z.record(z.unknown()).optional(),
|
|
18
|
-
run_id: z.string().uuid().optional(),
|
|
19
|
-
});
|
|
20
|
-
const getRunnableConfig = (userConfig) => {
|
|
21
|
-
if (!userConfig)
|
|
22
|
-
return {};
|
|
23
|
-
return {
|
|
24
|
-
configurable: userConfig.configurable,
|
|
25
|
-
tags: userConfig.tags,
|
|
26
|
-
metadata: userConfig.metadata,
|
|
27
|
-
runName: userConfig.run_name,
|
|
28
|
-
maxConcurrency: userConfig.max_concurrency,
|
|
29
|
-
recursionLimit: userConfig.recursion_limit,
|
|
30
|
-
runId: userConfig.run_id,
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
api.post("/assistants", zValidator("json", schemas.AssistantCreate), async (c) => {
|
|
34
|
-
// Create Assistant
|
|
35
|
-
const payload = c.req.valid("json");
|
|
36
|
-
const assistant = await assistants().put(payload.assistant_id ?? uuid(), {
|
|
37
|
-
config: payload.config ?? {},
|
|
38
|
-
context: payload.context ?? {},
|
|
39
|
-
graph_id: payload.graph_id,
|
|
40
|
-
metadata: payload.metadata ?? {},
|
|
41
|
-
if_exists: payload.if_exists ?? "raise",
|
|
42
|
-
name: payload.name ?? "Untitled",
|
|
43
|
-
description: payload.description,
|
|
44
|
-
}, c.var.auth);
|
|
45
|
-
return c.json(assistant);
|
|
46
|
-
});
|
|
47
|
-
api.post("/assistants/search", zValidator("json", schemas.AssistantSearchRequest), async (c) => {
|
|
48
|
-
// Search Assistants
|
|
49
|
-
const payload = c.req.valid("json");
|
|
50
|
-
const result = [];
|
|
51
|
-
let total = 0;
|
|
52
|
-
for await (const item of assistants().search({
|
|
53
|
-
graph_id: payload.graph_id,
|
|
54
|
-
name: payload.name,
|
|
55
|
-
metadata: payload.metadata,
|
|
56
|
-
limit: payload.limit ?? 10,
|
|
57
|
-
offset: payload.offset ?? 0,
|
|
58
|
-
sort_by: payload.sort_by,
|
|
59
|
-
sort_order: payload.sort_order,
|
|
60
|
-
select: payload.select,
|
|
61
|
-
}, c.var.auth)) {
|
|
62
|
-
result.push(Object.fromEntries(Object.entries(item.assistant).filter(([k]) => !payload.select || payload.select.includes(k))));
|
|
63
|
-
if (total === 0) {
|
|
64
|
-
total = item.total;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (total === payload.limit) {
|
|
68
|
-
c.res.headers.set("X-Pagination-Next", ((payload.offset ?? 0) + total).toString());
|
|
69
|
-
c.res.headers.set("X-Pagination-Total", ((payload.offset ?? 0) + total + 1).toString());
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
c.res.headers.set("X-Pagination-Total", ((payload.offset ?? 0) + total).toString());
|
|
73
|
-
}
|
|
74
|
-
return c.json(result);
|
|
75
|
-
});
|
|
76
|
-
api.post("/assistants/count", zValidator("json", schemas.AssistantCountRequest), async (c) => {
|
|
77
|
-
const payload = c.req.valid("json");
|
|
78
|
-
const total = await assistants().count(payload, c.var.auth);
|
|
79
|
-
return c.json(total);
|
|
80
|
-
});
|
|
81
|
-
api.get("/assistants/:assistant_id", async (c) => {
|
|
82
|
-
// Get Assistant
|
|
83
|
-
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
84
|
-
return c.json(await assistants().get(assistantId, c.var.auth));
|
|
85
|
-
});
|
|
86
|
-
api.delete("/assistants/:assistant_id", async (c) => {
|
|
87
|
-
// Delete Assistant
|
|
88
|
-
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
89
|
-
const deleteThreads = c.req.query("delete_threads") === "true";
|
|
90
|
-
return c.json(await assistants().delete(assistantId, deleteThreads, c.var.auth));
|
|
91
|
-
});
|
|
92
|
-
api.patch("/assistants/:assistant_id", zValidator("json", schemas.AssistantPatch), async (c) => {
|
|
93
|
-
// Patch Assistant
|
|
94
|
-
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
95
|
-
const payload = c.req.valid("json");
|
|
96
|
-
return c.json(await assistants().patch(assistantId, payload, c.var.auth));
|
|
97
|
-
});
|
|
98
|
-
api.get("/assistants/:assistant_id/graph", zValidator("query", z.object({ xray: schemas.coercedBoolean.optional() })), async (c) => {
|
|
99
|
-
// Get Assistant Graph
|
|
100
|
-
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
101
|
-
const assistant = await assistants().get(assistantId, c.var.auth);
|
|
102
|
-
const { xray } = c.req.valid("query");
|
|
103
|
-
const config = getRunnableConfig(assistant.config);
|
|
104
|
-
const graph = await getGraph(assistant.graph_id, config);
|
|
105
|
-
const drawable = await graph.getGraphAsync({
|
|
106
|
-
...config,
|
|
107
|
-
xray: xray ?? undefined,
|
|
108
|
-
});
|
|
109
|
-
return c.json(drawable.toJSON());
|
|
110
|
-
});
|
|
111
|
-
api.get("/assistants/:assistant_id/schemas", zValidator("json", z.object({ config: RunnableConfigSchema.optional() })), async (c) => {
|
|
112
|
-
// Get Assistant Schemas
|
|
113
|
-
const json = c.req.valid("json");
|
|
114
|
-
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
115
|
-
const assistant = await assistants().get(assistantId, c.var.auth);
|
|
116
|
-
const config = getRunnableConfig(json.config);
|
|
117
|
-
const graph = await getGraph(assistant.graph_id, config);
|
|
118
|
-
const schema = await (async () => {
|
|
119
|
-
const runtimeSchema = await getRuntimeGraphSchema(graph);
|
|
120
|
-
if (runtimeSchema)
|
|
121
|
-
return runtimeSchema;
|
|
122
|
-
const graphSchema = await getCachedStaticGraphSchema(assistant.graph_id);
|
|
123
|
-
const rootGraphId = Object.keys(graphSchema).find((i) => !i.includes("|"));
|
|
124
|
-
if (!rootGraphId)
|
|
125
|
-
throw new HTTPException(404, { message: "Failed to find root graph" });
|
|
126
|
-
return graphSchema[rootGraphId];
|
|
127
|
-
})();
|
|
128
|
-
return c.json({
|
|
129
|
-
graph_id: assistant.graph_id,
|
|
130
|
-
input_schema: schema.input,
|
|
131
|
-
output_schema: schema.output,
|
|
132
|
-
state_schema: schema.state,
|
|
133
|
-
config_schema: schema.config,
|
|
134
|
-
// From JS PoV `configSchema` and `contextSchema` are indistinguishable,
|
|
135
|
-
// thus we use config_schema for context_schema.
|
|
136
|
-
context_schema: schema.config,
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
api.get("/assistants/:assistant_id/subgraphs/:namespace?", zValidator("param", z.object({ assistant_id: z.string(), namespace: z.string().optional() })), zValidator("query", z.object({ recurse: schemas.coercedBoolean.optional() })), async (c) => {
|
|
140
|
-
// Get Assistant Subgraphs
|
|
141
|
-
const { assistant_id, namespace } = c.req.valid("param");
|
|
142
|
-
const { recurse } = c.req.valid("query");
|
|
143
|
-
const assistantId = getAssistantId(assistant_id);
|
|
144
|
-
const assistant = await assistants().get(assistantId, c.var.auth);
|
|
145
|
-
const config = getRunnableConfig(assistant.config);
|
|
146
|
-
const graph = await getGraph(assistant.graph_id, config);
|
|
147
|
-
const result = [];
|
|
148
|
-
const subgraphsGenerator = "getSubgraphsAsync" in graph
|
|
149
|
-
? graph.getSubgraphsAsync.bind(graph)
|
|
150
|
-
: // @ts-expect-error older versions of langgraph don't have getSubgraphsAsync
|
|
151
|
-
graph.getSubgraphs.bind(graph);
|
|
152
|
-
let graphSchemaPromise;
|
|
153
|
-
for await (const [ns, subgraph] of subgraphsGenerator(namespace, recurse)) {
|
|
154
|
-
const schema = await (async () => {
|
|
155
|
-
const runtimeSchema = await getRuntimeGraphSchema(subgraph);
|
|
156
|
-
if (runtimeSchema)
|
|
157
|
-
return runtimeSchema;
|
|
158
|
-
graphSchemaPromise ??= getCachedStaticGraphSchema(assistant.graph_id);
|
|
159
|
-
const graphSchema = await graphSchemaPromise;
|
|
160
|
-
const rootGraphId = Object.keys(graphSchema).find((i) => !i.includes("|"));
|
|
161
|
-
if (!rootGraphId) {
|
|
162
|
-
throw new HTTPException(404, {
|
|
163
|
-
message: "Failed to find root graph",
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
return graphSchema[`${rootGraphId}|${ns}`] || graphSchema[rootGraphId];
|
|
167
|
-
})();
|
|
168
|
-
result.push([ns, schema]);
|
|
169
|
-
}
|
|
170
|
-
return c.json(Object.fromEntries(result));
|
|
171
|
-
});
|
|
172
|
-
api.post("/assistants/:assistant_id/latest", zValidator("json", schemas.AssistantLatestVersion), async (c) => {
|
|
173
|
-
// Set Latest Assistant Version
|
|
174
|
-
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
175
|
-
const { version } = c.req.valid("json");
|
|
176
|
-
return c.json(await assistants().setLatest(assistantId, version, c.var.auth));
|
|
177
|
-
});
|
|
178
|
-
api.post("/assistants/:assistant_id/versions", zValidator("json", z.object({
|
|
179
|
-
limit: z.number().min(1).max(1000).optional().default(10),
|
|
180
|
-
offset: z.number().min(0).optional().default(0),
|
|
181
|
-
metadata: z.record(z.unknown()).optional(),
|
|
182
|
-
})), async (c) => {
|
|
183
|
-
// Get Assistant Versions
|
|
184
|
-
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
185
|
-
const { limit, offset, metadata } = c.req.valid("json");
|
|
186
|
-
const versions = await assistants().getVersions(assistantId, { limit, offset, metadata }, c.var.auth);
|
|
187
|
-
if (!versions?.length) {
|
|
188
|
-
throw new HTTPException(404, {
|
|
189
|
-
message: `Assistant "${assistantId}" not found.`,
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
return c.json(versions);
|
|
193
|
-
});
|
|
194
|
-
export default api;
|
package/dist/src/api/meta.d.mts
DELETED
package/dist/src/api/meta.mjs
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { Hono } from "hono";
|
|
2
|
-
import * as fs from "node:fs/promises";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import * as url from "node:url";
|
|
5
|
-
const api = new Hono();
|
|
6
|
-
// Get the version using the same pattern as semver/index.mts
|
|
7
|
-
const packageJsonPath = path.resolve(url.fileURLToPath(import.meta.url), "../../../package.json");
|
|
8
|
-
let version;
|
|
9
|
-
let langgraph_js_version;
|
|
10
|
-
let versionInfoLoaded = false;
|
|
11
|
-
const loadVersionInfo = async () => {
|
|
12
|
-
try {
|
|
13
|
-
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
14
|
-
version = packageJson.version;
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
console.warn("Could not determine version of langgraph-api");
|
|
18
|
-
}
|
|
19
|
-
// Get the installed version of @langchain/langgraph
|
|
20
|
-
try {
|
|
21
|
-
const langgraphPkg = await import("@langchain/langgraph/package.json");
|
|
22
|
-
if (langgraphPkg?.default?.version) {
|
|
23
|
-
langgraph_js_version = langgraphPkg.default.version;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
console.warn("Could not determine version of @langchain/langgraph");
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
// read env variable
|
|
31
|
-
const env = process.env;
|
|
32
|
-
api.get("/info", async (c) => {
|
|
33
|
-
if (!versionInfoLoaded) {
|
|
34
|
-
await loadVersionInfo();
|
|
35
|
-
versionInfoLoaded = true;
|
|
36
|
-
}
|
|
37
|
-
const langsmithApiKey = env["LANGSMITH_API_KEY"] || env["LANGCHAIN_API_KEY"];
|
|
38
|
-
const langsmithTracing = (() => {
|
|
39
|
-
if (langsmithApiKey) {
|
|
40
|
-
// Check if any tracing variable is explicitly set to "false"
|
|
41
|
-
const tracingVars = [
|
|
42
|
-
env["LANGCHAIN_TRACING_V2"],
|
|
43
|
-
env["LANGCHAIN_TRACING"],
|
|
44
|
-
env["LANGSMITH_TRACING_V2"],
|
|
45
|
-
env["LANGSMITH_TRACING"],
|
|
46
|
-
];
|
|
47
|
-
// Return true unless explicitly disabled
|
|
48
|
-
return !tracingVars.some((val) => val === "false" || val === "False");
|
|
49
|
-
}
|
|
50
|
-
return undefined;
|
|
51
|
-
})();
|
|
52
|
-
return c.json({
|
|
53
|
-
version,
|
|
54
|
-
langgraph_js_version,
|
|
55
|
-
context: "js",
|
|
56
|
-
flags: {
|
|
57
|
-
assistants: true,
|
|
58
|
-
crons: false,
|
|
59
|
-
langsmith: !!langsmithTracing,
|
|
60
|
-
langsmith_tracing_replicas: true,
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
api.get("/ok", (c) => c.json({ ok: true }));
|
|
65
|
-
export default api;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { Hono } from "hono";
|
|
2
|
-
import type { UpgradeWebSocket } from "hono/ws";
|
|
3
|
-
import type { Ops } from "../storage/types.mjs";
|
|
4
|
-
/**
|
|
5
|
-
* Register thread-centric protocol transport routes for LangGraph API.
|
|
6
|
-
*/
|
|
7
|
-
export default function createProtocolApi(upgradeWebSocket: UpgradeWebSocket, ops: Ops): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|