@skein-js/express 0.4.0 → 0.6.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/index.d.ts +7 -139
- package/dist/index.js +18 -473
- package/package.json +10 -11
package/dist/index.d.ts
CHANGED
|
@@ -1,45 +1,13 @@
|
|
|
1
|
-
import { ProtocolRuntime, Logger,
|
|
2
|
-
|
|
3
|
-
import {
|
|
1
|
+
import { ProtocolRuntime, Logger, ProtocolHandlers, ProtocolRequest, ProtocolResponse } from '@skein-js/agent-protocol';
|
|
2
|
+
export { skeinRoutes } from '@skein-js/agent-protocol';
|
|
3
|
+
import { SkeinRuntimeOptions } from '@skein-js/server-kit';
|
|
4
|
+
export { DevStateCounts, DevStateSnapshot, InMemoryRuntimeConfig, LanggraphCorsConfig, ReloadableInMemoryRuntime, corsFromHttpConfig, describeSnapshot, loadInMemoryRuntime, loadReloadableInMemoryRuntime, loadSnapshotIntoStore, readLanggraphDevState, toCorsOptions } from '@skein-js/server-kit';
|
|
4
5
|
import { Router, Express, Request, Response } from 'express';
|
|
5
6
|
import { Server } from 'node:http';
|
|
6
|
-
import {
|
|
7
|
-
import { BaseCheckpointSaver } from '@langchain/langgraph';
|
|
8
|
-
import { SkeinStore } from '@skein-js/core';
|
|
7
|
+
import { CorsOptions } from 'cors';
|
|
9
8
|
|
|
10
|
-
interface SkeinRouterCommonOptions {
|
|
11
|
-
logger?: Logger;
|
|
12
|
-
/**
|
|
13
|
-
* Cross-origin access for browser clients (Agent Chat UI, React `useStream`). When omitted, CORS
|
|
14
|
-
* is driven by the config's `http.cors` block (LangGraph-compatible) and is otherwise **off** — we
|
|
15
|
-
* do not default to LangGraph's permissive `origin: "*"`. Set this to override: `CorsOptions` to
|
|
16
|
-
* restrict origins for `skein up`, `true` for permissive dev (reflect the request origin), or
|
|
17
|
-
* `false` to force it off. An explicit value wins over `http.cors`.
|
|
18
|
-
*/
|
|
19
|
-
cors?: boolean | CorsOptions;
|
|
20
|
-
/**
|
|
21
|
-
* Eager-load every declared graph at boot instead of lazily on first request. `skein dev` sets
|
|
22
|
-
* this so (a) graph import errors surface at startup and (b) the graph source files enter the
|
|
23
|
-
* running process's module graph — which is what arms `tsx watch`'s hot reload (it only watches
|
|
24
|
-
* files that have actually been imported). Load failures are logged, not thrown, so one bad
|
|
25
|
-
* graph never takes the server down or breaks the watch loop.
|
|
26
|
-
*/
|
|
27
|
-
warm?: boolean;
|
|
28
|
-
}
|
|
29
9
|
/** Either point at a `langgraph.json` (in-memory runtime) or inject a ready `ProtocolDeps`. */
|
|
30
|
-
type SkeinRouterOptions =
|
|
31
|
-
config: string;
|
|
32
|
-
/**
|
|
33
|
-
* How graph modules are imported for the in-memory runtime. Defaults to a native
|
|
34
|
-
* dynamic `import()`; `skein dev` injects a vite-backed importer for TypeScript graphs.
|
|
35
|
-
*/
|
|
36
|
-
importModule?: ModuleImporter;
|
|
37
|
-
deps?: never;
|
|
38
|
-
} | {
|
|
39
|
-
deps: ProtocolDeps;
|
|
40
|
-
config?: never;
|
|
41
|
-
importModule?: never;
|
|
42
|
-
});
|
|
10
|
+
type SkeinRouterOptions = SkeinRuntimeOptions;
|
|
43
11
|
interface SkeinRouter {
|
|
44
12
|
/** Mount on an Express app: `app.use(router)`. */
|
|
45
13
|
router: Router;
|
|
@@ -65,21 +33,6 @@ interface SkeinExpressServer {
|
|
|
65
33
|
/** Build an Express server hosting the Agent Protocol, ready to `listen`. */
|
|
66
34
|
declare function createExpressServer(options: SkeinRouterOptions): Promise<SkeinExpressServer>;
|
|
67
35
|
|
|
68
|
-
type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
69
|
-
interface RouteBinding {
|
|
70
|
-
method: HttpMethod;
|
|
71
|
-
path: string;
|
|
72
|
-
handler: keyof ProtocolHandlers;
|
|
73
|
-
/**
|
|
74
|
-
* Fold the path `thread_id` into the request body before dispatch. The SDK addresses a
|
|
75
|
-
* thread-scoped run by its path (`POST /threads/{id}/runs/stream`) while carrying only
|
|
76
|
-
* `assistant_id` in the body, but the stateless run handlers read `thread_id` from the body — so a
|
|
77
|
-
* thread-scoped mount must copy it across, or the run would target a stray new thread.
|
|
78
|
-
*/
|
|
79
|
-
foldThreadIdIntoBody?: boolean;
|
|
80
|
-
}
|
|
81
|
-
/** The route table, ordered most-specific-first within each method so literals win over params. */
|
|
82
|
-
declare const skeinRoutes: readonly RouteBinding[];
|
|
83
36
|
interface HandlerRouterOptions {
|
|
84
37
|
/** Structured logger for unexpected (non-`SkeinHttpError`) faults. */
|
|
85
38
|
logger?: Logger;
|
|
@@ -106,89 +59,4 @@ declare function sendProtocolResponse(response: ProtocolResponse, res: Response)
|
|
|
106
59
|
/** Serialize a caught error onto `res`, using the protocol status when the error carries one. */
|
|
107
60
|
declare function sendErrorResponse(error: unknown, res: Response, logger?: Logger): void;
|
|
108
61
|
|
|
109
|
-
|
|
110
|
-
type SerializedCheckpointTuple = [string, string, string | undefined];
|
|
111
|
-
/** `MemorySaver.storage` (thread → namespace → checkpointId → tuple) with blobs base64-encoded. */
|
|
112
|
-
type SerializedCheckpointStorage = Record<string, Record<string, Record<string, SerializedCheckpointTuple>>>;
|
|
113
|
-
/** `MemorySaver.writes` (key → taskId+idx → `[taskId, channel, blob]`) with the blob base64-encoded. */
|
|
114
|
-
type SerializedCheckpointWrites = Record<string, Record<string, [string, string, string]>>;
|
|
115
|
-
interface CheckpointerSnapshot {
|
|
116
|
-
storage: SerializedCheckpointStorage;
|
|
117
|
-
writes: SerializedCheckpointWrites;
|
|
118
|
-
}
|
|
119
|
-
/** A JSON-serializable snapshot of the whole in-memory dev runtime. */
|
|
120
|
-
interface DevStateSnapshot {
|
|
121
|
-
version: 1;
|
|
122
|
-
store: MemoryStoreSnapshot;
|
|
123
|
-
checkpoints: CheckpointerSnapshot;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
interface InMemoryRuntimeConfig {
|
|
127
|
-
/** In-memory `ProtocolDeps` (store, queue, bus, checkpointer) around the config's graphs. */
|
|
128
|
-
deps: ProtocolDeps;
|
|
129
|
-
/** CORS mapped from the config's `http.cors`, or `undefined` when none is declared. */
|
|
130
|
-
cors?: CorsOptions;
|
|
131
|
-
}
|
|
132
|
-
/** Load `langgraph.json`, wiring fresh in-memory drivers and reading its `http.cors` for the adapter. */
|
|
133
|
-
declare function loadInMemoryRuntime(configPath: string, importModule?: ModuleImporter): Promise<InMemoryRuntimeConfig>;
|
|
134
|
-
interface ReloadableInMemoryRuntime extends InMemoryRuntimeConfig {
|
|
135
|
-
/**
|
|
136
|
-
* Re-read the config and swap in freshly imported graphs, keeping the same drivers. Because the
|
|
137
|
-
* run engine calls `graphs.load()` per run (it never caches the compiled graph itself), the next
|
|
138
|
-
* run picks up the new code while every thread, run, checkpoint, and store item survives. This is
|
|
139
|
-
* what lets `skein dev` hot-reload graph source without dropping in-memory state.
|
|
140
|
-
*/
|
|
141
|
-
reloadGraphs(): Promise<void>;
|
|
142
|
-
/** A JSON-serializable snapshot of all dev state (protocol store + checkpoints). */
|
|
143
|
-
snapshotState(): DevStateSnapshot;
|
|
144
|
-
/** Restore dev state from a {@link snapshotState} — call before the server starts serving. */
|
|
145
|
-
hydrateState(snapshot: DevStateSnapshot): void;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Like {@link loadInMemoryRuntime}, but the returned `deps.graphs` delegates to a swappable config
|
|
149
|
-
* registry so graphs can be reloaded in place, and it can snapshot/restore its dev state. `skein
|
|
150
|
-
* dev` pairs this with vite's watcher (clear vite's cache, then `reloadGraphs()` — no server
|
|
151
|
-
* restart, no lost state) and with on-disk JSON persistence across restarts.
|
|
152
|
-
*/
|
|
153
|
-
declare function loadReloadableInMemoryRuntime(configPath: string, importModule?: ModuleImporter): Promise<ReloadableInMemoryRuntime>;
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Read a LangGraph `.langgraph_api/` directory and reconstruct skein's `DevStateSnapshot`.
|
|
157
|
-
* Returns `null` when the directory holds none of the expected files (nothing to import).
|
|
158
|
-
*/
|
|
159
|
-
declare function readLanggraphDevState(langgraphApiDir: string): Promise<DevStateSnapshot | null>;
|
|
160
|
-
/** Row/checkpoint counts in a snapshot, for CLI + log summaries. */
|
|
161
|
-
interface DevStateCounts {
|
|
162
|
-
assistants: number;
|
|
163
|
-
threads: number;
|
|
164
|
-
runs: number;
|
|
165
|
-
items: number;
|
|
166
|
-
/** Threads that carry graph checkpoint history. */
|
|
167
|
-
checkpointedThreads: number;
|
|
168
|
-
}
|
|
169
|
-
declare function describeSnapshot(snapshot: DevStateSnapshot): DevStateCounts;
|
|
170
|
-
/**
|
|
171
|
-
* Load a `DevStateSnapshot` into a live store + checkpointer — the sink for importing into a real
|
|
172
|
-
* skein deployment (e.g. Postgres). Resource rows go through the driver's `restore` (ids +
|
|
173
|
-
* timestamps preserved, `ON CONFLICT DO NOTHING` so re-runs are safe); checkpoints — graph state +
|
|
174
|
-
* full history — are copied via the public checkpointer API. Throws if the store can't bulk-restore
|
|
175
|
-
* (both first-party drivers can); a custom driver must implement `restore` to be an import target.
|
|
176
|
-
*/
|
|
177
|
-
declare function loadSnapshotIntoStore(snapshot: DevStateSnapshot, store: SkeinStore, checkpointer: BaseCheckpointSaver): Promise<void>;
|
|
178
|
-
|
|
179
|
-
/** The `http.cors` block of a langgraph.json — LangGraph's field names (snake_case). */
|
|
180
|
-
interface LanggraphCorsConfig {
|
|
181
|
-
allow_origins?: string[];
|
|
182
|
-
allow_origin_regex?: string;
|
|
183
|
-
allow_methods?: string[];
|
|
184
|
-
allow_headers?: string[];
|
|
185
|
-
allow_credentials?: boolean;
|
|
186
|
-
expose_headers?: string[];
|
|
187
|
-
max_age?: number;
|
|
188
|
-
}
|
|
189
|
-
/** Translate a LangGraph `http.cors` config into `cors` middleware options. */
|
|
190
|
-
declare function toCorsOptions(config: LanggraphCorsConfig): CorsOptions;
|
|
191
|
-
/** Read `http.cors` from a langgraph.json `http` block, mapped to `CorsOptions`, or `undefined`. */
|
|
192
|
-
declare function corsFromHttpConfig(http: unknown): CorsOptions | undefined;
|
|
193
|
-
|
|
194
|
-
export { type DevStateCounts, type DevStateSnapshot, type HandlerRouterOptions, type InMemoryRuntimeConfig, type LanggraphCorsConfig, type ReloadableInMemoryRuntime, type SkeinExpressServer, type SkeinRouter, type SkeinRouterOptions, corsFromHttpConfig, createExpressServer, createHandlerRouter, describeSnapshot, loadInMemoryRuntime, loadReloadableInMemoryRuntime, loadSnapshotIntoStore, readLanggraphDevState, sendErrorResponse, sendProtocolResponse, skeinRouter, skeinRoutes, toCorsOptions, toProtocolRequest };
|
|
62
|
+
export { type HandlerRouterOptions, type SkeinExpressServer, type SkeinRouter, type SkeinRouterOptions, createExpressServer, createHandlerRouter, sendErrorResponse, sendProtocolResponse, skeinRouter, toProtocolRequest };
|
package/dist/index.js
CHANGED
|
@@ -1,158 +1,11 @@
|
|
|
1
1
|
// src/skein-router.ts
|
|
2
|
-
import {
|
|
3
|
-
createProtocolRuntime
|
|
4
|
-
} from "@skein-js/agent-protocol";
|
|
5
|
-
|
|
6
|
-
// src/in-memory-runtime.ts
|
|
7
|
-
import { MemorySaver } from "@langchain/langgraph";
|
|
8
|
-
import { loadAuthEngine, loadConfig } from "@skein-js/config";
|
|
9
|
-
import { MemoryRunEventBus, MemoryRunQueue, MemorySkeinStore } from "@skein-js/storage-memory";
|
|
10
|
-
|
|
11
|
-
// src/cors-config.ts
|
|
12
|
-
var ALWAYS_EXPOSED_HEADERS = ["content-location", "x-pagination-total"];
|
|
13
|
-
function toCorsOptions(config) {
|
|
14
|
-
const options = {};
|
|
15
|
-
const allowAll = config.allow_origins?.includes("*") ?? false;
|
|
16
|
-
if (config.allow_origin_regex !== void 0) {
|
|
17
|
-
const pattern = new RegExp(`^(?:${config.allow_origin_regex})$`);
|
|
18
|
-
const listed = allowAll ? void 0 : new Set(config.allow_origins);
|
|
19
|
-
options.origin = (origin, callback) => callback(
|
|
20
|
-
null,
|
|
21
|
-
origin !== void 0 && (allowAll || pattern.test(origin) || (listed?.has(origin) ?? false))
|
|
22
|
-
);
|
|
23
|
-
} else if (config.allow_origins !== void 0) {
|
|
24
|
-
options.origin = allowAll ? "*" : config.allow_origins;
|
|
25
|
-
}
|
|
26
|
-
if (config.allow_methods !== void 0) options.methods = config.allow_methods;
|
|
27
|
-
if (config.allow_headers !== void 0) options.allowedHeaders = config.allow_headers;
|
|
28
|
-
if (config.allow_credentials !== void 0) options.credentials = config.allow_credentials;
|
|
29
|
-
if (config.max_age !== void 0) options.maxAge = config.max_age;
|
|
30
|
-
const exposed = /* @__PURE__ */ new Set([...ALWAYS_EXPOSED_HEADERS, ...config.expose_headers ?? []]);
|
|
31
|
-
options.exposedHeaders = [...exposed];
|
|
32
|
-
return options;
|
|
33
|
-
}
|
|
34
|
-
function corsFromHttpConfig(http) {
|
|
35
|
-
if (typeof http !== "object" || http === null) return void 0;
|
|
36
|
-
const cors2 = http.cors;
|
|
37
|
-
if (typeof cors2 !== "object" || cors2 === null) return void 0;
|
|
38
|
-
return toCorsOptions(cors2);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// src/dev-persistence.ts
|
|
42
|
-
var toBase64 = (bytes) => Buffer.from(bytes).toString("base64");
|
|
43
|
-
var fromBase64 = (text) => new Uint8Array(Buffer.from(text, "base64"));
|
|
44
|
-
var mapValues = (record, fn) => Object.fromEntries(Object.entries(record).map(([key, value]) => [key, fn(value)]));
|
|
45
|
-
function snapshotCheckpointer(saver) {
|
|
46
|
-
return {
|
|
47
|
-
storage: mapValues(
|
|
48
|
-
saver.storage,
|
|
49
|
-
(namespaces) => mapValues(
|
|
50
|
-
namespaces,
|
|
51
|
-
(checkpoints) => mapValues(checkpoints, ([checkpoint, metadata, parentId]) => [
|
|
52
|
-
toBase64(checkpoint),
|
|
53
|
-
toBase64(metadata),
|
|
54
|
-
parentId
|
|
55
|
-
])
|
|
56
|
-
)
|
|
57
|
-
),
|
|
58
|
-
writes: mapValues(
|
|
59
|
-
saver.writes,
|
|
60
|
-
(taskWrites) => mapValues(taskWrites, ([taskId, channel, blob]) => [
|
|
61
|
-
taskId,
|
|
62
|
-
channel,
|
|
63
|
-
toBase64(blob)
|
|
64
|
-
])
|
|
65
|
-
)
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function hydrateCheckpointer(saver, snapshot) {
|
|
69
|
-
saver.storage = mapValues(
|
|
70
|
-
snapshot.storage,
|
|
71
|
-
(namespaces) => mapValues(
|
|
72
|
-
namespaces,
|
|
73
|
-
(checkpoints) => mapValues(
|
|
74
|
-
checkpoints,
|
|
75
|
-
([checkpoint, metadata, parentId]) => [
|
|
76
|
-
fromBase64(checkpoint),
|
|
77
|
-
fromBase64(metadata),
|
|
78
|
-
parentId
|
|
79
|
-
]
|
|
80
|
-
)
|
|
81
|
-
)
|
|
82
|
-
);
|
|
83
|
-
saver.writes = mapValues(
|
|
84
|
-
snapshot.writes,
|
|
85
|
-
(taskWrites) => mapValues(taskWrites, ([taskId, channel, blob]) => [
|
|
86
|
-
taskId,
|
|
87
|
-
channel,
|
|
88
|
-
fromBase64(blob)
|
|
89
|
-
])
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// src/in-memory-runtime.ts
|
|
94
|
-
function toGraphResolver(graphs) {
|
|
95
|
-
return {
|
|
96
|
-
ids: graphs.ids,
|
|
97
|
-
load: (graphId) => graphs.load(graphId),
|
|
98
|
-
schemas: async (graphId) => await graphs.schemas(graphId)
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
function buildInMemoryDeps(graphs) {
|
|
102
|
-
return {
|
|
103
|
-
store: new MemorySkeinStore(),
|
|
104
|
-
graphs,
|
|
105
|
-
queue: new MemoryRunQueue(),
|
|
106
|
-
bus: new MemoryRunEventBus(),
|
|
107
|
-
checkpointer: new MemorySaver()
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
async function loadInMemoryRuntime(configPath, importModule) {
|
|
111
|
-
const { graphs, config, configDir } = await loadConfig({ configPath, importModule });
|
|
112
|
-
const deps = buildInMemoryDeps(toGraphResolver(graphs));
|
|
113
|
-
deps.auth = await loadAuthEngine(config.auth, { configDir, importModule });
|
|
114
|
-
return {
|
|
115
|
-
deps,
|
|
116
|
-
cors: corsFromHttpConfig(config.http)
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
async function loadReloadableInMemoryRuntime(configPath, importModule) {
|
|
120
|
-
const first = await loadConfig({ configPath, importModule });
|
|
121
|
-
let current = first.graphs;
|
|
122
|
-
const graphs = {
|
|
123
|
-
ids: first.graphs.ids,
|
|
124
|
-
load: (graphId) => current.load(graphId),
|
|
125
|
-
schemas: async (graphId) => await current.schemas(graphId)
|
|
126
|
-
};
|
|
127
|
-
const store = new MemorySkeinStore();
|
|
128
|
-
const checkpointer = new MemorySaver();
|
|
129
|
-
const deps = {
|
|
130
|
-
store,
|
|
131
|
-
graphs,
|
|
132
|
-
queue: new MemoryRunQueue(),
|
|
133
|
-
bus: new MemoryRunEventBus(),
|
|
134
|
-
checkpointer,
|
|
135
|
-
auth: await loadAuthEngine(first.config.auth, { configDir: first.configDir, importModule })
|
|
136
|
-
};
|
|
137
|
-
return {
|
|
138
|
-
deps,
|
|
139
|
-
cors: corsFromHttpConfig(first.config.http),
|
|
140
|
-
reloadGraphs: async () => {
|
|
141
|
-
current = (await loadConfig({ configPath, importModule })).graphs;
|
|
142
|
-
},
|
|
143
|
-
snapshotState: () => ({
|
|
144
|
-
version: 1,
|
|
145
|
-
store: store.snapshot(),
|
|
146
|
-
checkpoints: snapshotCheckpointer(checkpointer)
|
|
147
|
-
}),
|
|
148
|
-
hydrateState: (snapshot) => {
|
|
149
|
-
store.hydrate(snapshot.store);
|
|
150
|
-
hydrateCheckpointer(checkpointer, snapshot.checkpoints);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
}
|
|
2
|
+
import { resolveProtocolRuntime } from "@skein-js/server-kit";
|
|
154
3
|
|
|
155
4
|
// src/routes.ts
|
|
5
|
+
import {
|
|
6
|
+
foldThreadId,
|
|
7
|
+
skeinRoutes
|
|
8
|
+
} from "@skein-js/agent-protocol";
|
|
156
9
|
import cors from "cors";
|
|
157
10
|
import express from "express";
|
|
158
11
|
|
|
@@ -245,59 +98,7 @@ function toProtocolRequest(req) {
|
|
|
245
98
|
}
|
|
246
99
|
|
|
247
100
|
// src/routes.ts
|
|
248
|
-
|
|
249
|
-
// assistants
|
|
250
|
-
{ method: "get", path: "/assistants/:assistant_id/schemas", handler: "getAssistantSchemas" },
|
|
251
|
-
{ method: "get", path: "/assistants/:assistant_id", handler: "getAssistant" },
|
|
252
|
-
{ method: "post", path: "/assistants/search", handler: "searchAssistants" },
|
|
253
|
-
// threads
|
|
254
|
-
{ method: "post", path: "/threads/search", handler: "listThreads" },
|
|
255
|
-
{ method: "post", path: "/threads", handler: "createThread" },
|
|
256
|
-
{ method: "post", path: "/threads/:thread_id/copy", handler: "copyThread" },
|
|
257
|
-
{ method: "get", path: "/threads/:thread_id", handler: "getThread" },
|
|
258
|
-
{ method: "patch", path: "/threads/:thread_id", handler: "patchThread" },
|
|
259
|
-
{ method: "delete", path: "/threads/:thread_id", handler: "deleteThread" },
|
|
260
|
-
{ method: "get", path: "/threads/:thread_id/state", handler: "getThreadState" },
|
|
261
|
-
{ method: "post", path: "/threads/:thread_id/history", handler: "getThreadHistory" },
|
|
262
|
-
// runs — the stateless handlers are reused on the thread-scoped path with the id folded in
|
|
263
|
-
{ method: "post", path: "/runs/wait", handler: "createWaitRun" },
|
|
264
|
-
{ method: "post", path: "/runs/stream", handler: "createStreamRun" },
|
|
265
|
-
{
|
|
266
|
-
method: "post",
|
|
267
|
-
path: "/threads/:thread_id/runs/wait",
|
|
268
|
-
handler: "createWaitRun",
|
|
269
|
-
foldThreadIdIntoBody: true
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
method: "post",
|
|
273
|
-
path: "/threads/:thread_id/runs/stream",
|
|
274
|
-
handler: "createStreamRun",
|
|
275
|
-
foldThreadIdIntoBody: true
|
|
276
|
-
},
|
|
277
|
-
{ method: "post", path: "/threads/:thread_id/runs", handler: "createBackgroundRun" },
|
|
278
|
-
{ method: "get", path: "/threads/:thread_id/runs", handler: "listThreadRuns" },
|
|
279
|
-
{ method: "post", path: "/threads/:thread_id/runs/:run_id/cancel", handler: "cancelRun" },
|
|
280
|
-
{ method: "get", path: "/threads/:thread_id/runs/:run_id/stream", handler: "joinRunStream" },
|
|
281
|
-
{ method: "get", path: "/threads/:thread_id/runs/:run_id", handler: "getRun" },
|
|
282
|
-
{ method: "delete", path: "/threads/:thread_id/runs/:run_id", handler: "deleteRun" },
|
|
283
|
-
{ method: "get", path: "/runs/:run_id/stream", handler: "joinRunStream" },
|
|
284
|
-
// thread streaming / commands
|
|
285
|
-
{ method: "post", path: "/threads/:thread_id/stream", handler: "postThreadStream" },
|
|
286
|
-
{ method: "get", path: "/threads/:thread_id/stream", handler: "getThreadStream" },
|
|
287
|
-
{ method: "post", path: "/threads/:thread_id/commands", handler: "postThreadCommands" },
|
|
288
|
-
// store
|
|
289
|
-
{ method: "put", path: "/store/items", handler: "putStoreItem" },
|
|
290
|
-
{ method: "get", path: "/store/items", handler: "getStoreItem" },
|
|
291
|
-
{ method: "delete", path: "/store/items", handler: "deleteStoreItem" },
|
|
292
|
-
{ method: "post", path: "/store/items/search", handler: "searchStoreItems" },
|
|
293
|
-
{ method: "post", path: "/store/namespaces", handler: "listStoreNamespaces" }
|
|
294
|
-
];
|
|
295
|
-
function foldThreadId(request) {
|
|
296
|
-
const threadId = request.params["thread_id"];
|
|
297
|
-
if (threadId === void 0) return request;
|
|
298
|
-
const base = typeof request.body === "object" && request.body !== null && !Array.isArray(request.body) ? request.body : {};
|
|
299
|
-
return { ...request, body: { ...base, thread_id: threadId } };
|
|
300
|
-
}
|
|
101
|
+
import { skeinRoutes as skeinRoutes2 } from "@skein-js/agent-protocol";
|
|
301
102
|
function createHandlerRouter(handlers, options = {}) {
|
|
302
103
|
const router = express.Router();
|
|
303
104
|
if (options.cors) router.use(cors(options.cors === true ? { origin: true } : options.cors));
|
|
@@ -321,31 +122,11 @@ function createHandlerRouter(handlers, options = {}) {
|
|
|
321
122
|
|
|
322
123
|
// src/skein-router.ts
|
|
323
124
|
async function skeinRouter(options) {
|
|
324
|
-
|
|
325
|
-
let corsFromConfig;
|
|
326
|
-
if (options.deps) {
|
|
327
|
-
deps = options.deps;
|
|
328
|
-
} else {
|
|
329
|
-
const loaded = await loadInMemoryRuntime(options.config, options.importModule);
|
|
330
|
-
deps = loaded.deps;
|
|
331
|
-
corsFromConfig = loaded.cors;
|
|
332
|
-
}
|
|
333
|
-
const runtime = createProtocolRuntime(deps);
|
|
334
|
-
await runtime.service.assistants.registerGraphAssistants();
|
|
335
|
-
if (options.warm) {
|
|
336
|
-
await Promise.all(
|
|
337
|
-
deps.graphs.ids.map(
|
|
338
|
-
(graphId) => deps.graphs.load(graphId).catch((error) => {
|
|
339
|
-
options.logger?.warn(`Failed to warm graph "${graphId}".`, error);
|
|
340
|
-
})
|
|
341
|
-
)
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
runtime.worker.start();
|
|
125
|
+
const { runtime, cors: cors2 } = await resolveProtocolRuntime(options);
|
|
345
126
|
const router = createHandlerRouter(runtime.handlers, {
|
|
346
127
|
logger: options.logger,
|
|
347
128
|
// Explicit option wins; otherwise fall back to the config's `http.cors`, else off.
|
|
348
|
-
cors: options.cors ??
|
|
129
|
+
cors: options.cors ?? cors2 ?? false
|
|
349
130
|
});
|
|
350
131
|
return { router, runtime };
|
|
351
132
|
}
|
|
@@ -399,252 +180,16 @@ async function createExpressServer(options) {
|
|
|
399
180
|
};
|
|
400
181
|
}
|
|
401
182
|
|
|
402
|
-
// src/
|
|
403
|
-
import { readFile } from "fs/promises";
|
|
404
|
-
import path from "path";
|
|
405
|
-
import {
|
|
406
|
-
MemorySaver as MemorySaver2
|
|
407
|
-
} from "@langchain/langgraph";
|
|
183
|
+
// src/index.ts
|
|
408
184
|
import {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
langgraphSuperjson.registerCustom(
|
|
418
|
-
{
|
|
419
|
-
isApplicable: (value) => value instanceof Uint8Array,
|
|
420
|
-
serialize: (value) => Buffer.from(value).toString("base64"),
|
|
421
|
-
deserialize: (value) => new Uint8Array(Buffer.from(value, "base64"))
|
|
422
|
-
},
|
|
423
|
-
"Uint8Array"
|
|
424
|
-
);
|
|
425
|
-
var timestamp = z.union([z.date(), z.string()]).optional();
|
|
426
|
-
var jsonObject = z.record(z.unknown());
|
|
427
|
-
var langgraphAssistantSchema = z.object({
|
|
428
|
-
assistant_id: z.string(),
|
|
429
|
-
graph_id: z.string(),
|
|
430
|
-
name: z.string().optional(),
|
|
431
|
-
description: z.string().nullish(),
|
|
432
|
-
config: jsonObject.optional(),
|
|
433
|
-
context: z.unknown().optional(),
|
|
434
|
-
metadata: jsonObject.optional(),
|
|
435
|
-
version: z.number().optional(),
|
|
436
|
-
created_at: timestamp,
|
|
437
|
-
updated_at: timestamp
|
|
438
|
-
}).passthrough();
|
|
439
|
-
var langgraphThreadSchema = z.object({
|
|
440
|
-
thread_id: z.string(),
|
|
441
|
-
status: z.string().optional(),
|
|
442
|
-
metadata: jsonObject.optional(),
|
|
443
|
-
values: jsonObject.optional(),
|
|
444
|
-
interrupts: jsonObject.optional(),
|
|
445
|
-
created_at: timestamp,
|
|
446
|
-
updated_at: timestamp
|
|
447
|
-
}).passthrough();
|
|
448
|
-
var langgraphRunSchema = z.object({
|
|
449
|
-
run_id: z.string(),
|
|
450
|
-
thread_id: z.string(),
|
|
451
|
-
assistant_id: z.string(),
|
|
452
|
-
status: z.string().optional(),
|
|
453
|
-
metadata: jsonObject.optional(),
|
|
454
|
-
multitask_strategy: z.string().nullish(),
|
|
455
|
-
kwargs: jsonObject.optional(),
|
|
456
|
-
created_at: timestamp,
|
|
457
|
-
updated_at: timestamp
|
|
458
|
-
}).passthrough();
|
|
459
|
-
var langgraphOpsSchema = z.object({
|
|
460
|
-
assistants: z.record(langgraphAssistantSchema).optional(),
|
|
461
|
-
threads: z.record(langgraphThreadSchema).optional(),
|
|
462
|
-
runs: z.record(langgraphRunSchema).optional()
|
|
463
|
-
}).passthrough();
|
|
464
|
-
var langgraphStoreItemSchema = z.object({
|
|
465
|
-
namespace: z.array(z.string()),
|
|
466
|
-
key: z.string(),
|
|
467
|
-
value: jsonObject,
|
|
468
|
-
createdAt: timestamp,
|
|
469
|
-
updatedAt: timestamp
|
|
470
|
-
}).passthrough();
|
|
471
|
-
var langgraphStoreFileSchema = z.object({ data: z.map(z.string(), z.map(z.string(), langgraphStoreItemSchema)).optional() }).passthrough();
|
|
472
|
-
var langgraphCheckpointerSchema = z.object({ storage: jsonObject, writes: jsonObject }).passthrough();
|
|
473
|
-
async function readSuperjsonFile(filepath) {
|
|
474
|
-
let text;
|
|
475
|
-
try {
|
|
476
|
-
text = await readFile(filepath, "utf8");
|
|
477
|
-
} catch {
|
|
478
|
-
return null;
|
|
479
|
-
}
|
|
480
|
-
try {
|
|
481
|
-
return langgraphSuperjson.parse(text) ?? null;
|
|
482
|
-
} catch (error) {
|
|
483
|
-
throw new Error(
|
|
484
|
-
`Could not parse LangGraph state file "${filepath}": ${error instanceof Error ? error.message : String(error)}`
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
function validateShape(schema, value, label) {
|
|
489
|
-
const result = schema.safeParse(value);
|
|
490
|
-
if (!result.success) {
|
|
491
|
-
const detail = result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
492
|
-
throw new Error(`Invalid LangGraph ${label}: ${detail}`);
|
|
493
|
-
}
|
|
494
|
-
return result.data;
|
|
495
|
-
}
|
|
496
|
-
function toIso(value, fallback) {
|
|
497
|
-
if (value instanceof Date) return value.toISOString();
|
|
498
|
-
if (typeof value === "string") return value;
|
|
499
|
-
return fallback;
|
|
500
|
-
}
|
|
501
|
-
function toAssistant(row, now) {
|
|
502
|
-
return {
|
|
503
|
-
assistant_id: row.assistant_id,
|
|
504
|
-
graph_id: row.graph_id,
|
|
505
|
-
name: row.name ?? row.graph_id,
|
|
506
|
-
description: row.description ?? void 0,
|
|
507
|
-
config: row.config ?? {},
|
|
508
|
-
context: row.context ?? {},
|
|
509
|
-
metadata: row.metadata ?? {},
|
|
510
|
-
version: row.version ?? 1,
|
|
511
|
-
created_at: toIso(row.created_at, now),
|
|
512
|
-
updated_at: toIso(row.updated_at, now)
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
function toThread(row, now) {
|
|
516
|
-
const createdAt = toIso(row.created_at, now);
|
|
517
|
-
const updatedAt = toIso(row.updated_at, createdAt);
|
|
518
|
-
return {
|
|
519
|
-
thread_id: row.thread_id,
|
|
520
|
-
status: row.status ?? "idle",
|
|
521
|
-
metadata: row.metadata ?? {},
|
|
522
|
-
values: row.values ?? {},
|
|
523
|
-
interrupts: row.interrupts ?? {},
|
|
524
|
-
created_at: createdAt,
|
|
525
|
-
updated_at: updatedAt,
|
|
526
|
-
state_updated_at: updatedAt
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
function toRun(row, now) {
|
|
530
|
-
const createdAt = toIso(row.created_at, now);
|
|
531
|
-
const status = row.status ?? "success";
|
|
532
|
-
return {
|
|
533
|
-
run_id: row.run_id,
|
|
534
|
-
thread_id: row.thread_id,
|
|
535
|
-
assistant_id: row.assistant_id,
|
|
536
|
-
status: isTerminalRunStatus(status) ? status : "error",
|
|
537
|
-
metadata: row.metadata ?? {},
|
|
538
|
-
multitask_strategy: row.multitask_strategy ?? null,
|
|
539
|
-
created_at: createdAt,
|
|
540
|
-
updated_at: toIso(row.updated_at, createdAt)
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
function toRunKwargs(kwargs) {
|
|
544
|
-
if (!kwargs) return {};
|
|
545
|
-
const { input, command, config, context, stream_mode, interrupt_before, interrupt_after } = kwargs;
|
|
546
|
-
return { input, command, config, context, stream_mode, interrupt_before, interrupt_after };
|
|
547
|
-
}
|
|
548
|
-
async function readLanggraphDevState(langgraphApiDir) {
|
|
549
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
550
|
-
const [opsRaw, storeRaw, checkpointerRaw] = await Promise.all([
|
|
551
|
-
readSuperjsonFile(path.join(langgraphApiDir, OPS_FILE)),
|
|
552
|
-
readSuperjsonFile(path.join(langgraphApiDir, STORE_FILE)),
|
|
553
|
-
readSuperjsonFile(path.join(langgraphApiDir, CHECKPOINTER_FILE))
|
|
554
|
-
]);
|
|
555
|
-
if (!opsRaw && !storeRaw && !checkpointerRaw) return null;
|
|
556
|
-
const ops = opsRaw ? validateShape(langgraphOpsSchema, opsRaw, OPS_FILE) : null;
|
|
557
|
-
const storeFile = storeRaw ? validateShape(langgraphStoreFileSchema, storeRaw, STORE_FILE) : null;
|
|
558
|
-
const checkpointerFile = checkpointerRaw ? validateShape(langgraphCheckpointerSchema, checkpointerRaw, CHECKPOINTER_FILE) : null;
|
|
559
|
-
const runEntries = Object.entries(ops?.runs ?? {});
|
|
560
|
-
const items = [];
|
|
561
|
-
if (storeFile?.data) {
|
|
562
|
-
for (const namespaceItems of storeFile.data.values()) {
|
|
563
|
-
for (const stored of namespaceItems.values()) {
|
|
564
|
-
const item = {
|
|
565
|
-
namespace: stored.namespace,
|
|
566
|
-
key: stored.key,
|
|
567
|
-
value: stored.value,
|
|
568
|
-
createdAt: toIso(stored.createdAt, now),
|
|
569
|
-
updatedAt: toIso(stored.updatedAt, now)
|
|
570
|
-
};
|
|
571
|
-
items.push([JSON.stringify([stored.namespace, stored.key]), item]);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
const store = {
|
|
576
|
-
assistants: Object.entries(ops?.assistants ?? {}).map(([id, row]) => [
|
|
577
|
-
id,
|
|
578
|
-
toAssistant(row, now)
|
|
579
|
-
]),
|
|
580
|
-
threads: Object.entries(ops?.threads ?? {}).map(([id, row]) => [id, toThread(row, now)]),
|
|
581
|
-
runs: runEntries.map(([id, row]) => [id, toRun(row, now)]),
|
|
582
|
-
runKwargs: runEntries.map(([id, row]) => [id, toRunKwargs(row.kwargs)]),
|
|
583
|
-
items
|
|
584
|
-
};
|
|
585
|
-
let checkpoints = { storage: {}, writes: {} };
|
|
586
|
-
if (checkpointerFile) {
|
|
587
|
-
const saver = new MemorySaver2();
|
|
588
|
-
saver.storage = checkpointerFile.storage;
|
|
589
|
-
saver.writes = checkpointerFile.writes;
|
|
590
|
-
checkpoints = snapshotCheckpointer(saver);
|
|
591
|
-
}
|
|
592
|
-
return { version: 1, store, checkpoints };
|
|
593
|
-
}
|
|
594
|
-
function describeSnapshot(snapshot) {
|
|
595
|
-
return {
|
|
596
|
-
assistants: snapshot.store.assistants.length,
|
|
597
|
-
threads: snapshot.store.threads.length,
|
|
598
|
-
runs: snapshot.store.runs.length,
|
|
599
|
-
items: snapshot.store.items.length,
|
|
600
|
-
checkpointedThreads: Object.keys(snapshot.checkpoints.storage).length
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
function supportsRestore(store) {
|
|
604
|
-
return typeof store.restore === "function";
|
|
605
|
-
}
|
|
606
|
-
async function copyCheckpoints(snapshot, target) {
|
|
607
|
-
const source = new MemorySaver2();
|
|
608
|
-
hydrateCheckpointer(source, snapshot);
|
|
609
|
-
const tuples = [];
|
|
610
|
-
for await (const tuple of source.list({})) tuples.push(tuple);
|
|
611
|
-
tuples.reverse();
|
|
612
|
-
for (const tuple of tuples) {
|
|
613
|
-
const configurable = tuple.config.configurable ?? {};
|
|
614
|
-
const putConfig = {
|
|
615
|
-
configurable: {
|
|
616
|
-
thread_id: configurable.thread_id,
|
|
617
|
-
checkpoint_ns: configurable.checkpoint_ns ?? "",
|
|
618
|
-
// `put` reads `checkpoint_id` as the PARENT pointer; the child's own id is `checkpoint.id`.
|
|
619
|
-
checkpoint_id: tuple.parentConfig?.configurable?.checkpoint_id
|
|
620
|
-
}
|
|
621
|
-
};
|
|
622
|
-
const stored = await target.put(
|
|
623
|
-
putConfig,
|
|
624
|
-
tuple.checkpoint,
|
|
625
|
-
tuple.metadata ?? {},
|
|
626
|
-
tuple.checkpoint.channel_versions ?? {}
|
|
627
|
-
);
|
|
628
|
-
const writesByTask = /* @__PURE__ */ new Map();
|
|
629
|
-
for (const [taskId, channel, value] of tuple.pendingWrites ?? []) {
|
|
630
|
-
const writes = writesByTask.get(taskId) ?? [];
|
|
631
|
-
writes.push([channel, value]);
|
|
632
|
-
writesByTask.set(taskId, writes);
|
|
633
|
-
}
|
|
634
|
-
for (const [taskId, writes] of writesByTask) {
|
|
635
|
-
await target.putWrites(stored, writes, taskId);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
async function loadSnapshotIntoStore(snapshot, store, checkpointer) {
|
|
640
|
-
if (!supportsRestore(store)) {
|
|
641
|
-
throw new Error(
|
|
642
|
-
"Target store does not support bulk import (no restore() method). Use the in-memory or Postgres driver, or implement restore() on your SkeinStore."
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
await store.restore(snapshot.store);
|
|
646
|
-
await copyCheckpoints(snapshot.checkpoints, checkpointer);
|
|
647
|
-
}
|
|
185
|
+
loadInMemoryRuntime,
|
|
186
|
+
loadReloadableInMemoryRuntime,
|
|
187
|
+
readLanggraphDevState,
|
|
188
|
+
loadSnapshotIntoStore,
|
|
189
|
+
describeSnapshot,
|
|
190
|
+
corsFromHttpConfig,
|
|
191
|
+
toCorsOptions
|
|
192
|
+
} from "@skein-js/server-kit";
|
|
648
193
|
export {
|
|
649
194
|
corsFromHttpConfig,
|
|
650
195
|
createExpressServer,
|
|
@@ -657,7 +202,7 @@ export {
|
|
|
657
202
|
sendErrorResponse,
|
|
658
203
|
sendProtocolResponse,
|
|
659
204
|
skeinRouter,
|
|
660
|
-
skeinRoutes,
|
|
205
|
+
skeinRoutes2 as skeinRoutes,
|
|
661
206
|
toCorsOptions,
|
|
662
207
|
toProtocolRequest
|
|
663
208
|
};
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skein-js/express",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Express adapter for skein-js — mount the Agent Protocol on an Express Router.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Maina Wycliffe <wmmaina7@gmail.com>",
|
|
7
|
-
"homepage": "https://github.com/
|
|
7
|
+
"homepage": "https://github.com/skein-js/skein-js/tree/main/packages/server-express#readme",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
10
|
+
"url": "git+https://github.com/skein-js/skein-js.git",
|
|
11
11
|
"directory": "packages/server-express"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/
|
|
14
|
+
"url": "https://github.com/skein-js/skein-js/issues"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"typescript",
|
|
@@ -41,12 +41,10 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"cors": "^2.8.5",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"@skein-js/
|
|
47
|
-
"@skein-js/
|
|
48
|
-
"@skein-js/storage-memory": "0.4.0",
|
|
49
|
-
"@skein-js/core": "0.4.0"
|
|
44
|
+
"@skein-js/agent-protocol": "0.6.3",
|
|
45
|
+
"@skein-js/config": "0.6.3",
|
|
46
|
+
"@skein-js/server-kit": "0.6.3",
|
|
47
|
+
"@skein-js/core": "0.6.3"
|
|
50
48
|
},
|
|
51
49
|
"peerDependencies": {
|
|
52
50
|
"@langchain/langgraph": "^1.4.0",
|
|
@@ -57,7 +55,8 @@
|
|
|
57
55
|
"@langchain/langgraph": "^1.4.0",
|
|
58
56
|
"@types/cors": "^2.8.17",
|
|
59
57
|
"@types/express": "^5.0.0",
|
|
60
|
-
"express": "^5.2.1"
|
|
58
|
+
"express": "^5.2.1",
|
|
59
|
+
"@skein-js/storage-memory": "0.6.3"
|
|
61
60
|
},
|
|
62
61
|
"publishConfig": {
|
|
63
62
|
"access": "public"
|