@langchain/langgraph-api 1.2.2-rc.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/api/assistants.d.mts +3 -0
- package/dist/src/api/assistants.mjs +194 -0
- package/dist/src/api/meta.d.mts +3 -0
- package/dist/src/api/meta.mjs +65 -0
- package/dist/src/api/protocol.d.mts +7 -0
- package/dist/src/api/protocol.mjs +157 -0
- package/dist/src/api/runs.d.mts +3 -0
- package/dist/src/api/runs.mjs +335 -0
- package/dist/src/api/store.d.mts +3 -0
- package/dist/src/api/store.mjs +111 -0
- package/dist/src/api/threads.d.mts +3 -0
- package/dist/src/api/threads.mjs +143 -0
- package/dist/src/graph/load.utils.d.mts +22 -0
- package/dist/src/graph/load.utils.mjs +59 -0
- package/dist/src/protocol/service.d.mts +101 -0
- package/dist/src/protocol/service.mjs +568 -0
- package/dist/src/protocol/session/event-normalizers.d.mts +52 -0
- package/dist/src/protocol/session/index.d.mts +261 -0
- package/dist/src/protocol/session/index.mjs +826 -0
- package/dist/src/protocol/session/namespace.d.mts +47 -0
- package/dist/src/protocol/session/namespace.mjs +62 -0
- package/dist/src/protocol/types.d.mts +121 -0
- package/dist/src/protocol/types.mjs +1 -0
- package/dist/src/schemas.d.mts +1552 -0
- package/dist/src/semver/index.d.mts +15 -0
- package/dist/src/semver/index.mjs +46 -0
- package/dist/src/semver/satisfiesPeerRange.d.mts +1 -0
- package/dist/src/semver/satisfiesPeerRange.mjs +19 -0
- package/dist/src/state.d.mts +3 -0
- package/dist/src/state.mjs +30 -0
- package/dist/src/storage/context.d.mts +3 -0
- package/dist/src/storage/context.mjs +11 -0
- package/dist/src/storage/ops.mjs +1281 -0
- package/dist/src/stream.d.mts +64 -0
- package/dist/src/stream.mjs +427 -0
- package/dist/src/utils/hono.d.mts +5 -0
- package/dist/src/utils/hono.mjs +24 -0
- package/dist/src/utils/runnableConfig.d.mts +3 -0
- package/dist/src/utils/runnableConfig.mjs +45 -0
- package/dist/src/webhook.d.mts +11 -0
- package/dist/src/webhook.mjs +30 -0
- package/package.json +4 -4
|
@@ -0,0 +1,194 @@
|
|
|
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;
|
|
@@ -0,0 +1,65 @@
|
|
|
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;
|
|
@@ -0,0 +1,7 @@
|
|
|
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, "/">;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { streamSSE } from "hono/streaming";
|
|
4
|
+
import { v7 as uuid7 } from "uuid";
|
|
5
|
+
import { z } from "zod/v3";
|
|
6
|
+
import { ProtocolService } from "../protocol/service.mjs";
|
|
7
|
+
import { jsonExtra } from "../utils/hono.mjs";
|
|
8
|
+
import { serialiseAsDict } from "../utils/serde.mjs";
|
|
9
|
+
const ThreadIdSchema = z.object({ thread_id: z.string() });
|
|
10
|
+
const EventsFilterSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
channels: z.array(z.string()),
|
|
13
|
+
namespaces: z.array(z.array(z.string())).optional(),
|
|
14
|
+
depth: z.number().int().nonnegative().optional(),
|
|
15
|
+
since: z.number().int().nonnegative().optional(),
|
|
16
|
+
})
|
|
17
|
+
.strict();
|
|
18
|
+
const ProtocolCommandSchema = z.object({
|
|
19
|
+
id: z.number().int().nonnegative(),
|
|
20
|
+
method: z.string(),
|
|
21
|
+
params: z.record(z.unknown()).optional(),
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Normalize browser/node websocket message payloads into UTF-8 text so the
|
|
25
|
+
* protocol layer only needs to handle JSON strings.
|
|
26
|
+
*/
|
|
27
|
+
const parseSocketPayload = async (event) => {
|
|
28
|
+
if (typeof event.data === "string")
|
|
29
|
+
return event.data;
|
|
30
|
+
if (event.data instanceof ArrayBuffer) {
|
|
31
|
+
return new TextDecoder().decode(new Uint8Array(event.data));
|
|
32
|
+
}
|
|
33
|
+
if (event.data instanceof Blob) {
|
|
34
|
+
const buffer = await event.data.arrayBuffer();
|
|
35
|
+
return new TextDecoder().decode(new Uint8Array(buffer));
|
|
36
|
+
}
|
|
37
|
+
return String(event.data);
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Register thread-centric protocol transport routes for LangGraph API.
|
|
41
|
+
*/
|
|
42
|
+
export default function createProtocolApi(upgradeWebSocket, ops) {
|
|
43
|
+
const api = new Hono();
|
|
44
|
+
const protocolService = new ProtocolService({
|
|
45
|
+
runs: ops.runs,
|
|
46
|
+
threads: ops.threads,
|
|
47
|
+
});
|
|
48
|
+
api.get("/threads/:thread_id/stream/events", zValidator("param", ThreadIdSchema), upgradeWebSocket((c) => {
|
|
49
|
+
const { thread_id } = c.req.valid("param");
|
|
50
|
+
const record = protocolService.ensureThread({
|
|
51
|
+
threadId: thread_id,
|
|
52
|
+
transport: "websocket",
|
|
53
|
+
auth: c.var.auth,
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
async onOpen(_event, ws) {
|
|
57
|
+
await protocolService.attachEventSink(thread_id, (event) => {
|
|
58
|
+
ws.send(serialiseAsDict(event));
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
async onMessage(event, ws) {
|
|
62
|
+
let payload;
|
|
63
|
+
try {
|
|
64
|
+
payload = JSON.parse(await parseSocketPayload(event));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
ws.send(serialiseAsDict({
|
|
68
|
+
type: "error",
|
|
69
|
+
id: null,
|
|
70
|
+
error: "invalid_argument",
|
|
71
|
+
message: "Protocol commands must be valid JSON.",
|
|
72
|
+
}));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (typeof payload !== "object" ||
|
|
76
|
+
payload == null ||
|
|
77
|
+
typeof payload.id !== "number" ||
|
|
78
|
+
typeof payload.method !== "string") {
|
|
79
|
+
ws.send(serialiseAsDict({
|
|
80
|
+
type: "error",
|
|
81
|
+
id: null,
|
|
82
|
+
error: "invalid_argument",
|
|
83
|
+
message: "Protocol commands must include an integer id and string method.",
|
|
84
|
+
}));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const response = await protocolService.handleCommand(record.threadId, payload);
|
|
88
|
+
// `null` means the session already wrote the response through
|
|
89
|
+
// the shared transport queue (see
|
|
90
|
+
// `ProtocolSession.handleSubscribeForResponse`). Sending again
|
|
91
|
+
// here would double-deliver the success and break ordering.
|
|
92
|
+
if (response != null) {
|
|
93
|
+
ws.send(serialiseAsDict(response));
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
onClose() {
|
|
97
|
+
void protocolService.closeThread(record.threadId);
|
|
98
|
+
},
|
|
99
|
+
onError() {
|
|
100
|
+
void protocolService.closeThread(record.threadId);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}));
|
|
104
|
+
api.post("/threads/:thread_id/commands", zValidator("param", ThreadIdSchema), zValidator("json", ProtocolCommandSchema), async (c) => {
|
|
105
|
+
const { thread_id } = c.req.valid("param");
|
|
106
|
+
protocolService.ensureThread({
|
|
107
|
+
threadId: thread_id,
|
|
108
|
+
transport: "sse-http",
|
|
109
|
+
auth: c.var.auth,
|
|
110
|
+
});
|
|
111
|
+
const payload = c.req.valid("json");
|
|
112
|
+
return jsonExtra(c, await protocolService.handleCommand(thread_id, payload));
|
|
113
|
+
});
|
|
114
|
+
api.post("/threads/:thread_id/stream/events", zValidator("param", ThreadIdSchema), zValidator("json", EventsFilterSchema), async (c) => {
|
|
115
|
+
const { thread_id } = c.req.valid("param");
|
|
116
|
+
protocolService.ensureThread({
|
|
117
|
+
threadId: thread_id,
|
|
118
|
+
transport: "sse-http",
|
|
119
|
+
auth: c.var.auth,
|
|
120
|
+
});
|
|
121
|
+
const body = c.req.valid("json");
|
|
122
|
+
const sinkId = uuid7();
|
|
123
|
+
const filter = {
|
|
124
|
+
channels: new Set(body.channels),
|
|
125
|
+
namespaces: body.namespaces,
|
|
126
|
+
depth: body.depth,
|
|
127
|
+
since: body.since,
|
|
128
|
+
};
|
|
129
|
+
return streamSSE(c, async (stream) => {
|
|
130
|
+
const delivered = new Set();
|
|
131
|
+
const writeSse = async (event) => {
|
|
132
|
+
if (event.event_id == null)
|
|
133
|
+
return;
|
|
134
|
+
if (delivered.has(event.event_id))
|
|
135
|
+
return;
|
|
136
|
+
delivered.add(event.event_id);
|
|
137
|
+
await stream.writeSSE({
|
|
138
|
+
id: event.event_id,
|
|
139
|
+
event: event.method,
|
|
140
|
+
data: serialiseAsDict(event),
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
await protocolService.attachFilteredEventSink(thread_id, {
|
|
144
|
+
id: sinkId,
|
|
145
|
+
filter,
|
|
146
|
+
send: writeSse,
|
|
147
|
+
});
|
|
148
|
+
stream.onAbort(() => {
|
|
149
|
+
protocolService.detachEventSink(thread_id, sinkId);
|
|
150
|
+
});
|
|
151
|
+
await new Promise((resolve) => {
|
|
152
|
+
stream.onAbort(() => resolve());
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
return api;
|
|
157
|
+
}
|