@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.
Files changed (3) hide show
  1. package/dist/index.d.ts +7 -139
  2. package/dist/index.js +18 -473
  3. package/package.json +10 -11
package/dist/index.d.ts CHANGED
@@ -1,45 +1,13 @@
1
- import { ProtocolRuntime, Logger, ProtocolDeps, ProtocolHandlers, ProtocolRequest, ProtocolResponse } from '@skein-js/agent-protocol';
2
- import { ModuleImporter } from '@skein-js/config';
3
- import { CorsOptions } from 'cors';
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 { MemoryStoreSnapshot } from '@skein-js/storage-memory';
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 = SkeinRouterCommonOptions & ({
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
- /** The checkpoint tuple `[checkpoint, metadata, parentId?]` with the blobs base64-encoded. */
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
- var skeinRoutes = [
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
- let deps;
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 ?? corsFromConfig ?? false
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/langgraph-import.ts
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
- isTerminalRunStatus
410
- } from "@skein-js/core";
411
- import { SuperJSON } from "superjson";
412
- import { z } from "zod";
413
- var OPS_FILE = ".langgraphjs_ops.json";
414
- var STORE_FILE = ".langgraphjs_api.store.json";
415
- var CHECKPOINTER_FILE = ".langgraphjs_api.checkpointer.json";
416
- var langgraphSuperjson = new SuperJSON();
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.4.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/mainawycliffe/skein-js/tree/main/packages/server-express#readme",
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/mainawycliffe/skein-js.git",
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/mainawycliffe/skein-js/issues"
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
- "superjson": "^2.2.6",
45
- "zod": "^3.25.76",
46
- "@skein-js/agent-protocol": "0.4.0",
47
- "@skein-js/config": "0.4.0",
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"