@skein-js/server-kit 0.11.1 → 0.11.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Shared, framework-agnostic building blocks for skein-js HTTP adapters.
4
4
 
5
- Part of **[skein-js](../../README.md)** — a TypeScript [Agent Protocol](https://github.com/langchain-ai/agent-protocol) server for [LangGraph.js](https://github.com/langchain-ai/langgraphjs), and a drop-in replacement for the LangGraph CLI.
5
+ Part of **[skein-js](../../README.md)** — the open-source alternative to LangGraph Platform for TypeScript: a self-hosted [Agent Protocol](https://github.com/langchain-ai/agent-protocol) server for [LangGraph.js](https://github.com/langchain-ai/langgraphjs), and a drop-in replacement for the LangGraph CLI.
6
6
 
7
7
  This package is the common ground the framework adapters ([`@skein-js/express`](../server-express),
8
8
  [`@skein-js/fastify`](../server-fastify), [`@skein-js/nestjs`](../server-nestjs),
@@ -34,6 +34,10 @@ handler table.
34
34
  / SSE) onto a Node `ServerResponse`, shared by the NestJS + Next.js Pages Router adapters.
35
35
  - **Mount prefix** — `stripBasePath`: strip the path an adapter is mounted under before matching the
36
36
  route table, for adapters that mount a catch-all and match by hand (NestJS, Next.js).
37
+ - **Logging** — `createConsoleLogger`: the opt-in `Logger` for adapters whose framework owns none
38
+ (Express, Next.js). `formatLogMeta` / `describeError` are the shared rendering every line-oriented
39
+ logger needs — a failed run's identity, stack, and `cause` chain — so the framework bridges and the
40
+ CLI's dev logger can't drift on how a failure reads.
37
41
 
38
42
  > The route table itself (`skeinRoutes`) is **not** here — it lives with the engine in
39
43
  > [`@skein-js/agent-protocol`](../agent-protocol), since it references the handler names. Adapters
@@ -73,11 +77,16 @@ Pass `overrides` to swap in production drivers or an auth engine while keeping t
73
77
  replaces any dep (e.g. a Postgres store, an `auth` engine). `createInMemoryDeps` is a
74
78
  **`@deprecated`** alias. `graphMapToResolver` / `normalizeEmbeddableGraphs` are the lower-level
75
79
  graph→`GraphResolver` helpers.
76
- - **`resolveProtocolRuntime(options): Promise<ResolvedProtocolRuntime>`** — turn a
80
+ - **`resolveProtocolRuntime(options, frameworkLogger?): Promise<ResolvedProtocolRuntime>`** — turn a
77
81
  `{ config } | { deps }` bag (`SkeinRuntimeOptions`) into a live runtime (assistants seeded, worker
78
82
  started) — the step every adapter runs before mounting routes. `options.worker`
79
83
  (`RunWorkerOptions`) tunes the background worker; `worker.maxConcurrency` is how many queued runs
80
- run at once.
84
+ run at once. `frameworkLogger` is the adapter's own default, applied only when the caller supplied
85
+ neither `options.logger` nor `deps.logger`; the result's `.logger` is the winner, which the adapter
86
+ should also use for its transport-fault logging.
87
+ - **`createConsoleLogger(options?): Logger`** — a plain `console.*` logger (`level`, `prefix`), for
88
+ adapters whose framework owns none. `formatLogMeta(meta)` / `describeError(thrown)` render a failed
89
+ run's identity, stack, and `cause` chain as text.
81
90
  - **`resolveRunConcurrency(explicit?, env?): number`** — the one precedence chain behind
82
91
  `worker.maxConcurrency`: explicit value → `SKEIN_RUN_CONCURRENCY` → `N_JOBS_PER_WORKER` →
83
92
  `DEFAULT_RUN_CONCURRENCY` (10, matching the LangGraph CLI). The environment is validated even when
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ProtocolRuntime, ProtocolDeps, Logger, RunWorkerOptions, GraphResolver, ProtocolResponse } from '@skein-js/agent-protocol';
2
- export { CompiledGraphFactory, DEFAULT_RUN_CONCURRENCY, DEFAULT_SHUTDOWN_GRACE_MS, GraphResolver, ProtocolDeps, ResolvedGraph, RunWorkerOptions } from '@skein-js/agent-protocol';
1
+ import { ProtocolRuntime, Logger, ProtocolDeps, RunWorkerOptions, RunFailureReport, GraphResolver, ProtocolResponse } from '@skein-js/agent-protocol';
2
+ export { CompiledGraphFactory, DEFAULT_RUN_CONCURRENCY, DEFAULT_SHUTDOWN_GRACE_MS, GraphResolver, Logger, ProtocolDeps, ResolvedGraph, RunWorkerOptions } from '@skein-js/agent-protocol';
3
3
  import { ModuleImporter, LanggraphJson, GraphSchemas } from '@skein-js/config';
4
4
  import { CorsOptions } from 'cors';
5
5
  export { CorsOptions } from 'cors';
@@ -9,7 +9,25 @@ import { SkeinStore } from '@skein-js/core';
9
9
  import { ServerResponse } from 'node:http';
10
10
 
11
11
  interface SkeinRuntimeCommonOptions {
12
- logger?: Logger;
12
+ /**
13
+ * Where skein reports itself: failed runs, webhook delivery failures, background-run summaries, and
14
+ * the adapter's own transport faults. It reaches **the run engine**, not just the transport — this
15
+ * is the one knob that decides whether a crashed graph is visible at all.
16
+ *
17
+ * Precedence, most explicit first:
18
+ *
19
+ * | You pass | Result |
20
+ * | ------------------------ | ----------------------------------------------------------------- |
21
+ * | a `Logger` | that logger, overriding any `deps.logger` |
22
+ * | `false` | the adapter contributes nothing and installs no default; an injected `deps.logger` still stands |
23
+ * | nothing | `deps.logger` if you injected one, else the adapter's framework default |
24
+ *
25
+ * The framework default exists only where the framework owns a logger the host has already
26
+ * configured: NestJS (`@nestjs/common`'s `Logger`, honoring `app.useLogger()`) and Fastify
27
+ * (`fastify.log`). Express and Next.js own no such thing, so they stay silent unless you pass one —
28
+ * `createConsoleLogger()` is the one-liner.
29
+ */
30
+ logger?: Logger | false;
13
31
  /**
14
32
  * Cross-origin access for browser clients (Agent Chat UI, React `useStream`). When omitted, CORS is
15
33
  * driven by the config's `http.cors` block (LangGraph-compatible) and is otherwise **off** — we do
@@ -60,12 +78,21 @@ interface ResolvedProtocolRuntime {
60
78
  runtime: ProtocolRuntime;
61
79
  /** CORS mapped from the config's `http.cors`, or `undefined` for the injected-`deps` path. */
62
80
  cors?: CorsOptions;
81
+ /**
82
+ * The logger the engine ended up with, after {@link SkeinRuntimeCommonOptions.logger}'s precedence
83
+ * chain. `undefined` means nothing logs. Adapters read this (rather than `options.logger`) for
84
+ * their own transport-fault logging, so the engine and the transport can never disagree about
85
+ * where output goes.
86
+ */
87
+ logger?: Logger;
63
88
  }
64
89
  /** Just the dependencies behind a `{ config } | { deps }` bag — no runtime, no worker. */
65
90
  interface ResolvedRuntimeDeps {
66
91
  deps: ProtocolDeps;
67
92
  /** CORS mapped from the config's `http.cors`, or `undefined` for the injected-`deps` path. */
68
93
  cors?: CorsOptions;
94
+ /** The resolved logger — see {@link ResolvedProtocolRuntime.logger}. */
95
+ logger?: Logger;
69
96
  }
70
97
  /**
71
98
  * Resolve the `{ config } | { deps }` seam down to a `ProtocolDeps` — injected as-is, or fresh
@@ -73,15 +100,65 @@ interface ResolvedRuntimeDeps {
73
100
  * {@link resolveProtocolRuntime} that stops short of building the engine, so the simplified invoke
74
101
  * surface (which needs only graphs + store) doesn't seed assistants or start a run worker it will
75
102
  * never use.
103
+ *
104
+ * `frameworkLogger` is the adapter's own default (Nest's `Logger`, `fastify.log`), used only when the
105
+ * caller supplied neither `options.logger` nor `deps.logger`.
76
106
  */
77
- declare function resolveRuntimeDeps(options: SkeinRuntimeOptions): Promise<ResolvedRuntimeDeps>;
107
+ declare function resolveRuntimeDeps(options: SkeinRuntimeOptions, frameworkLogger?: Logger): Promise<ResolvedRuntimeDeps>;
78
108
  /**
79
109
  * Build a `ProtocolRuntime` from adapter options: resolve `deps` (injected, or fresh in-memory
80
110
  * drivers from a `langgraph.json`), seed one assistant per declared graph, optionally warm the
81
111
  * graphs, and start the background run worker. Returns the runtime plus any CORS derived from the
82
112
  * config so the adapter can apply it. The caller owns shutdown (`runtime.worker.stop()`).
113
+ *
114
+ * `frameworkLogger` is the adapter's own default — see {@link resolveRuntimeDeps}.
115
+ */
116
+ declare function resolveProtocolRuntime(options: SkeinRuntimeOptions, frameworkLogger?: Logger): Promise<ResolvedProtocolRuntime>;
117
+
118
+ /** Levels in ascending severity — the order `level` filters against. */
119
+ declare const LEVELS: readonly ["debug", "info", "warn", "error"];
120
+ type ConsoleLogLevel = (typeof LEVELS)[number];
121
+ interface ConsoleLoggerOptions {
122
+ /**
123
+ * Quietest level to emit; anything below it is dropped. Defaults to `"info"`, which covers failed
124
+ * runs, webhook/rollback problems, and the one-line background-run summaries, while leaving
125
+ * `debug` off. `"warn"` suppresses the per-run summaries but never hides a failure.
126
+ */
127
+ level?: ConsoleLogLevel;
128
+ /** Prefix on every line, identifying the output as skein's. Defaults to `"skein"`. */
129
+ prefix?: string;
130
+ }
131
+ /**
132
+ * A plain console `Logger`. Pass it as an adapter's `logger` to see what skein reports — failed
133
+ * runs (with the stack and `cause` chain), webhook delivery failures, and background-run summaries:
134
+ *
135
+ * ```ts
136
+ * createExpressServer({ config: "./langgraph.json", logger: createConsoleLogger() });
137
+ * ```
138
+ */
139
+ declare function createConsoleLogger(options?: ConsoleLoggerOptions): Logger;
140
+
141
+ /**
142
+ * The `run=… thread=… assistant=… node=…` row identifying a failed run. Exported so the framework
143
+ * bridges, which place the stack in their own logger's dedicated slot rather than appending it to
144
+ * the message, still render identity identically — adding a field is then one edit, not three.
145
+ */
146
+ declare function runFailureIdentity(report: RunFailureReport): string;
147
+ /**
148
+ * Render structured log meta as plain text. Returns `""` when there is nothing to add, so callers can
149
+ * append unconditionally.
150
+ *
151
+ * A `RunFailureReport` renders as its identity row plus the error's stack and `cause` chain — the
152
+ * failure's *whole* story, since this is the one surface guaranteed to receive it (the wire gets only
153
+ * what is safe to hand a caller).
154
+ */
155
+ declare function formatLogMeta(meta: unknown): string;
156
+
157
+ /**
158
+ * An error's stack (falling back to `name: message`), its `details` when it carries any, and its
159
+ * `cause` chain appended. Depth-capped and cycle-safe; never throws.
83
160
  */
84
- declare function resolveProtocolRuntime(options: SkeinRuntimeOptions): Promise<ResolvedProtocolRuntime>;
161
+ declare function describeError(thrown: unknown): string;
85
162
 
86
163
  /**
87
164
  * The one precedence chain for background-run concurrency: an explicit value (a `worker.maxConcurrency`
@@ -292,4 +369,4 @@ declare function sendNodeError(error: unknown, res: ServerResponse, logger?: Log
292
369
  */
293
370
  declare function stripBasePath(pathname: string, basePath: string): string | null;
294
371
 
295
- export { type CorsSetting, type DevStateCounts, type DevStateSnapshot, type EmbeddableGraph, type InMemoryRuntimeConfig, type LanggraphCorsConfig, type ReloadableInMemoryRuntime, type ResolvedProtocolRuntime, type ResolvedRuntimeDeps, type SkeinRuntimeCommonOptions, type SkeinRuntimeOptions, allowedOrigin, applyNodeCors, corsFromHttpConfig, corsPreflightHeaders, corsResponseHeaders, createInMemoryDeps, describeSnapshot, embedInMemoryGraphs, graphMapToResolver, joinList, loadInMemoryRuntime, loadReloadableInMemoryRuntime, loadSnapshotIntoStore, normalizeEmbeddableGraphs, readLanggraphDevState, resolveProtocolRuntime, resolveRunConcurrency, resolveRuntimeDeps, resolveShutdownGraceMs, sendNodeError, sendNodePreflight, sendNodeResponse, stripBasePath, toCorsOptions };
372
+ export { type ConsoleLogLevel, type ConsoleLoggerOptions, type CorsSetting, type DevStateCounts, type DevStateSnapshot, type EmbeddableGraph, type InMemoryRuntimeConfig, type LanggraphCorsConfig, type ReloadableInMemoryRuntime, type ResolvedProtocolRuntime, type ResolvedRuntimeDeps, type SkeinRuntimeCommonOptions, type SkeinRuntimeOptions, allowedOrigin, applyNodeCors, corsFromHttpConfig, corsPreflightHeaders, corsResponseHeaders, createConsoleLogger, createInMemoryDeps, describeError, describeSnapshot, embedInMemoryGraphs, formatLogMeta, graphMapToResolver, joinList, loadInMemoryRuntime, loadReloadableInMemoryRuntime, loadSnapshotIntoStore, normalizeEmbeddableGraphs, readLanggraphDevState, resolveProtocolRuntime, resolveRunConcurrency, resolveRuntimeDeps, resolveShutdownGraceMs, runFailureIdentity, sendNodeError, sendNodePreflight, sendNodeResponse, stripBasePath, toCorsOptions };
package/dist/index.js CHANGED
@@ -242,13 +242,19 @@ function resolveShutdownGraceMs(explicit, env = process.env) {
242
242
  }
243
243
 
244
244
  // src/resolve-runtime.ts
245
- async function resolveRuntimeDeps(options) {
246
- if (options.deps) return { deps: options.deps };
247
- const loaded = await loadInMemoryRuntime(options.config, options.importModule);
248
- return { deps: loaded.deps, cors: loaded.cors };
249
- }
250
- async function resolveProtocolRuntime(options) {
251
- const { deps, cors: corsFromConfig } = await resolveRuntimeDeps(options);
245
+ function resolveLogger(option, injected, frameworkLogger) {
246
+ if (injected) return injected;
247
+ if (option === false) return void 0;
248
+ return option ?? frameworkLogger;
249
+ }
250
+ async function resolveRuntimeDeps(options, frameworkLogger) {
251
+ const loaded = options.deps ? { deps: options.deps, cors: void 0 } : await loadInMemoryRuntime(options.config, options.importModule);
252
+ const logger = resolveLogger(options.logger, loaded.deps.logger, frameworkLogger);
253
+ if (logger && !loaded.deps.logger) loaded.deps.logger = logger;
254
+ return { deps: loaded.deps, cors: loaded.cors, logger };
255
+ }
256
+ async function resolveProtocolRuntime(options, frameworkLogger) {
257
+ const { deps, cors: corsFromConfig, logger } = await resolveRuntimeDeps(options, frameworkLogger);
252
258
  const runtime = createProtocolRuntime(deps, {
253
259
  worker: {
254
260
  ...options.worker,
@@ -261,13 +267,126 @@ async function resolveProtocolRuntime(options) {
261
267
  await Promise.all(
262
268
  deps.graphs.ids.map(
263
269
  (graphId) => deps.graphs.load(graphId).catch((error) => {
264
- options.logger?.warn(`Failed to warm graph "${graphId}".`, error);
270
+ logger?.warn(`Failed to warm graph "${graphId}".`, error);
265
271
  })
266
272
  )
267
273
  );
268
274
  }
269
275
  runtime.worker.start();
270
- return { runtime, cors: corsFromConfig };
276
+ return { runtime, cors: corsFromConfig, logger };
277
+ }
278
+
279
+ // src/log-meta.ts
280
+ import { isRunFailureReport } from "@skein-js/agent-protocol";
281
+
282
+ // src/describe-error.ts
283
+ var MAX_CAUSE_DEPTH = 5;
284
+ function readProperty(source, key) {
285
+ try {
286
+ return source[key];
287
+ } catch {
288
+ return void 0;
289
+ }
290
+ }
291
+ function safeStringify(value) {
292
+ try {
293
+ return JSON.stringify(value);
294
+ } catch {
295
+ return void 0;
296
+ }
297
+ }
298
+ function describe(thrown, depth, seen) {
299
+ if (!(thrown instanceof Error)) {
300
+ return typeof thrown === "object" && thrown !== null ? safeStringify(thrown) ?? String(thrown) : String(thrown);
301
+ }
302
+ const stack = readProperty(thrown, "stack");
303
+ let described = typeof stack === "string" ? stack : `${thrown.name}: ${thrown.message}`;
304
+ const details = readProperty(thrown, "details");
305
+ if (details !== void 0) {
306
+ const rendered = safeStringify(details);
307
+ if (rendered !== void 0) described += `
308
+ details: ${rendered}`;
309
+ }
310
+ if (depth >= MAX_CAUSE_DEPTH || seen.has(thrown)) return described;
311
+ seen.add(thrown);
312
+ const cause = readProperty(thrown, "cause");
313
+ if (cause === void 0) return described;
314
+ return `${described}
315
+ caused by: ${describe(cause, depth + 1, seen)}`;
316
+ }
317
+ function describeError(thrown) {
318
+ return describe(thrown, 0, /* @__PURE__ */ new WeakSet());
319
+ }
320
+
321
+ // src/log-meta.ts
322
+ function runFailureIdentity(report) {
323
+ return [
324
+ `run=${report.runId}`,
325
+ `thread=${report.threadId}`,
326
+ `assistant=${report.assistantId}`,
327
+ // Omitted entirely when LangGraph named no node — no `unknown` placeholder, no guess.
328
+ ...report.failingNodes.length > 0 ? [`node=${report.failingNodes.join(",")}`] : []
329
+ ].join(" ");
330
+ }
331
+ function safeString(value) {
332
+ try {
333
+ return String(value);
334
+ } catch {
335
+ return Object.prototype.toString.call(value);
336
+ }
337
+ }
338
+ function safeStringify2(value) {
339
+ try {
340
+ return JSON.stringify(value) ?? safeString(value);
341
+ } catch {
342
+ return safeString(value);
343
+ }
344
+ }
345
+ function renderRecord(meta) {
346
+ let entries;
347
+ try {
348
+ entries = Object.entries(meta);
349
+ } catch {
350
+ return safeString(meta);
351
+ }
352
+ return entries.map(([key, value]) => {
353
+ const rendered = typeof value === "object" && value !== null ? safeStringify2(value) : safeString(value);
354
+ return `${key}=${rendered}`;
355
+ }).join(" ");
356
+ }
357
+ function formatLogMeta(meta) {
358
+ if (meta === void 0 || meta === null) return "";
359
+ if (isRunFailureReport(meta)) return `${runFailureIdentity(meta)}
360
+ ${describeError(meta.error)}`;
361
+ if (meta instanceof Error) return describeError(meta);
362
+ if (typeof meta === "object") return renderRecord(meta);
363
+ return safeString(meta);
364
+ }
365
+
366
+ // src/console-logger.ts
367
+ var LEVELS = ["debug", "info", "warn", "error"];
368
+ function createConsoleLogger(options = {}) {
369
+ const prefix = options.prefix ?? "skein";
370
+ const configured = LEVELS.indexOf(options.level ?? "info");
371
+ const threshold = configured === -1 ? LEVELS.indexOf("info") : configured;
372
+ function emit(level, write) {
373
+ const rank = LEVELS.indexOf(level);
374
+ if (rank < threshold) return () => {
375
+ };
376
+ return (message, meta) => {
377
+ const rendered = formatLogMeta(meta);
378
+ write(`${prefix} ${level}: ${message}${rendered ? `
379
+ ${rendered}` : ""}`);
380
+ };
381
+ }
382
+ return {
383
+ // Bound to `console` rather than passed by reference so a host that swaps `console.log` later
384
+ // (test harnesses do) is still honored at call time.
385
+ debug: emit("debug", (line) => console.debug(line)),
386
+ info: emit("info", (line) => console.log(line)),
387
+ warn: emit("warn", (line) => console.warn(line)),
388
+ error: emit("error", (line) => console.error(line))
389
+ };
271
390
  }
272
391
 
273
392
  // src/index.ts
@@ -720,9 +839,12 @@ export {
720
839
  corsFromHttpConfig,
721
840
  corsPreflightHeaders,
722
841
  corsResponseHeaders,
842
+ createConsoleLogger,
723
843
  createInMemoryDeps,
844
+ describeError,
724
845
  describeSnapshot,
725
846
  embedInMemoryGraphs,
847
+ formatLogMeta,
726
848
  graphMapToResolver,
727
849
  joinList,
728
850
  loadInMemoryRuntime,
@@ -734,6 +856,7 @@ export {
734
856
  resolveRunConcurrency,
735
857
  resolveRuntimeDeps,
736
858
  resolveShutdownGraceMs,
859
+ runFailureIdentity,
737
860
  sendNodeError,
738
861
  sendNodePreflight,
739
862
  sendNodeResponse,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skein-js/server-kit",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "description": "Shared, framework-agnostic building blocks for skein-js HTTP adapters — in-memory dev runtime, LangGraph dev-state import, and langgraph.json CORS mapping.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Maina Wycliffe <wmmaina7@gmail.com>",
@@ -44,10 +44,10 @@
44
44
  "cors": "^2.8.5",
45
45
  "superjson": "^2.2.6",
46
46
  "zod": "^3.25.76",
47
- "@skein-js/agent-protocol": "0.11.1",
48
- "@skein-js/config": "0.11.1",
49
- "@skein-js/core": "0.11.1",
50
- "@skein-js/storage-memory": "0.11.1"
47
+ "@skein-js/agent-protocol": "0.11.3",
48
+ "@skein-js/core": "0.11.3",
49
+ "@skein-js/storage-memory": "0.11.3",
50
+ "@skein-js/config": "0.11.3"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@langchain/langgraph": "^1.4.0"