@skein-js/nestjs 0.8.0 → 0.9.1
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 +96 -6
- package/dist/index.d.ts +40 -5
- package/dist/index.js +171 -26
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -31,10 +31,90 @@ export class AppModule {}
|
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
The Agent Protocol is now served (`/threads`, `/assistants`, `/runs`, `/store`, …) alongside your
|
|
34
|
-
routes.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
routes. Call `app.enableCors(...)` as usual if browser clients run on another origin. Enable shutdown
|
|
35
|
+
hooks (`app.enableShutdownHooks()`) so the background run worker drains on exit.
|
|
36
|
+
|
|
37
|
+
### Serving under a global prefix
|
|
38
|
+
|
|
39
|
+
If your app calls `app.setGlobalPrefix(...)`, the protocol follows it — there is nothing to configure
|
|
40
|
+
on the skein side. Two things to do:
|
|
41
|
+
|
|
42
|
+
**1. Set the prefix as you normally would**, and write your own controllers prefix-relative:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
@Controller("todos") // Nest serves this at /api/todos
|
|
46
|
+
class TodosController {}
|
|
47
|
+
|
|
48
|
+
const app = await NestFactory.create(AppModule);
|
|
49
|
+
app.setGlobalPrefix("api"); // the protocol moves to /api too
|
|
50
|
+
await app.listen(2024);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**2. Point your client at the prefixed root** — not the server root:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const client = new Client({ apiUrl: "http://localhost:2024/api" });
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
That's it. `/api/threads`, `/api/assistants`, `/api/runs`, `/api/store/items` all serve, and requests
|
|
60
|
+
that aren't skein's still fall through to your controllers.
|
|
61
|
+
|
|
62
|
+
#### Still getting 404s?
|
|
63
|
+
|
|
64
|
+
- **`Unsupported route path: "/api/*"` in your boot log** — expected and harmless. Nest logs it while
|
|
65
|
+
auto-converting the adapter's catch-all to NestJS 11 wildcard syntax; the conversion succeeds. It is
|
|
66
|
+
not the cause of a 404.
|
|
67
|
+
- **Requests to the server root 404** — correct once a prefix is set. `POST /threads` is not served
|
|
68
|
+
when the prefix is `api`; use `/api/threads`.
|
|
69
|
+
- **`GET /api` itself 404s** — expected, and not a sign the mount is wrong. Nest does not route the
|
|
70
|
+
bare prefix root to middleware ([nestjs/nest#14520](https://github.com/nestjs/nest/issues/14520)).
|
|
71
|
+
No protocol route lives there; probe `/api/assistants/search` instead.
|
|
72
|
+
- **`/info` 404s** — also correct. It isn't part of the Agent Protocol surface skein serves; the
|
|
73
|
+
endpoints are `/threads`, `/assistants`, `/runs` and `/store/items`. Note `GET /ok` is only mounted
|
|
74
|
+
by the standalone `createNestServer` — `SkeinModule` adds no health route to your app.
|
|
75
|
+
- **Everything 404s and you're on an older release** — the protocol ignored `setGlobalPrefix` before
|
|
76
|
+
this was fixed, so every path 404'd under a prefixed app. Upgrade, or drop the prefix and mount your
|
|
77
|
+
own controllers at `@Controller("api/…")` instead.
|
|
78
|
+
|
|
79
|
+
### No `langgraph.json`? Pass a graph you already have
|
|
80
|
+
|
|
81
|
+
`{ deps }` is the alternative to `{ config }`: bring a compiled graph straight from your code — no
|
|
82
|
+
config file, no CLI. [`embedInMemoryGraphs`](../server-kit) turns a graph map into the
|
|
83
|
+
`ProtocolDeps` the module needs:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { Module } from "@nestjs/common";
|
|
87
|
+
import { SkeinModule } from "@skein-js/nestjs";
|
|
88
|
+
import { embedInMemoryGraphs } from "@skein-js/server-kit";
|
|
89
|
+
|
|
90
|
+
import { agent } from "./graphs/agent-graph";
|
|
91
|
+
|
|
92
|
+
@Module({
|
|
93
|
+
imports: [SkeinModule.forRoot({ deps: embedInMemoryGraphs({ agent }) })],
|
|
94
|
+
controllers: [/* your own controllers */],
|
|
95
|
+
})
|
|
96
|
+
export class AppModule {}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Map keys become graph ids. For durable state, swap in `embedPostgresGraphs` (Postgres + Redis) from
|
|
100
|
+
[`@skein-js/runtime`](../runtime) — or, if you _do_ have a `langgraph.json` and just want production
|
|
101
|
+
drivers, its `buildRuntime`. Full walkthrough: [docs/embedding.md](../../docs/embedding.md).
|
|
102
|
+
|
|
103
|
+
## Graphs as plain endpoints (non-chat)
|
|
104
|
+
|
|
105
|
+
For workloads that aren't chat — a classifier, an extractor, a workflow another service calls — there
|
|
106
|
+
is a smaller surface: every graph mounted as `POST /invoke/:graph_id`, where the request body **is**
|
|
107
|
+
the graph input and the response **is** the final state. No threads, assistants, or runs.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { SkeinInvokeModule } from "@skein-js/nestjs";
|
|
111
|
+
|
|
112
|
+
@Module({ imports: [SkeinInvokeModule.forRoot({ deps })] })
|
|
113
|
+
export class AppModule {}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Send `Accept: text/event-stream` to stream the steps instead. See
|
|
117
|
+
[docs/serving-a-single-graph.md](../../docs/serving-a-single-graph.md).
|
|
38
118
|
|
|
39
119
|
## Standalone server
|
|
40
120
|
|
|
@@ -48,6 +128,9 @@ await server.listen(2024);
|
|
|
48
128
|
// on shutdown: await server.close(); // stops the run worker
|
|
49
129
|
```
|
|
50
130
|
|
|
131
|
+
The same `{ deps }` seam applies here — `createNestServer({ deps: embedInMemoryGraphs({ agent }) })`
|
|
132
|
+
serves a graph you hold in code, with no `langgraph.json` on disk.
|
|
133
|
+
|
|
51
134
|
## Streaming
|
|
52
135
|
|
|
53
136
|
SSE responses write directly to the raw Node response and stream the pre-serialized frames the engine
|
|
@@ -64,16 +147,23 @@ produced, tearing the run's subscription down on client disconnect.
|
|
|
64
147
|
- **`SKEIN_RUNTIME` / `SKEIN_LOGGER` / `SKEIN_CORS`** — DI tokens; inject `SKEIN_RUNTIME` to reach the
|
|
65
148
|
`ResolvedProtocolRuntime` from your own providers — its `.runtime` is the `ProtocolRuntime`
|
|
66
149
|
(assistants, handlers, worker), plus `.cors`.
|
|
150
|
+
- **`SkeinInvokeModule.forRoot(options): DynamicModule`** — the simplified serving surface:
|
|
151
|
+
`POST /invoke/:graph_id` per graph, body-in / final-state-out, for non-chat workloads. Options add
|
|
152
|
+
`prefix` (default `/invoke`) and `streamMode`. Also exports `SkeinInvokeMiddleware` and the
|
|
153
|
+
`SKEIN_INVOKE` token.
|
|
67
154
|
- **`SkeinRuntimeOptions`** — the shared seam every adapter accepts: common `{ logger?, cors?, warm? }`
|
|
68
155
|
**plus** either `{ config, importModule? }` (in-memory runtime from a `langgraph.json`) **or**
|
|
69
|
-
`{ deps }` (bring-your-own `ProtocolDeps
|
|
70
|
-
`
|
|
156
|
+
`{ deps }` (bring-your-own `ProtocolDeps`). Build `deps` in code with `embedInMemoryGraphs`
|
|
157
|
+
([`@skein-js/server-kit`](../server-kit)) or `embedPostgresGraphs` ([`@skein-js/runtime`](../runtime)),
|
|
158
|
+
or from a `langgraph.json` with that package's `buildRuntime`.
|
|
71
159
|
- Low-level mappers: `toProtocolRequest`, plus `sendNodeResponse` / `sendNodeError` (re-exported from
|
|
72
160
|
[`@skein-js/server-kit`](../server-kit)).
|
|
73
161
|
|
|
74
162
|
## Learn more
|
|
75
163
|
|
|
76
164
|
- [`@skein-js/express`](../server-express) — the reference adapter
|
|
165
|
+
- [Embedding a graph you already have](../../docs/embedding.md) — the `{ deps }` path, no `langgraph.json`
|
|
166
|
+
- [Serving a graph as a plain endpoint](../../docs/serving-a-single-graph.md) — the non-chat surface
|
|
77
167
|
- [Building your own adapter](../../docs/building-an-adapter.md) · [skein-js overview](../../docs/index.md)
|
|
78
168
|
|
|
79
169
|
## License
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { NestModule, DynamicModule, MiddlewareConsumer, NestMiddleware, INestApplication } from '@nestjs/common';
|
|
2
|
-
import { Logger, ProtocolRuntime, ProtocolRequest } from '@skein-js/agent-protocol';
|
|
2
|
+
import { Logger, GraphInvokeOptions, ProtocolHandler, RouteMatcher, GraphInvokeHandlerName, ProtocolRuntime, ProtocolRequest } from '@skein-js/agent-protocol';
|
|
3
3
|
import { SkeinRuntimeOptions, ResolvedProtocolRuntime, CorsSetting } from '@skein-js/server-kit';
|
|
4
|
-
export { SkeinRuntimeOptions, sendNodeError, sendNodeResponse } from '@skein-js/server-kit';
|
|
4
|
+
export { RunWorkerOptions, SkeinRuntimeOptions, sendNodeError, sendNodeResponse } from '@skein-js/server-kit';
|
|
5
5
|
import { IncomingMessage, ServerResponse, IncomingHttpHeaders } from 'node:http';
|
|
6
|
+
import { ApplicationConfig } from '@nestjs/core';
|
|
6
7
|
|
|
7
8
|
declare class SkeinModule implements NestModule {
|
|
8
9
|
/**
|
|
@@ -19,16 +20,18 @@ declare class SkeinModule implements NestModule {
|
|
|
19
20
|
configure(consumer: MiddlewareConsumer): void;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
/** The Node request the
|
|
23
|
+
/** The Node request the middlewares read (an Express request is structurally compatible). */
|
|
23
24
|
type NestRequest = IncomingMessage & {
|
|
24
25
|
originalUrl?: string;
|
|
25
26
|
body?: unknown;
|
|
26
27
|
};
|
|
28
|
+
|
|
27
29
|
declare class SkeinMiddleware implements NestMiddleware {
|
|
28
30
|
private readonly resolved;
|
|
29
31
|
private readonly logger;
|
|
30
32
|
private readonly optionCors;
|
|
31
|
-
|
|
33
|
+
private readonly appConfig?;
|
|
34
|
+
constructor(resolved: ResolvedProtocolRuntime, logger: Logger | null, optionCors: boolean | CorsSetting | null, appConfig?: ApplicationConfig | undefined);
|
|
32
35
|
use(req: NestRequest, res: ServerResponse, next: (error?: unknown) => void): Promise<void>;
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -38,6 +41,38 @@ declare const SKEIN_RUNTIME: unique symbol;
|
|
|
38
41
|
declare const SKEIN_LOGGER: unique symbol;
|
|
39
42
|
/** Provides the explicit `cors` option (or `null`) so the middleware can apply CORS to skein routes. */
|
|
40
43
|
declare const SKEIN_CORS: unique symbol;
|
|
44
|
+
/** Provides the resolved single-graph invoke surface (handler + matcher) to `SkeinInvokeMiddleware`. */
|
|
45
|
+
declare const SKEIN_INVOKE: unique symbol;
|
|
46
|
+
|
|
47
|
+
type SkeinInvokeModuleOptions = SkeinRuntimeOptions & GraphInvokeOptions & {
|
|
48
|
+
/** Path prefix for the endpoint; defaults to `/invoke` (→ `POST /invoke/:graph_id`). */
|
|
49
|
+
prefix?: string;
|
|
50
|
+
};
|
|
51
|
+
declare class SkeinInvokeModule implements NestModule {
|
|
52
|
+
/**
|
|
53
|
+
* Serve the invoke surface from a `langgraph.json` (in-memory drivers) or an injected
|
|
54
|
+
* `ProtocolDeps`. Deps are resolved once, lazily, when the module initializes — no assistants are
|
|
55
|
+
* seeded and no background run worker is started, so there is nothing to shut down.
|
|
56
|
+
*/
|
|
57
|
+
static forRoot(options: SkeinInvokeModuleOptions): DynamicModule;
|
|
58
|
+
configure(consumer: MiddlewareConsumer): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The resolved invoke surface the module provides: the handler plus its route matcher and CORS. */
|
|
62
|
+
interface ResolvedInvokeSurface {
|
|
63
|
+
handler: ProtocolHandler;
|
|
64
|
+
match: RouteMatcher<GraphInvokeHandlerName>;
|
|
65
|
+
/** CORS derived from the config's `http.cors`, when the `{ config }` path was used. */
|
|
66
|
+
cors?: CorsSetting;
|
|
67
|
+
}
|
|
68
|
+
declare class SkeinInvokeMiddleware implements NestMiddleware {
|
|
69
|
+
private readonly invoke;
|
|
70
|
+
private readonly logger;
|
|
71
|
+
private readonly optionCors;
|
|
72
|
+
private readonly appConfig?;
|
|
73
|
+
constructor(invoke: ResolvedInvokeSurface, logger: Logger | null, optionCors: boolean | CorsSetting | null, appConfig?: ApplicationConfig | undefined);
|
|
74
|
+
use(req: NestRequest, res: ServerResponse, next: (error?: unknown) => void): Promise<void>;
|
|
75
|
+
}
|
|
41
76
|
|
|
42
77
|
interface SkeinNestServer {
|
|
43
78
|
/** The Nest application, protocol mounted at `/`. */
|
|
@@ -64,4 +99,4 @@ interface NodeProtocolRequest {
|
|
|
64
99
|
/** Translate a Node request (+ parsed URL, matched params, and already-parsed body) into a `ProtocolRequest`. */
|
|
65
100
|
declare function toProtocolRequest(req: NodeProtocolRequest, url: URL, params: Record<string, string>, body: unknown): ProtocolRequest;
|
|
66
101
|
|
|
67
|
-
export { SKEIN_CORS, SKEIN_LOGGER, SKEIN_RUNTIME, SkeinMiddleware, SkeinModule, type SkeinNestServer, createNestServer, toProtocolRequest };
|
|
102
|
+
export { type ResolvedInvokeSurface, SKEIN_CORS, SKEIN_INVOKE, SKEIN_LOGGER, SKEIN_RUNTIME, SkeinInvokeMiddleware, SkeinInvokeModule, type SkeinInvokeModuleOptions, SkeinMiddleware, SkeinModule, type SkeinNestServer, createNestServer, toProtocolRequest };
|
package/dist/index.js
CHANGED
|
@@ -22,16 +22,35 @@ import {
|
|
|
22
22
|
} from "@skein-js/server-kit";
|
|
23
23
|
|
|
24
24
|
// src/skein.middleware.ts
|
|
25
|
-
import { Inject, Injectable } from "@nestjs/common";
|
|
25
|
+
import { Inject, Injectable, Optional } from "@nestjs/common";
|
|
26
|
+
import { ApplicationConfig } from "@nestjs/core";
|
|
26
27
|
import { copyThreadIdIntoBody, matchSkeinRoute } from "@skein-js/agent-protocol";
|
|
27
|
-
import { SkeinHttpError } from "@skein-js/core";
|
|
28
28
|
import {
|
|
29
29
|
applyNodeCors,
|
|
30
30
|
sendNodeError,
|
|
31
31
|
sendNodePreflight,
|
|
32
|
-
sendNodeResponse
|
|
32
|
+
sendNodeResponse,
|
|
33
|
+
stripBasePath
|
|
33
34
|
} from "@skein-js/server-kit";
|
|
34
35
|
|
|
36
|
+
// src/read-json-body.ts
|
|
37
|
+
import { SkeinHttpError } from "@skein-js/core";
|
|
38
|
+
async function readJsonBody(req) {
|
|
39
|
+
if (req.body !== void 0) return req.body;
|
|
40
|
+
const contentType = req.headers["content-type"];
|
|
41
|
+
const single = Array.isArray(contentType) ? contentType[0] : contentType;
|
|
42
|
+
if (!single || !single.includes("application/json")) return void 0;
|
|
43
|
+
const chunks = [];
|
|
44
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
45
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
46
|
+
if (!text) return {};
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(text);
|
|
49
|
+
} catch {
|
|
50
|
+
throw SkeinHttpError.badRequest("Request body is not valid JSON.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
// src/to-protocol-request.ts
|
|
36
55
|
function toSingleValueHeaders(headers) {
|
|
37
56
|
const flattened = {};
|
|
@@ -63,34 +82,23 @@ function toProtocolRequest(req, url, params, body) {
|
|
|
63
82
|
var SKEIN_RUNTIME = /* @__PURE__ */ Symbol("skein:runtime");
|
|
64
83
|
var SKEIN_LOGGER = /* @__PURE__ */ Symbol("skein:logger");
|
|
65
84
|
var SKEIN_CORS = /* @__PURE__ */ Symbol("skein:cors");
|
|
85
|
+
var SKEIN_INVOKE = /* @__PURE__ */ Symbol("skein:invoke");
|
|
66
86
|
|
|
67
87
|
// src/skein.middleware.ts
|
|
68
88
|
function firstHeader(value) {
|
|
69
89
|
return Array.isArray(value) ? value[0] : value;
|
|
70
90
|
}
|
|
71
|
-
async function readJsonBody(req) {
|
|
72
|
-
if (req.body !== void 0) return req.body;
|
|
73
|
-
const contentType = firstHeader(req.headers["content-type"]);
|
|
74
|
-
if (!contentType || !contentType.includes("application/json")) return void 0;
|
|
75
|
-
const chunks = [];
|
|
76
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
77
|
-
const text = Buffer.concat(chunks).toString("utf8");
|
|
78
|
-
if (!text) return {};
|
|
79
|
-
try {
|
|
80
|
-
return JSON.parse(text);
|
|
81
|
-
} catch {
|
|
82
|
-
throw SkeinHttpError.badRequest("Request body is not valid JSON.");
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
91
|
var SkeinMiddleware = class {
|
|
86
|
-
constructor(resolved, logger, optionCors) {
|
|
92
|
+
constructor(resolved, logger, optionCors, appConfig) {
|
|
87
93
|
this.resolved = resolved;
|
|
88
94
|
this.logger = logger;
|
|
89
95
|
this.optionCors = optionCors;
|
|
96
|
+
this.appConfig = appConfig;
|
|
90
97
|
}
|
|
91
98
|
resolved;
|
|
92
99
|
logger;
|
|
93
100
|
optionCors;
|
|
101
|
+
appConfig;
|
|
94
102
|
async use(req, res, next) {
|
|
95
103
|
const rawUrl = req.originalUrl ?? req.url ?? "/";
|
|
96
104
|
const host = req.headers.host ?? "localhost";
|
|
@@ -101,18 +109,23 @@ var SkeinMiddleware = class {
|
|
|
101
109
|
next();
|
|
102
110
|
return;
|
|
103
111
|
}
|
|
112
|
+
const skeinPathname = stripBasePath(url.pathname, this.appConfig?.getGlobalPrefix() ?? "");
|
|
113
|
+
if (skeinPathname === null) {
|
|
114
|
+
next();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
104
117
|
const cors = this.optionCors ?? this.resolved.cors;
|
|
105
118
|
const method = (req.method ?? "GET").toUpperCase();
|
|
106
119
|
if (method === "OPTIONS") {
|
|
107
120
|
const requestedMethod = firstHeader(req.headers["access-control-request-method"]);
|
|
108
|
-
if (cors && requestedMethod && matchSkeinRoute(requestedMethod,
|
|
121
|
+
if (cors && requestedMethod && matchSkeinRoute(requestedMethod, skeinPathname)) {
|
|
109
122
|
sendNodePreflight(req.headers, res, cors);
|
|
110
123
|
return;
|
|
111
124
|
}
|
|
112
125
|
next();
|
|
113
126
|
return;
|
|
114
127
|
}
|
|
115
|
-
const match = matchSkeinRoute(method,
|
|
128
|
+
const match = matchSkeinRoute(method, skeinPathname);
|
|
116
129
|
if (!match) {
|
|
117
130
|
next();
|
|
118
131
|
return;
|
|
@@ -135,7 +148,9 @@ SkeinMiddleware = __decorateClass([
|
|
|
135
148
|
Injectable(),
|
|
136
149
|
__decorateParam(0, Inject(SKEIN_RUNTIME)),
|
|
137
150
|
__decorateParam(1, Inject(SKEIN_LOGGER)),
|
|
138
|
-
__decorateParam(2, Inject(SKEIN_CORS))
|
|
151
|
+
__decorateParam(2, Inject(SKEIN_CORS)),
|
|
152
|
+
__decorateParam(3, Optional()),
|
|
153
|
+
__decorateParam(3, Inject(ApplicationConfig))
|
|
139
154
|
], SkeinMiddleware);
|
|
140
155
|
|
|
141
156
|
// src/skein.module.ts
|
|
@@ -192,8 +207,135 @@ SkeinModule = __decorateClass([
|
|
|
192
207
|
Module({})
|
|
193
208
|
], SkeinModule);
|
|
194
209
|
|
|
210
|
+
// src/skein-invoke.module.ts
|
|
211
|
+
import {
|
|
212
|
+
Module as Module2,
|
|
213
|
+
RequestMethod as RequestMethod2
|
|
214
|
+
} from "@nestjs/common";
|
|
215
|
+
import {
|
|
216
|
+
createGraphInvokeHandler,
|
|
217
|
+
createRouteMatcher,
|
|
218
|
+
graphInvokeRoutes
|
|
219
|
+
} from "@skein-js/agent-protocol";
|
|
220
|
+
import {
|
|
221
|
+
resolveRuntimeDeps
|
|
222
|
+
} from "@skein-js/server-kit";
|
|
223
|
+
|
|
224
|
+
// src/skein-invoke.middleware.ts
|
|
225
|
+
import { Inject as Inject3, Injectable as Injectable3, Optional as Optional2 } from "@nestjs/common";
|
|
226
|
+
import { ApplicationConfig as ApplicationConfig2 } from "@nestjs/core";
|
|
227
|
+
import {
|
|
228
|
+
applyNodeCors as applyNodeCors2,
|
|
229
|
+
sendNodeError as sendNodeError2,
|
|
230
|
+
sendNodePreflight as sendNodePreflight2,
|
|
231
|
+
sendNodeResponse as sendNodeResponse2,
|
|
232
|
+
stripBasePath as stripBasePath2
|
|
233
|
+
} from "@skein-js/server-kit";
|
|
234
|
+
var SkeinInvokeMiddleware = class {
|
|
235
|
+
constructor(invoke, logger, optionCors, appConfig) {
|
|
236
|
+
this.invoke = invoke;
|
|
237
|
+
this.logger = logger;
|
|
238
|
+
this.optionCors = optionCors;
|
|
239
|
+
this.appConfig = appConfig;
|
|
240
|
+
}
|
|
241
|
+
invoke;
|
|
242
|
+
logger;
|
|
243
|
+
optionCors;
|
|
244
|
+
appConfig;
|
|
245
|
+
async use(req, res, next) {
|
|
246
|
+
const rawUrl = req.originalUrl ?? req.url ?? "/";
|
|
247
|
+
const host = req.headers.host ?? "localhost";
|
|
248
|
+
let url;
|
|
249
|
+
try {
|
|
250
|
+
url = new URL(rawUrl, `http://${host}`);
|
|
251
|
+
} catch {
|
|
252
|
+
next();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const skeinPathname = stripBasePath2(url.pathname, this.appConfig?.getGlobalPrefix() ?? "");
|
|
256
|
+
if (skeinPathname === null) {
|
|
257
|
+
next();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const cors = this.optionCors ?? this.invoke.cors;
|
|
261
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
262
|
+
if (method === "OPTIONS") {
|
|
263
|
+
const requested = Array.isArray(req.headers["access-control-request-method"]) ? req.headers["access-control-request-method"][0] : req.headers["access-control-request-method"];
|
|
264
|
+
if (cors && requested && this.invoke.match(requested, skeinPathname)) {
|
|
265
|
+
sendNodePreflight2(req.headers, res, cors);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
next();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const match = this.invoke.match(method, skeinPathname);
|
|
272
|
+
if (!match) {
|
|
273
|
+
next();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (cors) applyNodeCors2(req.headers, res, cors);
|
|
277
|
+
const disconnected = new AbortController();
|
|
278
|
+
res.once("close", () => disconnected.abort(new Error("client disconnected")));
|
|
279
|
+
try {
|
|
280
|
+
const body = await readJsonBody(req);
|
|
281
|
+
const request = {
|
|
282
|
+
...toProtocolRequest(req, url, match.params, body),
|
|
283
|
+
signal: disconnected.signal
|
|
284
|
+
};
|
|
285
|
+
await sendNodeResponse2(await this.invoke.handler(request), res);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
sendNodeError2(error, res, this.logger ?? void 0, "skein NestJS invoke");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
SkeinInvokeMiddleware = __decorateClass([
|
|
292
|
+
Injectable3(),
|
|
293
|
+
__decorateParam(0, Inject3(SKEIN_INVOKE)),
|
|
294
|
+
__decorateParam(1, Inject3(SKEIN_LOGGER)),
|
|
295
|
+
__decorateParam(2, Inject3(SKEIN_CORS)),
|
|
296
|
+
__decorateParam(3, Optional2()),
|
|
297
|
+
__decorateParam(3, Inject3(ApplicationConfig2))
|
|
298
|
+
], SkeinInvokeMiddleware);
|
|
299
|
+
|
|
300
|
+
// src/skein-invoke.module.ts
|
|
301
|
+
var SkeinInvokeModule = class {
|
|
302
|
+
/**
|
|
303
|
+
* Serve the invoke surface from a `langgraph.json` (in-memory drivers) or an injected
|
|
304
|
+
* `ProtocolDeps`. Deps are resolved once, lazily, when the module initializes — no assistants are
|
|
305
|
+
* seeded and no background run worker is started, so there is nothing to shut down.
|
|
306
|
+
*/
|
|
307
|
+
static forRoot(options) {
|
|
308
|
+
return {
|
|
309
|
+
module: SkeinInvokeModule,
|
|
310
|
+
providers: [
|
|
311
|
+
{
|
|
312
|
+
provide: SKEIN_INVOKE,
|
|
313
|
+
useFactory: async () => {
|
|
314
|
+
const { deps, cors } = await resolveRuntimeDeps(options);
|
|
315
|
+
return {
|
|
316
|
+
handler: createGraphInvokeHandler(deps, { streamMode: options.streamMode }),
|
|
317
|
+
match: createRouteMatcher(graphInvokeRoutes(options.prefix)),
|
|
318
|
+
cors
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
{ provide: SKEIN_LOGGER, useValue: options.logger ?? null },
|
|
323
|
+
{ provide: SKEIN_CORS, useValue: options.cors ?? null },
|
|
324
|
+
SkeinInvokeMiddleware
|
|
325
|
+
],
|
|
326
|
+
exports: [SKEIN_INVOKE]
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
configure(consumer) {
|
|
330
|
+
consumer.apply(SkeinInvokeMiddleware).forRoutes({ path: "*", method: RequestMethod2.ALL });
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
SkeinInvokeModule = __decorateClass([
|
|
334
|
+
Module2({})
|
|
335
|
+
], SkeinInvokeModule);
|
|
336
|
+
|
|
195
337
|
// src/create-nest-server.ts
|
|
196
|
-
import { Controller, Get, Module as
|
|
338
|
+
import { Controller, Get, Module as Module3 } from "@nestjs/common";
|
|
197
339
|
import { NestFactory } from "@nestjs/core";
|
|
198
340
|
import { resolveProtocolRuntime as resolveProtocolRuntime2 } from "@skein-js/server-kit";
|
|
199
341
|
var SkeinHealthController = class {
|
|
@@ -212,7 +354,7 @@ async function createNestServer(options) {
|
|
|
212
354
|
let SkeinRootModule = class {
|
|
213
355
|
};
|
|
214
356
|
SkeinRootModule = __decorateClass([
|
|
215
|
-
|
|
357
|
+
Module3({
|
|
216
358
|
imports: [SkeinModule.forResolvedRuntime(resolved, options.logger, options.cors)],
|
|
217
359
|
controllers: [SkeinHealthController]
|
|
218
360
|
})
|
|
@@ -231,15 +373,18 @@ async function createNestServer(options) {
|
|
|
231
373
|
}
|
|
232
374
|
|
|
233
375
|
// src/index.ts
|
|
234
|
-
import { sendNodeResponse as
|
|
376
|
+
import { sendNodeResponse as sendNodeResponse3, sendNodeError as sendNodeError3 } from "@skein-js/server-kit";
|
|
235
377
|
export {
|
|
236
378
|
SKEIN_CORS,
|
|
379
|
+
SKEIN_INVOKE,
|
|
237
380
|
SKEIN_LOGGER,
|
|
238
381
|
SKEIN_RUNTIME,
|
|
382
|
+
SkeinInvokeMiddleware,
|
|
383
|
+
SkeinInvokeModule,
|
|
239
384
|
SkeinMiddleware,
|
|
240
385
|
SkeinModule,
|
|
241
386
|
createNestServer,
|
|
242
|
-
|
|
243
|
-
|
|
387
|
+
sendNodeError3 as sendNodeError,
|
|
388
|
+
sendNodeResponse3 as sendNodeResponse,
|
|
244
389
|
toProtocolRequest
|
|
245
390
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skein-js/nestjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "NestJS adapter for skein-js — serve the Agent Protocol from a Nest module.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Maina Wycliffe <wmmaina7@gmail.com>",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"node": ">=20"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@skein-js/core": "0.
|
|
44
|
-
"@skein-js/server-kit": "0.
|
|
45
|
-
"@skein-js/agent-protocol": "0.
|
|
43
|
+
"@skein-js/core": "0.9.1",
|
|
44
|
+
"@skein-js/server-kit": "0.9.1",
|
|
45
|
+
"@skein-js/agent-protocol": "0.9.1"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@langchain/langgraph": "^1.4.0",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@nestjs/platform-express": "^11.0.0",
|
|
59
59
|
"reflect-metadata": "^0.2.2",
|
|
60
60
|
"rxjs": "^7.8.1",
|
|
61
|
-
"@skein-js/storage-memory": "0.
|
|
61
|
+
"@skein-js/storage-memory": "0.9.1"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|