@skein-js/agent-protocol 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/dist/index.d.ts +54 -11
- package/dist/index.js +213 -74
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -374,6 +374,12 @@ interface ProtocolRequest {
|
|
|
374
374
|
query: Record<string, string | string[] | undefined>;
|
|
375
375
|
body: unknown;
|
|
376
376
|
headers: Record<string, string | undefined>;
|
|
377
|
+
/**
|
|
378
|
+
* Aborts when the client goes away, so a handler can stop work the caller will never read.
|
|
379
|
+
* Optional: an adapter that can't observe disconnects simply omits it. Currently honored by the
|
|
380
|
+
* single-graph invoke surface; protocol runs carry their own cancellation through the run row.
|
|
381
|
+
*/
|
|
382
|
+
signal?: AbortSignal;
|
|
377
383
|
}
|
|
378
384
|
/** A normalized response an adapter serializes back onto its framework response. */
|
|
379
385
|
type ProtocolResponse = {
|
|
@@ -466,11 +472,16 @@ interface ProtocolRuntime {
|
|
|
466
472
|
declare function createProtocolRuntime(deps: ProtocolDeps, options?: ProtocolRuntimeOptions): ProtocolRuntime;
|
|
467
473
|
|
|
468
474
|
type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
469
|
-
|
|
475
|
+
/**
|
|
476
|
+
* One route. `HandlerName` defaults to a member of the protocol handler table; a surface mounted
|
|
477
|
+
* outside that table (the single-graph invoke endpoint) parameterizes it with its own handler names,
|
|
478
|
+
* so `handler` never has to lie about being a `keyof ProtocolHandlers` an adapter could look up.
|
|
479
|
+
*/
|
|
480
|
+
interface RouteBinding<HandlerName = keyof ProtocolHandlers> {
|
|
470
481
|
method: HttpMethod;
|
|
471
482
|
/** Path with `:param` placeholders, e.g. `/threads/:thread_id/runs/stream`. */
|
|
472
483
|
path: string;
|
|
473
|
-
handler:
|
|
484
|
+
handler: HandlerName;
|
|
474
485
|
/**
|
|
475
486
|
* Fold the path `thread_id` into the request body before dispatch. The SDK addresses a
|
|
476
487
|
* thread-scoped run by its path (`POST /threads/{id}/runs/stream`) while carrying only
|
|
@@ -493,18 +504,50 @@ declare function copyThreadIdIntoBody(request: ProtocolRequest): ProtocolRequest
|
|
|
493
504
|
*/
|
|
494
505
|
declare const foldThreadId: typeof copyThreadIdIntoBody;
|
|
495
506
|
/** A resolved route: the matched binding plus the path params extracted from the URL. */
|
|
496
|
-
interface RouteMatch {
|
|
497
|
-
binding: RouteBinding
|
|
507
|
+
interface RouteMatch<HandlerName = keyof ProtocolHandlers> {
|
|
508
|
+
binding: RouteBinding<HandlerName>;
|
|
498
509
|
params: Record<string, string>;
|
|
499
510
|
}
|
|
511
|
+
/** Matches a `method` + `pathname` against a fixed route table. Built by {@link createRouteMatcher}. */
|
|
512
|
+
type RouteMatcher<HandlerName = keyof ProtocolHandlers> = (method: string, pathname: string) => RouteMatch<HandlerName> | undefined;
|
|
513
|
+
/**
|
|
514
|
+
* Build a matcher over any route table — the protocol's own, or the simplified invoke surface's.
|
|
515
|
+
* Each `:param` path is compiled to a named-group regex once, anchored (`^…$`) so a literal segment
|
|
516
|
+
* can't be matched as a param value. The returned matcher iterates in table order, which is
|
|
517
|
+
* most-specific-first, so a literal segment wins over a `:param`.
|
|
518
|
+
*/
|
|
519
|
+
declare function createRouteMatcher<HandlerName>(bindings: readonly RouteBinding<HandlerName>[]): RouteMatcher<HandlerName>;
|
|
520
|
+
/**
|
|
521
|
+
* Match a `method` + `pathname` (no query string) against the Agent Protocol route table. This is
|
|
522
|
+
* what adapters that dispatch from a catch-all route (NestJS middleware, Next.js route handlers) use
|
|
523
|
+
* in place of a framework router; Express/Fastify bind `skeinRoutes` to their native router instead.
|
|
524
|
+
*/
|
|
525
|
+
declare const matchSkeinRoute: RouteMatcher;
|
|
526
|
+
|
|
527
|
+
/** The default path prefix the invoke endpoint mounts under. */
|
|
528
|
+
declare const DEFAULT_INVOKE_PREFIX = "/invoke";
|
|
529
|
+
/** Options for {@link createGraphInvokeHandler}. */
|
|
530
|
+
interface GraphInvokeOptions {
|
|
531
|
+
/**
|
|
532
|
+
* Stream modes used when the caller opts into SSE, overridable per request via `?stream_mode=`.
|
|
533
|
+
* Defaults to `"values"` — each frame is the full state after a step, ending at the same value the
|
|
534
|
+
* JSON response would have returned.
|
|
535
|
+
*/
|
|
536
|
+
streamMode?: StreamMode | StreamMode[];
|
|
537
|
+
}
|
|
538
|
+
/** The single handler name this surface binds — deliberately not a `keyof ProtocolHandlers`. */
|
|
539
|
+
type GraphInvokeHandlerName = "invokeGraph";
|
|
540
|
+
/**
|
|
541
|
+
* The route table for the invoke surface — one POST, shaped like {@link skeinRoutes} so catch-all
|
|
542
|
+
* adapters (NestJS, Next.js) can match it the same way and native routers can bind it directly.
|
|
543
|
+
* Parameterized with its own handler name, so nothing can mistake it for a protocol-table lookup key.
|
|
544
|
+
*/
|
|
545
|
+
declare function graphInvokeRoutes(prefix?: string): RouteBinding<GraphInvokeHandlerName>[];
|
|
500
546
|
/**
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
* most-specific-first, so a literal segment wins over a `:param`. This is what adapters that dispatch
|
|
504
|
-
* from a catch-all route (NestJS middleware, Next.js route handlers) use in place of a framework
|
|
505
|
-
* router; Express/Fastify bind `skeinRoutes` to their native router instead.
|
|
547
|
+
* Build the handler for `POST <prefix>/:graph_id`. Returns a {@link ProtocolHandler}, so every
|
|
548
|
+
* adapter serializes it through the same JSON/SSE path the protocol routes already use.
|
|
506
549
|
*/
|
|
507
|
-
declare function
|
|
550
|
+
declare function createGraphInvokeHandler(deps: ProtocolDeps, options?: GraphInvokeOptions): ProtocolHandler;
|
|
508
551
|
|
|
509
552
|
type LangGraphSearchItem = Item$1 & {
|
|
510
553
|
score?: number;
|
|
@@ -559,4 +602,4 @@ declare function toSseEvents(frames: AsyncIterable<RunFrame>, finalStatus: () =>
|
|
|
559
602
|
*/
|
|
560
603
|
declare function parseAfterSeq(lastEventId: string | undefined): number;
|
|
561
604
|
|
|
562
|
-
export { type AssistantService, type Clock, type CommandInput, type CompiledGraphFactory, type CreateAssistantInput, type CreateRunInput, type CreateThreadInput, type DeleteAssistantOptions, type DrawGraphOptions, type GraphResolver, type GraphSchemas, type HistoryOptions, type HttpMethod, type Logger, type PatchThreadInput, type ProtocolContext, type ProtocolDeps, type ProtocolHandler, type ProtocolHandlers, type ProtocolRequest, type ProtocolResponse, type ProtocolRuntime, type ProtocolRuntimeOptions, type ProtocolService, type ResolvedGraph, type RouteBinding, type RouteMatch, type RunService, type RunWorker, type RunWorkerOptions, SSE_HEADERS, SkeinBaseStore, type StartedStream, type StoreService, type SubgraphsOptions, type ThreadService, type ThreadStreamInput, type ThreadStreamService, buildProtocolService, copyThreadIdIntoBody, createContext, createProtocolHandlers, createProtocolRuntime, createProtocolService, createProtocolServiceFromContext, createRunWorker, encodeFrame, encodeTerminal, foldThreadId, matchSkeinRoute, parseAfterSeq, skeinRoutes, toSseEvents };
|
|
605
|
+
export { type AssistantService, type Clock, type CommandInput, type CompiledGraphFactory, type CreateAssistantInput, type CreateRunInput, type CreateThreadInput, DEFAULT_INVOKE_PREFIX, type DeleteAssistantOptions, type DrawGraphOptions, type GraphInvokeHandlerName, type GraphInvokeOptions, type GraphResolver, type GraphSchemas, type HistoryOptions, type HttpMethod, type Logger, type PatchThreadInput, type ProtocolContext, type ProtocolDeps, type ProtocolHandler, type ProtocolHandlers, type ProtocolRequest, type ProtocolResponse, type ProtocolRuntime, type ProtocolRuntimeOptions, type ProtocolService, type ResolvedGraph, type RouteBinding, type RouteMatch, type RouteMatcher, type RunService, type RunWorker, type RunWorkerOptions, SSE_HEADERS, SkeinBaseStore, type StartedStream, type StoreService, type SubgraphsOptions, type ThreadService, type ThreadStreamInput, type ThreadStreamService, buildProtocolService, copyThreadIdIntoBody, createContext, createGraphInvokeHandler, createProtocolHandlers, createProtocolRuntime, createProtocolService, createProtocolServiceFromContext, createRouteMatcher, createRunWorker, encodeFrame, encodeTerminal, foldThreadId, graphInvokeRoutes, matchSkeinRoute, parseAfterSeq, skeinRoutes, toSseEvents };
|
package/dist/index.js
CHANGED
|
@@ -758,50 +758,6 @@ import {
|
|
|
758
758
|
SkeinHttpError as SkeinHttpError4
|
|
759
759
|
} from "@skein-js/core";
|
|
760
760
|
|
|
761
|
-
// src/normalize-error.ts
|
|
762
|
-
import { isSkeinHttpError as isSkeinHttpError2, SkeinHttpError as SkeinHttpError3 } from "@skein-js/core";
|
|
763
|
-
function serializeError(error) {
|
|
764
|
-
if (error instanceof Error) {
|
|
765
|
-
return { name: error.name, message: error.message };
|
|
766
|
-
}
|
|
767
|
-
return { name: "Error", message: String(error) };
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// src/sse/run-frame-stream.ts
|
|
771
|
-
var STREAM_MODES = [
|
|
772
|
-
"values",
|
|
773
|
-
"updates",
|
|
774
|
-
"messages",
|
|
775
|
-
"messages-tuple",
|
|
776
|
-
"custom",
|
|
777
|
-
"events",
|
|
778
|
-
"debug"
|
|
779
|
-
];
|
|
780
|
-
function isStreamMode(value) {
|
|
781
|
-
return typeof value === "string" && STREAM_MODES.includes(value);
|
|
782
|
-
}
|
|
783
|
-
function chunkToFrameBody(chunk) {
|
|
784
|
-
if (Array.isArray(chunk) && chunk.length === 2 && isStreamMode(chunk[0])) {
|
|
785
|
-
return { event: chunk[0], data: chunk[1] };
|
|
786
|
-
}
|
|
787
|
-
return { event: "updates", data: chunk };
|
|
788
|
-
}
|
|
789
|
-
function toRunFrame(seq, body) {
|
|
790
|
-
return { seq, event: body.event, data: body.data };
|
|
791
|
-
}
|
|
792
|
-
function streamEventToFrameBody(event, runId, graphModes) {
|
|
793
|
-
if (event.tags?.includes("langsmith:hidden")) return null;
|
|
794
|
-
if (event.event === "on_chain_stream" && event.run_id === runId) {
|
|
795
|
-
const chunk = event.data?.chunk;
|
|
796
|
-
if (Array.isArray(chunk) && chunk.length === 2 && isStreamMode(chunk[0])) {
|
|
797
|
-
const mode = chunk[0];
|
|
798
|
-
return graphModes.includes(mode) ? { event: mode, data: chunk[1] } : null;
|
|
799
|
-
}
|
|
800
|
-
return null;
|
|
801
|
-
}
|
|
802
|
-
return { event: "events", data: event };
|
|
803
|
-
}
|
|
804
|
-
|
|
805
761
|
// src/store/skein-base-store.ts
|
|
806
762
|
import {
|
|
807
763
|
BaseStore
|
|
@@ -878,6 +834,60 @@ var SkeinBaseStore = class extends BaseStore {
|
|
|
878
834
|
}
|
|
879
835
|
};
|
|
880
836
|
|
|
837
|
+
// src/graphs/resolve-compiled-graph.ts
|
|
838
|
+
async function resolveCompiledGraph(graphs, graphId, attachments) {
|
|
839
|
+
const resolved = await graphs.load(graphId);
|
|
840
|
+
const shared = typeof resolved === "function" ? await resolved({ configurable: attachments.configurable }) : resolved;
|
|
841
|
+
const graph = Object.create(shared);
|
|
842
|
+
graph.checkpointer = attachments.checkpointer;
|
|
843
|
+
graph.store = new SkeinBaseStore(attachments.store);
|
|
844
|
+
return graph;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/normalize-error.ts
|
|
848
|
+
import { isSkeinHttpError as isSkeinHttpError2, SkeinHttpError as SkeinHttpError3 } from "@skein-js/core";
|
|
849
|
+
function serializeError(error) {
|
|
850
|
+
if (error instanceof Error) {
|
|
851
|
+
return { name: error.name, message: error.message };
|
|
852
|
+
}
|
|
853
|
+
return { name: "Error", message: String(error) };
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// src/sse/run-frame-stream.ts
|
|
857
|
+
var STREAM_MODES = [
|
|
858
|
+
"values",
|
|
859
|
+
"updates",
|
|
860
|
+
"messages",
|
|
861
|
+
"messages-tuple",
|
|
862
|
+
"custom",
|
|
863
|
+
"events",
|
|
864
|
+
"debug"
|
|
865
|
+
];
|
|
866
|
+
function isStreamMode(value) {
|
|
867
|
+
return typeof value === "string" && STREAM_MODES.includes(value);
|
|
868
|
+
}
|
|
869
|
+
function chunkToFrameBody(chunk) {
|
|
870
|
+
if (Array.isArray(chunk) && chunk.length === 2 && isStreamMode(chunk[0])) {
|
|
871
|
+
return { event: chunk[0], data: chunk[1] };
|
|
872
|
+
}
|
|
873
|
+
return { event: "updates", data: chunk };
|
|
874
|
+
}
|
|
875
|
+
function toRunFrame(seq, body) {
|
|
876
|
+
return { seq, event: body.event, data: body.data };
|
|
877
|
+
}
|
|
878
|
+
function streamEventToFrameBody(event, runId, graphModes) {
|
|
879
|
+
if (event.tags?.includes("langsmith:hidden")) return null;
|
|
880
|
+
if (event.event === "on_chain_stream" && event.run_id === runId) {
|
|
881
|
+
const chunk = event.data?.chunk;
|
|
882
|
+
if (Array.isArray(chunk) && chunk.length === 2 && isStreamMode(chunk[0])) {
|
|
883
|
+
const mode = chunk[0];
|
|
884
|
+
return graphModes.includes(mode) ? { event: mode, data: chunk[1] } : null;
|
|
885
|
+
}
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
return { event: "events", data: event };
|
|
889
|
+
}
|
|
890
|
+
|
|
881
891
|
// src/runs/run-status.ts
|
|
882
892
|
import { isTerminalRunStatus } from "@skein-js/core";
|
|
883
893
|
function threadStatusForRun(status) {
|
|
@@ -1125,16 +1135,11 @@ function abortedStatus(reason) {
|
|
|
1125
1135
|
return "cancelled";
|
|
1126
1136
|
}
|
|
1127
1137
|
async function resolveGraph(deps, graphId, kwargs) {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
await resolved({ configurable: toFactoryConfigurable(kwargs) })
|
|
1134
|
-
) : resolved;
|
|
1135
|
-
graph.checkpointer = deps.checkpointer;
|
|
1136
|
-
graph.store = new SkeinBaseStore(deps.store.store);
|
|
1137
|
-
return graph;
|
|
1138
|
+
return resolveCompiledGraph(deps.graphs, graphId, {
|
|
1139
|
+
configurable: toFactoryConfigurable(kwargs),
|
|
1140
|
+
checkpointer: deps.checkpointer,
|
|
1141
|
+
store: deps.store.store
|
|
1142
|
+
});
|
|
1138
1143
|
}
|
|
1139
1144
|
async function finalizeRun(deps, runId, status) {
|
|
1140
1145
|
const fresh = await deps.store.runs.get(runId);
|
|
@@ -1960,27 +1965,29 @@ function synthesizeRequest(req) {
|
|
|
1960
1965
|
});
|
|
1961
1966
|
}
|
|
1962
1967
|
|
|
1963
|
-
// src/auth/
|
|
1968
|
+
// src/auth/authenticate-request.ts
|
|
1964
1969
|
var STUDIO_USER = {
|
|
1965
1970
|
identity: "langgraph-studio-user",
|
|
1966
1971
|
display_name: "langgraph-studio-user",
|
|
1967
1972
|
is_authenticated: true,
|
|
1968
1973
|
permissions: []
|
|
1969
1974
|
};
|
|
1975
|
+
async function resolveAuthContext(engine, req) {
|
|
1976
|
+
if (!engine.studioAuthDisabled && req.headers["x-auth-scheme"] === "langsmith") {
|
|
1977
|
+
return { user: STUDIO_USER, scopes: [] };
|
|
1978
|
+
}
|
|
1979
|
+
return engine.authenticate(synthesizeRequest(req));
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// src/auth/authorizing-handlers.ts
|
|
1970
1983
|
function createAuthorizingHandlers(context, engine) {
|
|
1971
1984
|
const baseHandlers = createProtocolHandlers(createProtocolServiceFromContext(context));
|
|
1972
1985
|
const names = Object.keys(ROUTE_AUTHZ);
|
|
1973
|
-
const resolveAuthContext = async (req) => {
|
|
1974
|
-
if (!engine.studioAuthDisabled && req.headers["x-auth-scheme"] === "langsmith") {
|
|
1975
|
-
return { user: STUDIO_USER, scopes: [] };
|
|
1976
|
-
}
|
|
1977
|
-
return engine.authenticate(synthesizeRequest(req));
|
|
1978
|
-
};
|
|
1979
1986
|
const wrapped = {};
|
|
1980
1987
|
for (const name of names) {
|
|
1981
1988
|
const route = ROUTE_AUTHZ[name];
|
|
1982
1989
|
wrapped[name] = async (req) => {
|
|
1983
|
-
const authContext = await resolveAuthContext(req);
|
|
1990
|
+
const authContext = await resolveAuthContext(engine, req);
|
|
1984
1991
|
const { filters } = await engine.authorize({
|
|
1985
1992
|
resource: route.resource,
|
|
1986
1993
|
action: route.action,
|
|
@@ -2154,19 +2161,22 @@ function copyThreadIdIntoBody(request) {
|
|
|
2154
2161
|
return { ...request, body: { ...base, thread_id: threadId } };
|
|
2155
2162
|
}
|
|
2156
2163
|
var foldThreadId = copyThreadIdIntoBody;
|
|
2157
|
-
|
|
2158
|
-
binding
|
|
2159
|
-
|
|
2160
|
-
})
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2164
|
+
function createRouteMatcher(bindings) {
|
|
2165
|
+
const compiled = bindings.map((binding) => ({
|
|
2166
|
+
binding,
|
|
2167
|
+
regex: new RegExp(`^${binding.path.replace(/:(\w+)/g, "(?<$1>[^/]+)")}$`)
|
|
2168
|
+
}));
|
|
2169
|
+
return (method, pathname) => {
|
|
2170
|
+
const wanted = method.toLowerCase();
|
|
2171
|
+
for (const { binding, regex } of compiled) {
|
|
2172
|
+
if (binding.method !== wanted) continue;
|
|
2173
|
+
const match = regex.exec(pathname);
|
|
2174
|
+
if (match) return { binding, params: decodeParams(match.groups) };
|
|
2175
|
+
}
|
|
2176
|
+
return void 0;
|
|
2177
|
+
};
|
|
2169
2178
|
}
|
|
2179
|
+
var matchSkeinRoute = createRouteMatcher(skeinRoutes);
|
|
2170
2180
|
function decodeParams(groups) {
|
|
2171
2181
|
const params = {};
|
|
2172
2182
|
for (const [key, value] of Object.entries(groups ?? {})) {
|
|
@@ -2178,20 +2188,149 @@ function decodeParams(groups) {
|
|
|
2178
2188
|
}
|
|
2179
2189
|
return params;
|
|
2180
2190
|
}
|
|
2191
|
+
|
|
2192
|
+
// src/invoke/graph-invoke.ts
|
|
2193
|
+
import { randomUUID } from "crypto";
|
|
2194
|
+
import { MemorySaver } from "@langchain/langgraph";
|
|
2195
|
+
import {
|
|
2196
|
+
SkeinHttpError as SkeinHttpError10
|
|
2197
|
+
} from "@skein-js/core";
|
|
2198
|
+
import { z as z2 } from "zod";
|
|
2199
|
+
var DEFAULT_INVOKE_PREFIX = "/invoke";
|
|
2200
|
+
function graphInvokeRoutes(prefix = DEFAULT_INVOKE_PREFIX) {
|
|
2201
|
+
return [{ method: "post", path: `${normalizePrefix(prefix)}/:graph_id`, handler: "invokeGraph" }];
|
|
2202
|
+
}
|
|
2203
|
+
function normalizePrefix(prefix) {
|
|
2204
|
+
const trimmed = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
2205
|
+
return trimmed === "/" ? "" : trimmed;
|
|
2206
|
+
}
|
|
2207
|
+
function wantsEventStream(req) {
|
|
2208
|
+
return req.headers["accept"]?.includes("text/event-stream") ?? false;
|
|
2209
|
+
}
|
|
2210
|
+
function createRunSignal(timeoutMs, callerSignal) {
|
|
2211
|
+
if (timeoutMs === void 0 && !callerSignal) return { signal: void 0, dispose: () => {
|
|
2212
|
+
} };
|
|
2213
|
+
const controller = new AbortController();
|
|
2214
|
+
const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => controller.abort(new Error(`Run exceeded ${timeoutMs}ms`)), timeoutMs);
|
|
2215
|
+
const onCallerAbort = () => controller.abort(callerSignal?.reason);
|
|
2216
|
+
if (callerSignal?.aborted) onCallerAbort();
|
|
2217
|
+
else callerSignal?.addEventListener("abort", onCallerAbort);
|
|
2218
|
+
return {
|
|
2219
|
+
signal: controller.signal,
|
|
2220
|
+
dispose: () => {
|
|
2221
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2222
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
2223
|
+
}
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
var INVOKE_STREAM_MODES = [
|
|
2227
|
+
"values",
|
|
2228
|
+
"updates",
|
|
2229
|
+
"messages",
|
|
2230
|
+
"messages-tuple",
|
|
2231
|
+
"custom",
|
|
2232
|
+
"debug"
|
|
2233
|
+
];
|
|
2234
|
+
var streamModeQuerySchema = z2.array(z2.enum(INVOKE_STREAM_MODES)).min(1, "expected at least one stream mode");
|
|
2235
|
+
function streamModeFromQuery(value) {
|
|
2236
|
+
const raw = Array.isArray(value) ? value : value === void 0 ? [] : value.split(",");
|
|
2237
|
+
const modes = raw.map((mode) => mode.trim()).filter((mode) => mode.length > 0);
|
|
2238
|
+
if (modes.length === 0) return void 0;
|
|
2239
|
+
if (modes.includes("events")) {
|
|
2240
|
+
throw SkeinHttpError10.badRequest(
|
|
2241
|
+
'Stream mode "events" is not supported on the invoke surface; use the Agent Protocol run endpoints for token-level events.'
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
return parse(streamModeQuerySchema, modes, "stream_mode query parameter");
|
|
2245
|
+
}
|
|
2246
|
+
function createGraphInvokeHandler(deps, options = {}) {
|
|
2247
|
+
return async (req) => {
|
|
2248
|
+
const graphId = requireParam(req.params, "graph_id");
|
|
2249
|
+
let authContext;
|
|
2250
|
+
if (deps.auth) {
|
|
2251
|
+
authContext = await resolveAuthContext(deps.auth, req);
|
|
2252
|
+
await deps.auth.authorize({
|
|
2253
|
+
resource: "threads",
|
|
2254
|
+
action: "create_run",
|
|
2255
|
+
// `authValue` spreads the body last, and on this surface the body is arbitrary caller-supplied
|
|
2256
|
+
// graph input — so a body key named `graph_id` would shadow the path param and let a policy
|
|
2257
|
+
// authorize a different graph than the one that actually runs. Re-stamp the server-derived id
|
|
2258
|
+
// last so the value a policy judges is always the graph we execute.
|
|
2259
|
+
value: { ...authValue(req), graph_id: graphId },
|
|
2260
|
+
context: authContext
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
if (!deps.graphs.ids.includes(graphId)) {
|
|
2264
|
+
throw SkeinHttpError10.notFound(`Graph "${graphId}" is not registered.`, {
|
|
2265
|
+
code: "graph_not_found"
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
const factoryConfigurable2 = withAuthUser({}, authContext?.user, authContext?.scopes);
|
|
2269
|
+
const graph = await resolveCompiledGraph(deps.graphs, graphId, {
|
|
2270
|
+
configurable: Object.keys(factoryConfigurable2).length > 0 ? factoryConfigurable2 : void 0,
|
|
2271
|
+
checkpointer: new MemorySaver(),
|
|
2272
|
+
store: deps.store.store
|
|
2273
|
+
});
|
|
2274
|
+
const configurable = withAuthUser(
|
|
2275
|
+
{ thread_id: randomUUID() },
|
|
2276
|
+
authContext?.user,
|
|
2277
|
+
authContext?.scopes
|
|
2278
|
+
);
|
|
2279
|
+
const input = req.body ?? {};
|
|
2280
|
+
const { signal, dispose } = createRunSignal(deps.runTimeoutMs, req.signal);
|
|
2281
|
+
if (!wantsEventStream(req)) {
|
|
2282
|
+
try {
|
|
2283
|
+
const output = await graph.invoke(input, { configurable, signal });
|
|
2284
|
+
return { kind: "json", status: 200, body: output };
|
|
2285
|
+
} finally {
|
|
2286
|
+
dispose();
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
const streamMode = toGraphStreamModes(
|
|
2290
|
+
streamModeFromQuery(req.query["stream_mode"]) ?? options.streamMode ?? "values"
|
|
2291
|
+
);
|
|
2292
|
+
let status = "success";
|
|
2293
|
+
const frames = async function* () {
|
|
2294
|
+
let seq = 0;
|
|
2295
|
+
try {
|
|
2296
|
+
const stream = await graph.stream(input, {
|
|
2297
|
+
configurable,
|
|
2298
|
+
streamMode,
|
|
2299
|
+
signal
|
|
2300
|
+
});
|
|
2301
|
+
for await (const chunk of stream) {
|
|
2302
|
+
seq += 1;
|
|
2303
|
+
yield toRunFrame(seq, chunkToFrameBody(chunk));
|
|
2304
|
+
}
|
|
2305
|
+
} catch (error) {
|
|
2306
|
+
status = "error";
|
|
2307
|
+
seq += 1;
|
|
2308
|
+
yield { seq, event: "error", data: serializeError(error) };
|
|
2309
|
+
} finally {
|
|
2310
|
+
dispose();
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2313
|
+
return { kind: "sse", status: 200, events: toSseEvents(frames(), async () => status) };
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2181
2316
|
export {
|
|
2317
|
+
DEFAULT_INVOKE_PREFIX,
|
|
2182
2318
|
SSE_HEADERS,
|
|
2183
2319
|
SkeinBaseStore,
|
|
2184
2320
|
buildProtocolService,
|
|
2185
2321
|
copyThreadIdIntoBody,
|
|
2186
2322
|
createContext,
|
|
2323
|
+
createGraphInvokeHandler,
|
|
2187
2324
|
createProtocolHandlers,
|
|
2188
2325
|
createProtocolRuntime,
|
|
2189
2326
|
createProtocolService,
|
|
2190
2327
|
createProtocolServiceFromContext,
|
|
2328
|
+
createRouteMatcher,
|
|
2191
2329
|
createRunWorker,
|
|
2192
2330
|
encodeFrame,
|
|
2193
2331
|
encodeTerminal,
|
|
2194
2332
|
foldThreadId,
|
|
2333
|
+
graphInvokeRoutes,
|
|
2195
2334
|
matchSkeinRoute,
|
|
2196
2335
|
parseAfterSeq,
|
|
2197
2336
|
skeinRoutes,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skein-js/agent-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Framework-agnostic Agent Protocol engine for LangGraph.js — run engine, handlers, and SSE, driven entirely by injected dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Maina Wycliffe <wmmaina7@gmail.com>",
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"zod": "^3.25.76",
|
|
47
|
-
"@skein-js/core": "0.
|
|
47
|
+
"@skein-js/core": "0.9.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@langchain/langgraph": "^1.4.0",
|
|
51
51
|
"@langchain/langgraph-sdk": "^1.9.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@skein-js/storage-memory": "0.
|
|
54
|
+
"@skein-js/storage-memory": "0.9.0"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public"
|