@skein-js/server-kit 0.14.1 → 0.16.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/dist/channels-runtime-OADE6ORT.js +137 -0
- package/dist/{chunk-XDS3YWFL.js → chunk-OZ477M7A.js} +75 -4
- package/dist/{dev-BfYYRfEB.d.ts → dev-Zz4VRv7T.d.ts} +2 -2
- package/dist/dev.d.ts +2 -2
- package/dist/dev.js +1 -3
- package/dist/{in-memory-runtime-BUUSE63C.js → in-memory-runtime-HE3LTSLD.js} +1 -1
- package/dist/index.d.ts +59 -9
- package/dist/index.js +40 -4
- package/package.json +17 -8
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/channels-runtime.ts
|
|
2
|
+
import {
|
|
3
|
+
createAuthScopedStore,
|
|
4
|
+
createContext,
|
|
5
|
+
createProtocolServiceFromContext
|
|
6
|
+
} from "@skein-js/agent-protocol";
|
|
7
|
+
import { SkeinConfigError } from "@skein-js/config";
|
|
8
|
+
import { SkeinHttpError } from "@skein-js/core";
|
|
9
|
+
async function resolveChannels(input) {
|
|
10
|
+
const names = Object.keys(input.channels);
|
|
11
|
+
if (names.length === 0) return void 0;
|
|
12
|
+
const channels = await importChannelsPackage(names);
|
|
13
|
+
const registry = channels.buildChannelRegistry({
|
|
14
|
+
channels: Object.fromEntries(
|
|
15
|
+
Object.entries(input.channels).map(([name, loaded]) => [
|
|
16
|
+
name,
|
|
17
|
+
{
|
|
18
|
+
module: loaded.module,
|
|
19
|
+
config: {
|
|
20
|
+
assistant: loaded.assistant,
|
|
21
|
+
...loaded.allowedAssistants ? { allowedAssistants: loaded.allowedAssistants } : {},
|
|
22
|
+
...loaded.publicUrl ? { publicUrl: loaded.publicUrl } : {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
])
|
|
26
|
+
),
|
|
27
|
+
graphIds: input.graphIds
|
|
28
|
+
});
|
|
29
|
+
const pipelineDeps = buildPipelineDeps(input.deps, input.service);
|
|
30
|
+
return {
|
|
31
|
+
routes: channels.channelRouteBindings(registry.names),
|
|
32
|
+
wrapDispatcher: (inner) => channels.wrapChannelDispatcher({
|
|
33
|
+
registry,
|
|
34
|
+
inner,
|
|
35
|
+
logger: input.deps.logger ?? console
|
|
36
|
+
}),
|
|
37
|
+
handlers: {
|
|
38
|
+
handleInboundEvent: async (req) => {
|
|
39
|
+
const name = req.url.split("?")[0].split("/channels/")[1] ?? "";
|
|
40
|
+
const registered = registry.get(name);
|
|
41
|
+
if (!registered) throw SkeinHttpError.notFound(`No channel named "${name}".`);
|
|
42
|
+
return channels.handleInboundEvent(
|
|
43
|
+
registered,
|
|
44
|
+
{
|
|
45
|
+
method: req.method,
|
|
46
|
+
url: req.url,
|
|
47
|
+
headers: req.headers,
|
|
48
|
+
...typeof req.body === "string" ? { text: req.body } : {}
|
|
49
|
+
},
|
|
50
|
+
pipelineDeps
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function buildPipelineDeps(deps, service) {
|
|
57
|
+
let requestFilters;
|
|
58
|
+
const scopedService = (authContext) => {
|
|
59
|
+
if (!authContext && !requestFilters) return service();
|
|
60
|
+
const base = createContext(deps);
|
|
61
|
+
return createProtocolServiceFromContext({
|
|
62
|
+
...base,
|
|
63
|
+
...authContext?.user ? { authUser: authContext.user } : {},
|
|
64
|
+
...authContext?.scopes ? { authScopes: authContext.scopes } : {},
|
|
65
|
+
deps: requestFilters && deps.auth ? {
|
|
66
|
+
...base.deps,
|
|
67
|
+
store: createAuthScopedStore(base.deps.store, deps.auth, requestFilters, "threads")
|
|
68
|
+
} : base.deps
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
...deps.store.idempotency ? { idempotency: deps.store.idempotency } : {},
|
|
73
|
+
clock: () => deps.clock ? deps.clock() : /* @__PURE__ */ new Date(),
|
|
74
|
+
logger: deps.logger ?? console,
|
|
75
|
+
// Straight off the run event bus — the same stream `runs.joinStream` serves, without the HTTP
|
|
76
|
+
// connection per conversation that reaching it from outside would cost.
|
|
77
|
+
runFrames: { subscribe: (runId) => deps.bus.subscribe(runId) },
|
|
78
|
+
authorize: async (principal, authz) => {
|
|
79
|
+
if (!deps.auth) return void 0;
|
|
80
|
+
const context = {
|
|
81
|
+
user: {
|
|
82
|
+
identity: principal.identity,
|
|
83
|
+
display_name: principal.identity,
|
|
84
|
+
is_authenticated: true,
|
|
85
|
+
permissions: principal.permissions ?? [],
|
|
86
|
+
...principal.metadata ?? {}
|
|
87
|
+
},
|
|
88
|
+
scopes: principal.permissions ?? []
|
|
89
|
+
};
|
|
90
|
+
const decision = await deps.auth.authorize({
|
|
91
|
+
resource: authz.resource,
|
|
92
|
+
action: authz.action,
|
|
93
|
+
value: {},
|
|
94
|
+
context
|
|
95
|
+
});
|
|
96
|
+
requestFilters = decision.filters;
|
|
97
|
+
return context;
|
|
98
|
+
},
|
|
99
|
+
ensureThread: async (threadId, metadata, authContext) => {
|
|
100
|
+
await scopedService(authContext).threads.create({
|
|
101
|
+
thread_id: threadId,
|
|
102
|
+
metadata,
|
|
103
|
+
ifExists: "do_nothing"
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
threadStatus: async (threadId) => {
|
|
107
|
+
const thread = await service().threads.get(threadId).catch(() => null);
|
|
108
|
+
return thread?.status ?? null;
|
|
109
|
+
},
|
|
110
|
+
createRun: async (input) => {
|
|
111
|
+
const run = await scopedService(input.authContext).runs.createBackground(input.threadId, {
|
|
112
|
+
assistant_id: input.assistantId,
|
|
113
|
+
...input.input !== void 0 ? { input: input.input } : {},
|
|
114
|
+
...input.command ? { command: input.command } : {},
|
|
115
|
+
...input.ifThreadStatus ? { if_thread_status: [...input.ifThreadStatus] } : {},
|
|
116
|
+
...input.multitaskStrategy ? { multitask_strategy: input.multitaskStrategy } : {},
|
|
117
|
+
...input.metadata ? { metadata: input.metadata } : {},
|
|
118
|
+
...input.webhook ? { webhook: input.webhook } : {},
|
|
119
|
+
stream_mode: [...input.streamMode]
|
|
120
|
+
});
|
|
121
|
+
return { runId: run.run_id };
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function importChannelsPackage(names) {
|
|
126
|
+
try {
|
|
127
|
+
return await import("@skein-js/channels");
|
|
128
|
+
} catch (cause) {
|
|
129
|
+
throw new SkeinConfigError(
|
|
130
|
+
`skein.channels configures ${names.join(", ")}, but @skein-js/channels is not installed. Add it as a dependency.`,
|
|
131
|
+
{ cause }
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
resolveChannels
|
|
137
|
+
};
|
|
@@ -7,8 +7,10 @@ import {
|
|
|
7
7
|
import { MemorySaver as MemorySaver2 } from "@langchain/langgraph";
|
|
8
8
|
import {
|
|
9
9
|
loadAuthEngine,
|
|
10
|
+
loadChannels,
|
|
10
11
|
loadConfig
|
|
11
12
|
} from "@skein-js/config";
|
|
13
|
+
import { langGraphResolver as langGraphResolver2 } from "@skein-js/langgraph";
|
|
12
14
|
import { MemoryRunEventBus as MemoryRunEventBus2, MemoryRunQueue as MemoryRunQueue2, MemorySkeinStore as MemorySkeinStore2 } from "@skein-js/storage-memory";
|
|
13
15
|
|
|
14
16
|
// src/cors-config.ts
|
|
@@ -99,6 +101,7 @@ function resolveIdempotency(raw) {
|
|
|
99
101
|
|
|
100
102
|
// src/in-memory-deps.ts
|
|
101
103
|
import { MemorySaver } from "@langchain/langgraph";
|
|
104
|
+
import { cloneLangGraphCheckpoint, langGraphResolver, SkeinBaseStore } from "@skein-js/langgraph";
|
|
102
105
|
import { MemoryRunEventBus, MemoryRunQueue, MemorySkeinStore } from "@skein-js/storage-memory";
|
|
103
106
|
|
|
104
107
|
// src/max-page-size.ts
|
|
@@ -146,13 +149,50 @@ function resolveMemoryBusLimits(explicit = {}, env = process.env) {
|
|
|
146
149
|
};
|
|
147
150
|
}
|
|
148
151
|
|
|
152
|
+
// src/webhooks-config.ts
|
|
153
|
+
var WEBHOOK_SECRET_ENV = "SKEIN_WEBHOOK_SECRET";
|
|
154
|
+
function resolveSecrets(raw, env) {
|
|
155
|
+
const fromEnv = (env[WEBHOOK_SECRET_ENV] ?? "").split(",").map((secret) => secret.trim()).filter((secret) => secret.length > 0);
|
|
156
|
+
if (fromEnv.length > 0) return fromEnv;
|
|
157
|
+
const configured = typeof raw.secret === "string" ? [raw.secret] : raw.secret ?? [];
|
|
158
|
+
return configured.map((secret) => secret.trim()).filter((secret) => secret.length > 0);
|
|
159
|
+
}
|
|
160
|
+
function resolveWebhooks(raw, options = {}) {
|
|
161
|
+
const env = options.env ?? process.env;
|
|
162
|
+
const secrets = resolveSecrets(raw ?? {}, env);
|
|
163
|
+
if (!raw && secrets.length === 0) return void 0;
|
|
164
|
+
const config = {};
|
|
165
|
+
if (secrets.length > 0) {
|
|
166
|
+
config.secrets = secrets;
|
|
167
|
+
if (!env[WEBHOOK_SECRET_ENV] && raw?.secret !== void 0) {
|
|
168
|
+
options.warn?.(
|
|
169
|
+
`skein: a webhook signing key is set in langgraph.json, which puts it in version control. Set ${WEBHOOK_SECRET_ENV} instead \u2014 it takes precedence over this value.`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!raw) return config;
|
|
174
|
+
const retries = {};
|
|
175
|
+
if (typeof raw.retries?.max_attempts === "number") retries.maxAttempts = raw.retries.max_attempts;
|
|
176
|
+
if (typeof raw.retries?.initial_delay_ms === "number") {
|
|
177
|
+
retries.initialDelayMs = raw.retries.initial_delay_ms;
|
|
178
|
+
}
|
|
179
|
+
if (Object.keys(retries).length > 0) config.retries = retries;
|
|
180
|
+
if (typeof raw.max_payload_bytes === "number") config.maxPayloadBytes = raw.max_payload_bytes;
|
|
181
|
+
if (typeof raw.retain_hours === "number") config.retainHours = raw.retain_hours;
|
|
182
|
+
if (Array.isArray(raw.allowed_hosts) && raw.allowed_hosts.length > 0) {
|
|
183
|
+
config.allowedHosts = [...raw.allowed_hosts];
|
|
184
|
+
}
|
|
185
|
+
if (typeof raw.require_https === "boolean") config.requireHttps = raw.require_https;
|
|
186
|
+
return Object.keys(config).length > 0 ? config : void 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
149
189
|
// src/in-memory-deps.ts
|
|
150
190
|
function isGraphResolver(graphs) {
|
|
151
191
|
const candidate = graphs;
|
|
152
192
|
return Array.isArray(candidate.ids) && typeof candidate.load === "function";
|
|
153
193
|
}
|
|
154
194
|
function normalizeEmbeddableGraphs(graphs) {
|
|
155
|
-
return isGraphResolver(graphs) ? graphs : graphMapToResolver(graphs);
|
|
195
|
+
return langGraphResolver(isGraphResolver(graphs) ? graphs : graphMapToResolver(graphs));
|
|
156
196
|
}
|
|
157
197
|
function graphMapToResolver(graphs) {
|
|
158
198
|
const ids = Object.keys(graphs);
|
|
@@ -172,6 +212,7 @@ function graphMapToResolver(graphs) {
|
|
|
172
212
|
};
|
|
173
213
|
}
|
|
174
214
|
function embedInMemoryGraphs(graphs, overrides = {}) {
|
|
215
|
+
const webhooks = resolveWebhooks(void 0);
|
|
175
216
|
return {
|
|
176
217
|
// `maxPageSize` resolved for the same reason as the bus limits below: an embedded host's
|
|
177
218
|
// `SKEIN_MAX_PAGE_SIZE` has to reach the driver that applies it.
|
|
@@ -182,6 +223,20 @@ function embedInMemoryGraphs(graphs, overrides = {}) {
|
|
|
182
223
|
// the driver set a long-lived single-process deployment actually runs on.
|
|
183
224
|
bus: new MemoryRunEventBus(resolveMemoryBusLimits()),
|
|
184
225
|
checkpointer: new MemorySaver(),
|
|
226
|
+
// Bridges long-term memory into a LangGraph `BaseStore` so nodes reach it via `getStore()`. A
|
|
227
|
+
// function on deps rather than an import inside the engine — see `ProtocolDeps.storeBridge`.
|
|
228
|
+
storeBridge: (store) => new SkeinBaseStore(store),
|
|
229
|
+
// Throwaway saver for `POST /invoke/:graph_id` — see `ProtocolDeps.ephemeralCheckpointer`.
|
|
230
|
+
ephemeralCheckpointer: () => new MemorySaver(),
|
|
231
|
+
// Clones a checkpoint before it is re-put under another thread id — see `ProtocolDeps.cloneCheckpoint`.
|
|
232
|
+
cloneCheckpoint: cloneLangGraphCheckpoint,
|
|
233
|
+
// Resolved for the same reason as `maxPageSize` and the bus limits: an embedded host's env has to
|
|
234
|
+
// reach the code that applies it. Without this, a host that sets `SKEIN_WEBHOOK_SECRET` and
|
|
235
|
+
// follows the docs gets callbacks that are silently **unsigned** — a fail-open indistinguishable
|
|
236
|
+
// from a working setup, since an unsigned callback is delivered exactly like a signed one and only
|
|
237
|
+
// the receiver ever finds out. There is no `langgraph.json` on this path, so the env var is the
|
|
238
|
+
// only input; `overrides` still spreads last and wins.
|
|
239
|
+
...webhooks ? { webhooks } : {},
|
|
185
240
|
...overrides
|
|
186
241
|
};
|
|
187
242
|
}
|
|
@@ -222,10 +277,14 @@ async function loadInMemoryRuntime(configPath, importModule, staticSchemas) {
|
|
|
222
277
|
importModule,
|
|
223
278
|
staticSchemas
|
|
224
279
|
});
|
|
225
|
-
const deps = embedInMemoryGraphs(toGraphResolver(graphs));
|
|
280
|
+
const deps = embedInMemoryGraphs(langGraphResolver2(toGraphResolver(graphs)));
|
|
226
281
|
deps.auth = await loadAuthEngine(config.auth, { configDir, importModule });
|
|
227
282
|
return {
|
|
228
283
|
deps,
|
|
284
|
+
channels: await loadChannels(config.skein?.channels, {
|
|
285
|
+
configDir,
|
|
286
|
+
...importModule ? { importModule } : {}
|
|
287
|
+
}),
|
|
229
288
|
cors: corsFromHttpConfig(config.http),
|
|
230
289
|
routes: routesFromHttpConfig(config.http)
|
|
231
290
|
};
|
|
@@ -233,14 +292,17 @@ async function loadInMemoryRuntime(configPath, importModule, staticSchemas) {
|
|
|
233
292
|
async function loadReloadableInMemoryRuntime(configPath, importModule, staticSchemas, extraDeps) {
|
|
234
293
|
const first = await loadConfig({ configPath, importModule, staticSchemas });
|
|
235
294
|
let current = first.graphs;
|
|
236
|
-
const graphs = {
|
|
295
|
+
const graphs = langGraphResolver2({
|
|
237
296
|
ids: first.graphs.ids,
|
|
238
297
|
load: (graphId) => current.load(graphId),
|
|
239
298
|
schemas: async (graphId) => await current.schemas(graphId)
|
|
240
|
-
};
|
|
299
|
+
});
|
|
241
300
|
const threadTtl = resolveThreadTtl(first.config.checkpointer?.ttl);
|
|
242
301
|
const storeTtl = resolveStoreTtl(first.config.store?.ttl);
|
|
243
302
|
const idempotency = resolveIdempotency(first.config.skein?.idempotency);
|
|
303
|
+
const webhooks = resolveWebhooks(first.config.skein?.webhooks, {
|
|
304
|
+
warn: (message) => console.warn(message)
|
|
305
|
+
});
|
|
244
306
|
const store = new MemorySkeinStore2({
|
|
245
307
|
maxPageSize: resolveMaxPageSize(),
|
|
246
308
|
...threadTtl ? { threadTtl } : {},
|
|
@@ -260,10 +322,18 @@ async function loadReloadableInMemoryRuntime(configPath, importModule, staticSch
|
|
|
260
322
|
...threadTtl ? { threadTtl } : {},
|
|
261
323
|
// Unlike `threadTtl`, absence here means *defaults*, not *off*: the header is honoured either way.
|
|
262
324
|
...idempotency ? { idempotency } : {},
|
|
325
|
+
...webhooks ? { webhooks } : {},
|
|
263
326
|
...extraDeps
|
|
264
327
|
};
|
|
265
328
|
return {
|
|
266
329
|
deps,
|
|
330
|
+
// Loaded here so the modules are imported once, with the same importer the graphs used — under
|
|
331
|
+
// `skein dev` that is the vite-backed one, which is what lets a channel be a TypeScript file in
|
|
332
|
+
// the user's own repo rather than something they have to build first.
|
|
333
|
+
channels: await loadChannels(first.config.skein?.channels, {
|
|
334
|
+
configDir: first.configDir,
|
|
335
|
+
...importModule ? { importModule } : {}
|
|
336
|
+
}),
|
|
267
337
|
cors: corsFromHttpConfig(first.config.http),
|
|
268
338
|
routes: routesFromHttpConfig(first.config.http),
|
|
269
339
|
config: first.config,
|
|
@@ -294,6 +364,7 @@ export {
|
|
|
294
364
|
resolveIdempotency,
|
|
295
365
|
resolveMaxPageSize,
|
|
296
366
|
resolveMemoryBusLimits,
|
|
367
|
+
resolveWebhooks,
|
|
297
368
|
normalizeEmbeddableGraphs,
|
|
298
369
|
graphMapToResolver,
|
|
299
370
|
embedInMemoryGraphs,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ThreadCheckpointer } from '@skein-js/agent-protocol';
|
|
2
2
|
import { SkeinStore } from '@skein-js/core';
|
|
3
3
|
import { MemoryStoreSnapshot } from '@skein-js/storage-memory';
|
|
4
4
|
|
|
@@ -41,6 +41,6 @@ declare function describeSnapshot(snapshot: DevStateSnapshot): DevStateCounts;
|
|
|
41
41
|
* full history — are copied via the public checkpointer API. Throws if the store can't bulk-restore
|
|
42
42
|
* (both first-party drivers can); a custom driver must implement `restore` to be an import target.
|
|
43
43
|
*/
|
|
44
|
-
declare function loadSnapshotIntoStore(snapshot: DevStateSnapshot, store: SkeinStore, checkpointer:
|
|
44
|
+
declare function loadSnapshotIntoStore(snapshot: DevStateSnapshot, store: SkeinStore, checkpointer: ThreadCheckpointer): Promise<void>;
|
|
45
45
|
|
|
46
46
|
export { type DevStateSnapshot as D, type DevStateCounts as a, describeSnapshot as d, loadSnapshotIntoStore as l, readLanggraphDevState as r };
|
package/dist/dev.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { a as DevStateCounts, d as describeSnapshot, l as loadSnapshotIntoStore, r as readLanggraphDevState } from './dev-
|
|
2
|
-
import '@
|
|
1
|
+
export { a as DevStateCounts, d as describeSnapshot, l as loadSnapshotIntoStore, r as readLanggraphDevState } from './dev-Zz4VRv7T.js';
|
|
2
|
+
import '@skein-js/agent-protocol';
|
|
3
3
|
import '@skein-js/core';
|
|
4
4
|
import '@skein-js/storage-memory';
|
package/dist/dev.js
CHANGED
|
@@ -6,9 +6,7 @@ import {
|
|
|
6
6
|
// src/langgraph-import.ts
|
|
7
7
|
import { readFile } from "fs/promises";
|
|
8
8
|
import path from "path";
|
|
9
|
-
import {
|
|
10
|
-
MemorySaver
|
|
11
|
-
} from "@langchain/langgraph";
|
|
9
|
+
import { MemorySaver } from "@langchain/langgraph";
|
|
12
10
|
import {
|
|
13
11
|
isTerminalRunStatus
|
|
14
12
|
} from "@skein-js/core";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { ProtocolRuntime, RouteBinding, Logger, ProtocolDeps, RunWorkerOptions, CronSchedulerOptions, RunFailureReport, IdempotencyConfig, GraphResolver, RouteMatcher, DisabledRouteGroups, ProtocolResponse } from '@skein-js/agent-protocol';
|
|
1
|
+
import { ProtocolRuntime, RouteBinding, Logger, ProtocolDeps, RunWorkerOptions, CronSchedulerOptions, RunFailureReport, IdempotencyConfig, WebhookDeliveryConfig, GraphResolver, RouteMatcher, DisabledRouteGroups, ProtocolResponse } from '@skein-js/agent-protocol';
|
|
2
2
|
export { CompiledGraphFactory, DEFAULT_RUN_CONCURRENCY, DEFAULT_SHUTDOWN_GRACE_MS, GraphResolver, Logger, ProtocolDeps, ResolvedGraph, RunWorkerOptions } from '@skein-js/agent-protocol';
|
|
3
|
-
import { ModuleImporter, LanggraphJson, GraphSchemas } from '@skein-js/config';
|
|
3
|
+
import { LoadedChannel, ModuleImporter, LanggraphJson, GraphSchemas } from '@skein-js/config';
|
|
4
4
|
import { CorsOptions } from 'cors';
|
|
5
5
|
export { CorsOptions } from 'cors';
|
|
6
6
|
import { MemoryRunEventBusOptions } from '@skein-js/storage-memory';
|
|
7
|
-
import { D as DevStateSnapshot } from './dev-
|
|
8
|
-
export { a as DevStateCounts } from './dev-
|
|
7
|
+
import { D as DevStateSnapshot } from './dev-Zz4VRv7T.js';
|
|
8
|
+
export { a as DevStateCounts } from './dev-Zz4VRv7T.js';
|
|
9
9
|
import { StoreTtlConfig, ThreadTtlConfig } from '@skein-js/core';
|
|
10
10
|
import { CompiledGraph } from '@langchain/langgraph';
|
|
11
11
|
import { ServerResponse } from 'node:http';
|
|
@@ -114,6 +114,8 @@ interface ResolvedRuntimeDeps {
|
|
|
114
114
|
routes: readonly RouteBinding[];
|
|
115
115
|
/** The resolved logger — see {@link ResolvedProtocolRuntime.logger}. */
|
|
116
116
|
logger?: Logger;
|
|
117
|
+
/** Channel modules named by `skein.channels`, already imported. Absent on the injected-deps path. */
|
|
118
|
+
channels?: Record<string, LoadedChannel>;
|
|
117
119
|
}
|
|
118
120
|
/**
|
|
119
121
|
* Resolve the `{ config } | { deps }` seam down to a `ProtocolDeps` — injected as-is, or fresh
|
|
@@ -416,6 +418,13 @@ interface InMemoryRuntimeConfig {
|
|
|
416
418
|
cors?: CorsOptions;
|
|
417
419
|
/** The route table to mount, with any `http.disable_*` groups already removed. */
|
|
418
420
|
routes: readonly RouteBinding[];
|
|
421
|
+
/**
|
|
422
|
+
* Channel modules named by `skein.channels`, already imported, keyed by name.
|
|
423
|
+
*
|
|
424
|
+
* Empty when none is configured, which is what keeps the channel routes out of the table entirely
|
|
425
|
+
* rather than merely disabled.
|
|
426
|
+
*/
|
|
427
|
+
channels?: Record<string, LoadedChannel>;
|
|
419
428
|
}
|
|
420
429
|
/** Load `langgraph.json`, wiring fresh in-memory drivers and reading its `http.cors` for the adapter. */
|
|
421
430
|
declare function loadInMemoryRuntime(configPath: string, importModule?: ModuleImporter, staticSchemas?: Record<string, GraphSchemas>): Promise<InMemoryRuntimeConfig>;
|
|
@@ -497,14 +506,55 @@ interface RawIdempotency {
|
|
|
497
506
|
*/
|
|
498
507
|
declare function resolveIdempotency(raw: RawIdempotency | undefined): IdempotencyConfig | undefined;
|
|
499
508
|
|
|
509
|
+
/** The raw `skein.webhooks` block as `langgraph.json` spells it. */
|
|
510
|
+
interface RawWebhooks {
|
|
511
|
+
retries?: {
|
|
512
|
+
max_attempts?: number;
|
|
513
|
+
initial_delay_ms?: number;
|
|
514
|
+
};
|
|
515
|
+
secret?: string | string[];
|
|
516
|
+
max_payload_bytes?: number;
|
|
517
|
+
retain_hours?: number;
|
|
518
|
+
allowed_hosts?: string[];
|
|
519
|
+
require_https?: boolean;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Map the raw block to {@link WebhookDeliveryConfig}, or `undefined` when nothing is set.
|
|
523
|
+
*
|
|
524
|
+
* `undefined` means **defaults, not disabled**, the same distinction `resolveIdempotency` draws: a run
|
|
525
|
+
* that carries a `webhook` owes a callback whether or not a policy was configured. There is no
|
|
526
|
+
* setting that stops skein trying — only `retries.max_attempts: 1`, which asks it to try once.
|
|
527
|
+
*/
|
|
528
|
+
declare function resolveWebhooks(raw: RawWebhooks | undefined, options?: {
|
|
529
|
+
env?: Record<string, string | undefined>;
|
|
530
|
+
warn?: (message: string) => void;
|
|
531
|
+
}): WebhookDeliveryConfig | undefined;
|
|
532
|
+
|
|
500
533
|
type AnyCompiledGraph = CompiledGraph<any>;
|
|
501
534
|
/**
|
|
502
|
-
*
|
|
503
|
-
*
|
|
535
|
+
* The surface the engine actually drives, stated structurally.
|
|
536
|
+
*
|
|
537
|
+
* `CompiledGraph` is not the right bound here even though every graph is one. LangChain's own
|
|
538
|
+
* `createAgent` returns a `ReactAgent`, which is graph-*like* — it streams and it has state — but is
|
|
539
|
+
* not structurally a `CompiledGraph`, so requiring that type rejected the blessed way to build an
|
|
540
|
+
* agent. The engine never needed it: `langGraphResolver` casts whatever it is to an `AgentGraph`, and
|
|
541
|
+
* `AgentGraph` requires exactly these two methods.
|
|
542
|
+
*
|
|
543
|
+
* Kept narrow enough to still catch a mistake — an object with neither method is not a graph — while
|
|
544
|
+
* accepting anything the runtime can genuinely drive.
|
|
545
|
+
*/
|
|
546
|
+
interface EmbeddableAgentSurface {
|
|
547
|
+
stream(input: any, options?: any): any;
|
|
548
|
+
getState(config: any): any;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* A graph you can embed in code: any compiled LangGraph.js graph, anything `createAgent` returns, or a
|
|
552
|
+
* factory that builds one per run (called with the run's `configurable`). Keys of a graph map become
|
|
553
|
+
* graph ids.
|
|
504
554
|
*/
|
|
505
|
-
type EmbeddableGraph = AnyCompiledGraph | ((config: {
|
|
555
|
+
type EmbeddableGraph = AnyCompiledGraph | EmbeddableAgentSurface | ((config: {
|
|
506
556
|
configurable?: Record<string, unknown>;
|
|
507
|
-
}) => AnyCompiledGraph | Promise<AnyCompiledGraph>);
|
|
557
|
+
}) => AnyCompiledGraph | EmbeddableAgentSurface | Promise<AnyCompiledGraph | EmbeddableAgentSurface>);
|
|
508
558
|
/**
|
|
509
559
|
* Accept either a ready {@link GraphResolver} or a plain graph map and return a `GraphResolver` either
|
|
510
560
|
* way — the normalization every in-code embedding helper shares (`embedInMemoryGraphs` here,
|
|
@@ -703,4 +753,4 @@ type RuntimeGlobal = typeof globalThis & {
|
|
|
703
753
|
/** Detect Node, Bun, or Deno without importing a runtime-specific module. */
|
|
704
754
|
declare function detectRuntimeCapabilities(scope?: RuntimeGlobal): RuntimeCapabilities;
|
|
705
755
|
|
|
706
|
-
export { type ConsoleLogLevel, type ConsoleLoggerOptions, type CorsSetting, DEFAULT_HEAP_SAMPLE_MS, DEFAULT_HEAP_WARN_PERCENT, DevStateSnapshot, type EmbeddableGraph, type FileReader, HEAP_REARM_MARGIN_PERCENT, HEAP_REARM_PERCENT, type HeapHeadroom, type HeapPressureContext, type HeapPressureMonitor, type HeapPressureMonitorOptions, type InMemoryRuntimeConfig, type IntervalHandle, type LanggraphCorsConfig, type RawIdempotency, type RawStoreTtl, type RawThreadTtl, type ReloadableInMemoryRuntime, type ResolvedMemoryBusLimits, type ResolvedProtocolRuntime, type ResolvedRuntimeDeps, type RuntimeCapabilities, type RuntimeMemorySnapshot, type RuntimeSignalSubscription, type SkeinRuntimeCommonOptions, type SkeinRuntimeOptions, allowedOrigin, applyNodeCors, checkHeapHeadroom, consoleMountFromHttpConfig, corsFromHttpConfig, corsPreflightHeaders, corsResponseHeaders, createConsoleLogger, createInMemoryDeps, describeError, describeHeapHeadroom, describePoolPressure, detectRuntimeCapabilities, disabledRoutesFromHttpConfig, embedInMemoryGraphs, formatLogMeta, graphMapToResolver, joinList, loadInMemoryRuntime, loadReloadableInMemoryRuntime, normalizeEmbeddableGraphs, readContainerMemoryLimit, resolveCronTickMs, resolveHeapPressureOptions, resolveIdempotency, resolveMaxPageSize, resolveMemoryBusLimits, resolveProtocolRuntime, resolveRunConcurrency, resolveRunTimeoutMs, resolveRuntimeDeps, resolveShutdownGraceMs, resolveStoreTtl, resolveThreadTtl, routeMatcherFor, routesFromHttpConfig, runFailureIdentity, sendNodeError, sendNodePreflight, sendNodeResponse, startHeapPressureMonitor, stripBasePath, toCorsOptions };
|
|
756
|
+
export { type ConsoleLogLevel, type ConsoleLoggerOptions, type CorsSetting, DEFAULT_HEAP_SAMPLE_MS, DEFAULT_HEAP_WARN_PERCENT, DevStateSnapshot, type EmbeddableGraph, type FileReader, HEAP_REARM_MARGIN_PERCENT, HEAP_REARM_PERCENT, type HeapHeadroom, type HeapPressureContext, type HeapPressureMonitor, type HeapPressureMonitorOptions, type InMemoryRuntimeConfig, type IntervalHandle, type LanggraphCorsConfig, type RawIdempotency, type RawStoreTtl, type RawThreadTtl, type RawWebhooks, type ReloadableInMemoryRuntime, type ResolvedMemoryBusLimits, type ResolvedProtocolRuntime, type ResolvedRuntimeDeps, type RuntimeCapabilities, type RuntimeMemorySnapshot, type RuntimeSignalSubscription, type SkeinRuntimeCommonOptions, type SkeinRuntimeOptions, allowedOrigin, applyNodeCors, checkHeapHeadroom, consoleMountFromHttpConfig, corsFromHttpConfig, corsPreflightHeaders, corsResponseHeaders, createConsoleLogger, createInMemoryDeps, describeError, describeHeapHeadroom, describePoolPressure, detectRuntimeCapabilities, disabledRoutesFromHttpConfig, embedInMemoryGraphs, formatLogMeta, graphMapToResolver, joinList, loadInMemoryRuntime, loadReloadableInMemoryRuntime, normalizeEmbeddableGraphs, readContainerMemoryLimit, resolveCronTickMs, resolveHeapPressureOptions, resolveIdempotency, resolveMaxPageSize, resolveMemoryBusLimits, resolveProtocolRuntime, resolveRunConcurrency, resolveRunTimeoutMs, resolveRuntimeDeps, resolveShutdownGraceMs, resolveStoreTtl, resolveThreadTtl, resolveWebhooks, routeMatcherFor, routesFromHttpConfig, runFailureIdentity, sendNodeError, sendNodePreflight, sendNodeResponse, startHeapPressureMonitor, stripBasePath, toCorsOptions };
|
package/dist/index.js
CHANGED
|
@@ -15,14 +15,16 @@ import {
|
|
|
15
15
|
resolveMemoryBusLimits,
|
|
16
16
|
resolveStoreTtl,
|
|
17
17
|
resolveThreadTtl,
|
|
18
|
+
resolveWebhooks,
|
|
18
19
|
routesFromHttpConfig,
|
|
19
20
|
toCorsOptions
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-OZ477M7A.js";
|
|
21
22
|
import "./chunk-JUBBCC7R.js";
|
|
22
23
|
|
|
23
24
|
// src/resolve-runtime.ts
|
|
24
25
|
import {
|
|
25
26
|
createProtocolRuntime,
|
|
27
|
+
fetchWebhookDispatcher,
|
|
26
28
|
skeinRoutes
|
|
27
29
|
} from "@skein-js/agent-protocol";
|
|
28
30
|
|
|
@@ -166,22 +168,45 @@ function resolveLogger(option, injected, frameworkLogger) {
|
|
|
166
168
|
return option ?? frameworkLogger;
|
|
167
169
|
}
|
|
168
170
|
async function resolveRuntimeDeps(options, frameworkLogger) {
|
|
169
|
-
const loaded = options.deps ? { deps: options.deps, cors: void 0, routes: skeinRoutes } : await (await import("./in-memory-runtime-
|
|
171
|
+
const loaded = options.deps ? { deps: options.deps, cors: void 0, routes: skeinRoutes, channels: void 0 } : await (await import("./in-memory-runtime-HE3LTSLD.js")).loadInMemoryRuntime(options.config, options.importModule);
|
|
170
172
|
const logger = resolveLogger(options.logger, loaded.deps.logger, frameworkLogger);
|
|
171
173
|
if (logger && !loaded.deps.logger) loaded.deps.logger = logger;
|
|
172
|
-
return {
|
|
174
|
+
return {
|
|
175
|
+
deps: loaded.deps,
|
|
176
|
+
cors: loaded.cors,
|
|
177
|
+
routes: loaded.routes,
|
|
178
|
+
...loaded.channels ? { channels: loaded.channels } : {},
|
|
179
|
+
logger
|
|
180
|
+
};
|
|
173
181
|
}
|
|
174
182
|
async function resolveProtocolRuntime(options, frameworkLogger) {
|
|
175
183
|
const {
|
|
176
184
|
deps,
|
|
177
185
|
cors: corsFromConfig,
|
|
178
186
|
routes,
|
|
187
|
+
channels,
|
|
179
188
|
logger
|
|
180
189
|
} = await resolveRuntimeDeps(options, frameworkLogger);
|
|
181
190
|
const heapPressureOptions = resolveHeapPressureOptions();
|
|
182
191
|
const runTimeoutMs = resolveRunTimeoutMs(deps.runTimeoutMs);
|
|
183
192
|
if (runTimeoutMs !== void 0) deps.runTimeoutMs = runTimeoutMs;
|
|
193
|
+
const runtimeRef = {};
|
|
194
|
+
const channelSurface = channels ? await (await import("./channels-runtime-OADE6ORT.js")).resolveChannels({
|
|
195
|
+
channels,
|
|
196
|
+
graphIds: deps.graphs.ids,
|
|
197
|
+
deps,
|
|
198
|
+
service: () => {
|
|
199
|
+
const current = runtimeRef.current;
|
|
200
|
+
if (!current) throw new Error("channel pipeline used before the runtime was assembled");
|
|
201
|
+
return current.service;
|
|
202
|
+
}
|
|
203
|
+
}) : void 0;
|
|
204
|
+
if (channelSurface) {
|
|
205
|
+
const inner = deps.webhookDispatcher ?? fetchWebhookDispatcher;
|
|
206
|
+
deps.webhookDispatcher = channelSurface.wrapDispatcher(inner);
|
|
207
|
+
}
|
|
184
208
|
const runtime = createProtocolRuntime(deps, {
|
|
209
|
+
...channelSurface ? { handlers: channelSurface.handlers } : {},
|
|
185
210
|
worker: {
|
|
186
211
|
...options.worker,
|
|
187
212
|
maxConcurrency: resolveRunConcurrency(options.worker?.maxConcurrency),
|
|
@@ -195,6 +220,7 @@ async function resolveProtocolRuntime(options, frameworkLogger) {
|
|
|
195
220
|
enabled: options.scheduler?.enabled ?? routes.some((route) => route.group === "crons")
|
|
196
221
|
}
|
|
197
222
|
});
|
|
223
|
+
runtimeRef.current = runtime;
|
|
198
224
|
await runtime.service.assistants.registerGraphAssistants();
|
|
199
225
|
if (options.warm) {
|
|
200
226
|
await Promise.all(
|
|
@@ -230,7 +256,15 @@ async function resolveProtocolRuntime(options, frameworkLogger) {
|
|
|
230
256
|
await runtime.worker.stop();
|
|
231
257
|
}
|
|
232
258
|
};
|
|
233
|
-
return {
|
|
259
|
+
return {
|
|
260
|
+
runtime: { ...runtime, worker },
|
|
261
|
+
cors: corsFromConfig,
|
|
262
|
+
// Channel routes are *appended*, never declared in `skeinRoutes`: they exist only when a channel
|
|
263
|
+
// is configured, which is what makes the feature undetectable to a deployment that never wanted
|
|
264
|
+
// it — strictly stronger than an `http.disable_*` flag, and it needs no `RouteGroup` to express.
|
|
265
|
+
routes: channelSurface ? [...routes, ...channelSurface.routes] : routes,
|
|
266
|
+
logger
|
|
267
|
+
};
|
|
234
268
|
}
|
|
235
269
|
|
|
236
270
|
// src/log-meta.ts
|
|
@@ -529,6 +563,7 @@ function sendNodeError(error, res, logger, adapterName = "skein") {
|
|
|
529
563
|
return;
|
|
530
564
|
}
|
|
531
565
|
if (isSkeinHttpError(error)) {
|
|
566
|
+
if (error.status >= 500) logger?.error(`Request failed: ${error.message}`, error);
|
|
532
567
|
res.writeHead(error.status, { "content-type": "application/json" });
|
|
533
568
|
res.end(
|
|
534
569
|
JSON.stringify({
|
|
@@ -688,6 +723,7 @@ export {
|
|
|
688
723
|
resolveShutdownGraceMs,
|
|
689
724
|
resolveStoreTtl,
|
|
690
725
|
resolveThreadTtl,
|
|
726
|
+
resolveWebhooks,
|
|
691
727
|
routeMatcherFor,
|
|
692
728
|
routesFromHttpConfig,
|
|
693
729
|
runFailureIdentity,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skein-js/server-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
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>",
|
|
@@ -49,20 +49,29 @@
|
|
|
49
49
|
"cors": "^2.8.5",
|
|
50
50
|
"superjson": "^2.2.6",
|
|
51
51
|
"zod": "^3.25.76",
|
|
52
|
-
"@skein-js/
|
|
53
|
-
"@skein-js/
|
|
54
|
-
"@skein-js/
|
|
55
|
-
"@skein-js/
|
|
52
|
+
"@skein-js/agent-protocol": "0.16.0",
|
|
53
|
+
"@skein-js/config": "0.16.0",
|
|
54
|
+
"@skein-js/core": "0.16.0",
|
|
55
|
+
"@skein-js/langgraph": "0.16.0",
|
|
56
|
+
"@skein-js/storage-memory": "0.16.0"
|
|
56
57
|
},
|
|
57
58
|
"peerDependencies": {
|
|
58
|
-
"@langchain/langgraph": "^1.4.0"
|
|
59
|
+
"@langchain/langgraph": "^1.4.0",
|
|
60
|
+
"@skein-js/channels": "0.16.0"
|
|
59
61
|
},
|
|
60
62
|
"devDependencies": {
|
|
61
|
-
"@langchain/core": "^1.2.
|
|
63
|
+
"@langchain/core": "^1.2.9",
|
|
62
64
|
"@langchain/langgraph": "^1.4.0",
|
|
63
|
-
"@
|
|
65
|
+
"@langchain/langgraph-sdk": "^1.9.0",
|
|
66
|
+
"@types/cors": "^2.8.17",
|
|
67
|
+
"@skein-js/channels": "0.16.0"
|
|
64
68
|
},
|
|
65
69
|
"publishConfig": {
|
|
66
70
|
"access": "public"
|
|
71
|
+
},
|
|
72
|
+
"peerDependenciesMeta": {
|
|
73
|
+
"@skein-js/channels": {
|
|
74
|
+
"optional": true
|
|
75
|
+
}
|
|
67
76
|
}
|
|
68
77
|
}
|