@skein-js/fastify 0.8.0 → 0.9.0

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
@@ -46,6 +46,22 @@ await app.listen({ port: 3000 });
46
46
  // the Agent Protocol is now served under /agent/*
47
47
  ```
48
48
 
49
+ ## Graphs as plain endpoints (non-chat)
50
+
51
+ For workloads that aren't chat — a classifier, an extractor, a workflow another service calls — there
52
+ is a smaller surface: every graph mounted as `POST /invoke/:graph_id`, where the request body **is**
53
+ the graph input and the response **is** the final state. No threads, assistants, or runs.
54
+
55
+ ```ts
56
+ import { skeinInvokePlugin } from "@skein-js/fastify";
57
+
58
+ await app.register(skeinInvokePlugin, { prefix: "/agent", deps });
59
+ // → POST /agent/invoke/:graph_id
60
+ ```
61
+
62
+ Send `Accept: text/event-stream` to stream the steps instead. See
63
+ [docs/serving-a-single-graph.md](../../docs/serving-a-single-graph.md).
64
+
49
65
  ## Streaming
50
66
 
51
67
  SSE responses take over the raw Node response (`reply.hijack()` + `reply.raw`) and stream the
@@ -56,6 +72,8 @@ pre-serialized frames the engine produced, tearing the run's subscription down o
56
72
  - **`createFastifyServer(options): Promise<SkeinFastifyServer>`** — a standalone server;
57
73
  `SkeinFastifyServer` = `{ app, runtime, listen(port?, host?), close() }`. `listen` defaults to port
58
74
  `2024`, host `"localhost"`; `close()` stops the run worker then the HTTP server.
75
+ - **`skeinInvokePlugin`** — the simplified serving surface (`POST /invoke/:graph_id`, body-in /
76
+ final-state-out). Options add `invokePrefix` (default `/invoke`) and `streamMode`.
59
77
  - **`skeinPlugin`** — a Fastify plugin: `await app.register(skeinPlugin, { prefix, ...options })`
60
78
  mounts the protocol under `prefix`. Encapsulated, so skein's routes + CORS stay isolated from the
61
79
  host app. Options: `SkeinPluginOptions` (an alias of `SkeinRuntimeOptions`); `prefix` is Fastify's
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Server } from 'node:http';
2
- import { ProtocolRuntime, Logger, ProtocolHandlers, ProtocolRequest, ProtocolResponse } from '@skein-js/agent-protocol';
2
+ import { ProtocolRuntime, Logger, ProtocolHandlers, GraphInvokeOptions, ProtocolRequest, ProtocolResponse } from '@skein-js/agent-protocol';
3
3
  export { skeinRoutes } from '@skein-js/agent-protocol';
4
4
  import { SkeinRuntimeOptions, CorsOptions } from '@skein-js/server-kit';
5
5
  export { SkeinRuntimeOptions } from '@skein-js/server-kit';
@@ -33,8 +33,7 @@ interface HandlerRoutesOptions {
33
33
  /**
34
34
  * Bind the Agent Protocol route table to a handler table on a Fastify instance — the pure shim. It
35
35
  * assembles no runtime and knows no storage driver, so a caller with custom `ProtocolDeps` can mount
36
- * it over any `ProtocolHandlers`. Registers CORS first (when enabled) so preflight `OPTIONS` and the
37
- * `Access-Control-Allow-*` headers cover every route, including the SSE streams.
36
+ * it over any `ProtocolHandlers`.
38
37
  */
39
38
  declare function registerSkeinHandlers(fastify: FastifyInstance, handlers: ProtocolHandlers, options?: HandlerRoutesOptions): Promise<void>;
40
39
  /**
@@ -49,6 +48,25 @@ declare function registerSkeinHandlers(fastify: FastifyInstance, handlers: Proto
49
48
  */
50
49
  declare const skeinPlugin: FastifyPluginAsync<SkeinPluginOptions>;
51
50
 
51
+ type SkeinInvokePluginOptions = SkeinRuntimeOptions & GraphInvokeOptions & {
52
+ /**
53
+ * Path prefix *inside* this plugin, appended to any Fastify `prefix` it is registered under.
54
+ * Defaults to `/invoke`; pass `"/"` to serve `:graph_id` directly at the registration prefix.
55
+ */
56
+ invokePrefix?: string;
57
+ };
58
+ /**
59
+ * A Fastify plugin serving `POST <invokePrefix>/:graph_id`. Resolves deps from the shared
60
+ * `{ config } | { deps }` seam; seeds no assistants and starts no background run worker, since an
61
+ * invoke-only service needs neither.
62
+ *
63
+ * ```ts
64
+ * await app.register(skeinInvokePlugin, { prefix: "/api", deps });
65
+ * // → POST /api/invoke/:graph_id
66
+ * ```
67
+ */
68
+ declare const skeinInvokePlugin: FastifyPluginAsync<SkeinInvokePluginOptions>;
69
+
52
70
  /** Translate a Fastify `FastifyRequest` into the normalized `ProtocolRequest` the handler table reads. */
53
71
  declare function toProtocolRequest(req: FastifyRequest): ProtocolRequest;
54
72
 
@@ -58,4 +76,4 @@ declare function sendProtocolResponse(response: ProtocolResponse, reply: Fastify
58
76
  /** Serialize a caught error onto `reply`, using the protocol status when the error carries one. */
59
77
  declare function sendErrorResponse(error: unknown, reply: FastifyReply, logger?: Logger): void;
60
78
 
61
- export { type HandlerRoutesOptions, type SkeinFastifyServer, type SkeinPluginOptions, createFastifyServer, registerSkeinHandlers, sendErrorResponse, sendProtocolResponse, skeinPlugin, toProtocolRequest };
79
+ export { type HandlerRoutesOptions, type SkeinFastifyServer, type SkeinInvokePluginOptions, type SkeinPluginOptions, createFastifyServer, registerSkeinHandlers, sendErrorResponse, sendProtocolResponse, skeinInvokePlugin, skeinPlugin, toProtocolRequest };
package/dist/index.js CHANGED
@@ -106,7 +106,7 @@ var HTTP_METHOD_TO_FASTIFY = {
106
106
  patch: "PATCH",
107
107
  delete: "DELETE"
108
108
  };
109
- async function registerSkeinHandlers(fastify, handlers, options = {}) {
109
+ async function prepareSkeinContext(fastify, options = {}) {
110
110
  if (options.cors) {
111
111
  let fastifyCors;
112
112
  try {
@@ -121,7 +121,10 @@ async function registerSkeinHandlers(fastify, handlers, options = {}) {
121
121
  options.cors === true ? { origin: true } : options.cors
122
122
  );
123
123
  }
124
- fastify.removeContentTypeParser("application/json");
124
+ try {
125
+ fastify.removeContentTypeParser("application/json");
126
+ } catch {
127
+ }
125
128
  fastify.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
126
129
  const text = (typeof body === "string" ? body : body.toString()).trim();
127
130
  if (text === "") {
@@ -134,6 +137,9 @@ async function registerSkeinHandlers(fastify, handlers, options = {}) {
134
137
  done(error);
135
138
  }
136
139
  });
140
+ }
141
+ async function registerSkeinHandlers(fastify, handlers, options = {}) {
142
+ await prepareSkeinContext(fastify, options);
137
143
  for (const binding of skeinRoutes) {
138
144
  const invoke = handlers[binding.handler];
139
145
  fastify.route({
@@ -195,6 +201,39 @@ async function createFastifyServer(options) {
195
201
  };
196
202
  }
197
203
 
204
+ // src/skein-invoke-plugin.ts
205
+ import {
206
+ createGraphInvokeHandler,
207
+ graphInvokeRoutes
208
+ } from "@skein-js/agent-protocol";
209
+ import { resolveRuntimeDeps } from "@skein-js/server-kit";
210
+ var skeinInvokePlugin = async (fastify, options) => {
211
+ const { deps, cors } = await resolveRuntimeDeps(options);
212
+ const invoke = createGraphInvokeHandler(deps, { streamMode: options.streamMode });
213
+ await prepareSkeinContext(fastify, {
214
+ logger: options.logger,
215
+ // Explicit option wins; otherwise fall back to the config's `http.cors`, else off.
216
+ cors: options.cors ?? cors ?? false
217
+ });
218
+ for (const binding of graphInvokeRoutes(options.invokePrefix)) {
219
+ fastify.route({
220
+ method: HTTP_METHOD_TO_FASTIFY[binding.method],
221
+ url: binding.path,
222
+ handler: async (req, reply) => {
223
+ const disconnected = new AbortController();
224
+ reply.raw.once("close", () => disconnected.abort(new Error("client disconnected")));
225
+ try {
226
+ const request = { ...toProtocolRequest(req), signal: disconnected.signal };
227
+ await sendProtocolResponse(await invoke(request), reply);
228
+ } catch (error) {
229
+ sendErrorResponse(error, reply, options.logger);
230
+ }
231
+ return reply;
232
+ }
233
+ });
234
+ }
235
+ };
236
+
198
237
  // src/index.ts
199
238
  import { skeinRoutes as skeinRoutes2 } from "@skein-js/agent-protocol";
200
239
  export {
@@ -202,6 +241,7 @@ export {
202
241
  registerSkeinHandlers,
203
242
  sendErrorResponse,
204
243
  sendProtocolResponse,
244
+ skeinInvokePlugin,
205
245
  skeinPlugin,
206
246
  skeinRoutes2 as skeinRoutes,
207
247
  toProtocolRequest
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skein-js/fastify",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Fastify adapter for skein-js — mount the Agent Protocol as a Fastify plugin.",
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.8.0",
44
- "@skein-js/agent-protocol": "0.8.0",
45
- "@skein-js/server-kit": "0.8.0"
43
+ "@skein-js/agent-protocol": "0.9.0",
44
+ "@skein-js/server-kit": "0.9.0",
45
+ "@skein-js/core": "0.9.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@fastify/cors": ">=8.0.0",
@@ -59,7 +59,7 @@
59
59
  "@langchain/core": "^1.2.0",
60
60
  "@langchain/langgraph": "^1.4.0",
61
61
  "fastify": "^5.2.0",
62
- "@skein-js/storage-memory": "0.8.0"
62
+ "@skein-js/storage-memory": "0.9.0"
63
63
  },
64
64
  "publishConfig": {
65
65
  "access": "public"