@skein-js/agent-protocol 0.3.0 → 0.5.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 +15 -2
- package/dist/index.js +209 -42
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GraphSchema, SkeinStore, RunQueue, RunEventBus, AuthEngine, Assistant, Config, StreamMode, Metadata, MultitaskStrategy, RunFrame, DefaultValues, Run, RunStatus, Item, StoreSearchQuery, SearchItem, Thread, ThreadState, StoreRepo } from '@skein-js/core';
|
|
1
|
+
import { GraphSchema, SkeinStore, RunQueue, RunEventBus, AuthEngine, Assistant, AuthUser, Config, StreamMode, Metadata, MultitaskStrategy, RunFrame, DefaultValues, Run, RunStatus, StorePutOptions, Item, StoreSearchQuery, SearchItem, Thread, ThreadSearchQuery, ThreadState, StoreRepo } from '@skein-js/core';
|
|
2
2
|
import { CompiledGraph, BaseCheckpointSaver, BaseStore, Item as Item$1, Operation, OperationResults } from '@langchain/langgraph';
|
|
3
3
|
|
|
4
4
|
/** A factory export: called (optionally with per-run config) to produce a compiled graph. */
|
|
@@ -119,6 +119,14 @@ interface ProtocolContext {
|
|
|
119
119
|
deps: ResolvedDeps;
|
|
120
120
|
control: RunControlRegistry;
|
|
121
121
|
locks: ThreadLocks;
|
|
122
|
+
/**
|
|
123
|
+
* The authenticated caller for the current request, populated by the authorizing handler wrapper
|
|
124
|
+
* so the run service can stamp it onto a run's kwargs. Undefined when no auth engine is
|
|
125
|
+
* configured (the default), matching `langgraph dev` — no principal is injected into the graph.
|
|
126
|
+
*/
|
|
127
|
+
authUser?: AuthUser;
|
|
128
|
+
/** The current request's authenticated permission scopes (`AuthContext.scopes`), stamped with {@link authUser}. */
|
|
129
|
+
authScopes?: string[];
|
|
122
130
|
}
|
|
123
131
|
declare function createContext(deps: ProtocolDeps): ProtocolContext;
|
|
124
132
|
|
|
@@ -159,7 +167,7 @@ interface RunService {
|
|
|
159
167
|
}
|
|
160
168
|
|
|
161
169
|
interface StoreService {
|
|
162
|
-
put(namespace: string[], key: string, value: Record<string, unknown
|
|
170
|
+
put(namespace: string[], key: string, value: Record<string, unknown>, options?: StorePutOptions): Promise<Item>;
|
|
163
171
|
get(namespace: string[], key: string): Promise<Item>;
|
|
164
172
|
delete(namespace: string[], key: string): Promise<void>;
|
|
165
173
|
search(query: StoreSearchQuery): Promise<SearchItem[]>;
|
|
@@ -180,7 +188,11 @@ interface ThreadService {
|
|
|
180
188
|
create(input?: CreateThreadInput): Promise<Thread>;
|
|
181
189
|
get(threadId: string): Promise<Thread>;
|
|
182
190
|
list(): Promise<Thread[]>;
|
|
191
|
+
/** Filtered + paginated listing — `POST /threads/search`. */
|
|
192
|
+
search(query: ThreadSearchQuery): Promise<Thread[]>;
|
|
183
193
|
patch(threadId: string, patch: PatchThreadInput): Promise<Thread>;
|
|
194
|
+
/** Duplicate a thread (new id) together with its full checkpoint history — `POST /threads/{id}/copy`. */
|
|
195
|
+
copy(threadId: string): Promise<Thread>;
|
|
184
196
|
delete(threadId: string): Promise<void>;
|
|
185
197
|
history(threadId: string, options?: HistoryOptions): Promise<ThreadState[]>;
|
|
186
198
|
/** The thread's current state snapshot — `GET /threads/{id}/state`, what `useStream` hydrates from. */
|
|
@@ -262,6 +274,7 @@ interface ProtocolHandlers {
|
|
|
262
274
|
createThread: ProtocolHandler;
|
|
263
275
|
getThread: ProtocolHandler;
|
|
264
276
|
listThreads: ProtocolHandler;
|
|
277
|
+
copyThread: ProtocolHandler;
|
|
265
278
|
patchThread: ProtocolHandler;
|
|
266
279
|
deleteThread: ProtocolHandler;
|
|
267
280
|
getThreadHistory: ProtocolHandler;
|
package/dist/index.js
CHANGED
|
@@ -101,6 +101,16 @@ var threadCreateSchema = z.object({
|
|
|
101
101
|
var threadPatchSchema = z.object({
|
|
102
102
|
metadata: z.record(z.unknown()).optional()
|
|
103
103
|
}).passthrough();
|
|
104
|
+
var threadSearchSchema = z.object({
|
|
105
|
+
metadata: z.record(z.unknown()).optional(),
|
|
106
|
+
values: z.record(z.unknown()).optional(),
|
|
107
|
+
status: z.enum(["idle", "busy", "interrupted", "error"]).optional(),
|
|
108
|
+
ids: z.array(z.string()).optional(),
|
|
109
|
+
limit: z.number().int().positive().optional(),
|
|
110
|
+
offset: z.number().int().nonnegative().optional(),
|
|
111
|
+
sort_by: z.enum(["thread_id", "status", "created_at", "updated_at"]).optional(),
|
|
112
|
+
sort_order: z.enum(["asc", "desc"]).optional()
|
|
113
|
+
}).passthrough();
|
|
104
114
|
var assistantSearchSchema = z.object({
|
|
105
115
|
graph_id: z.string().optional(),
|
|
106
116
|
limit: z.number().int().positive().optional(),
|
|
@@ -109,7 +119,9 @@ var assistantSearchSchema = z.object({
|
|
|
109
119
|
var storePutSchema = z.object({
|
|
110
120
|
namespace: z.array(z.string()).min(1),
|
|
111
121
|
key: z.string().min(1),
|
|
112
|
-
value: z.record(z.unknown())
|
|
122
|
+
value: z.record(z.unknown()),
|
|
123
|
+
/** Optional item lifetime in minutes; overrides the configured `store.ttl.default_ttl`. */
|
|
124
|
+
ttl: z.number().positive().optional()
|
|
113
125
|
}).passthrough();
|
|
114
126
|
var storeSearchSchema = z.object({
|
|
115
127
|
namespace_prefix: z.array(z.string()).optional(),
|
|
@@ -153,7 +165,22 @@ function createProtocolHandlers(service) {
|
|
|
153
165
|
// --- threads ------------------------------------------------------------------------------
|
|
154
166
|
createThread: async (req) => json(await service.threads.create(parse(threadCreateSchema, req.body ?? {}))),
|
|
155
167
|
getThread: async (req) => json(await service.threads.get(requireParam(req.params, "thread_id"))),
|
|
156
|
-
listThreads: async () =>
|
|
168
|
+
listThreads: async (req) => {
|
|
169
|
+
const body = parse(threadSearchSchema, req.body ?? {});
|
|
170
|
+
return json(
|
|
171
|
+
await service.threads.search({
|
|
172
|
+
metadata: body.metadata,
|
|
173
|
+
values: body.values,
|
|
174
|
+
status: body.status,
|
|
175
|
+
ids: body.ids,
|
|
176
|
+
limit: body.limit,
|
|
177
|
+
offset: body.offset,
|
|
178
|
+
sortBy: body.sort_by,
|
|
179
|
+
sortOrder: body.sort_order
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
},
|
|
183
|
+
copyThread: async (req) => json(await service.threads.copy(requireParam(req.params, "thread_id"))),
|
|
157
184
|
patchThread: async (req) => json(
|
|
158
185
|
await service.threads.patch(
|
|
159
186
|
requireParam(req.params, "thread_id"),
|
|
@@ -221,7 +248,7 @@ function createProtocolHandlers(service) {
|
|
|
221
248
|
// --- store --------------------------------------------------------------------------------
|
|
222
249
|
putStoreItem: async (req) => {
|
|
223
250
|
const body = parse(storePutSchema, req.body);
|
|
224
|
-
return json(await service.store.put(body.namespace, body.key, body.value));
|
|
251
|
+
return json(await service.store.put(body.namespace, body.key, body.value, { ttl: body.ttl }));
|
|
225
252
|
},
|
|
226
253
|
getStoreItem: async (req) => {
|
|
227
254
|
const namespace = namespaceFromQuery(req.query["namespace"]);
|
|
@@ -496,9 +523,11 @@ function threadStatusForRun(status) {
|
|
|
496
523
|
case "error":
|
|
497
524
|
case "timeout":
|
|
498
525
|
return "error";
|
|
499
|
-
// pending (not yet started) and
|
|
526
|
+
// pending (not yet started), success, and cancelled all leave the thread idle. A cancellation
|
|
527
|
+
// is a clean stop, not a failure, so the thread returns to idle rather than error.
|
|
500
528
|
case "pending":
|
|
501
529
|
case "success":
|
|
530
|
+
case "cancelled":
|
|
502
531
|
return "idle";
|
|
503
532
|
default:
|
|
504
533
|
return "idle";
|
|
@@ -593,7 +622,10 @@ var RESERVED_CONFIGURABLE_KEYS = /* @__PURE__ */ new Set([
|
|
|
593
622
|
"checkpoint_id",
|
|
594
623
|
"checkpoint_ns",
|
|
595
624
|
"checkpoint_map",
|
|
596
|
-
"checkpoint"
|
|
625
|
+
"checkpoint",
|
|
626
|
+
"langgraph_auth_user",
|
|
627
|
+
"langgraph_auth_user_id",
|
|
628
|
+
"langgraph_auth_permissions"
|
|
597
629
|
]);
|
|
598
630
|
function sanitizeConfigurable(configurable) {
|
|
599
631
|
if (!configurable) return {};
|
|
@@ -604,10 +636,32 @@ function sanitizeConfigurable(configurable) {
|
|
|
604
636
|
}
|
|
605
637
|
return clean;
|
|
606
638
|
}
|
|
639
|
+
function withAuthUser(configurable, authUser, scopes) {
|
|
640
|
+
if (!authUser) return configurable;
|
|
641
|
+
return {
|
|
642
|
+
...configurable,
|
|
643
|
+
langgraph_auth_user: authUser,
|
|
644
|
+
langgraph_auth_user_id: authUser.identity,
|
|
645
|
+
langgraph_auth_permissions: scopes ?? authUser.permissions
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function toFactoryConfigurable(kwargs) {
|
|
649
|
+
const configurable = withAuthUser(
|
|
650
|
+
sanitizeConfigurable(kwargs.config?.configurable),
|
|
651
|
+
kwargs.auth_user,
|
|
652
|
+
kwargs.auth_scopes
|
|
653
|
+
);
|
|
654
|
+
return Object.keys(configurable).length > 0 ? configurable : void 0;
|
|
655
|
+
}
|
|
607
656
|
function toGraphCallOptions(kwargs, threadId, signal) {
|
|
608
657
|
const options = {
|
|
609
|
-
// Drop server-owned keys from the client's configurable,
|
|
610
|
-
|
|
658
|
+
// Drop server-owned keys from the client's configurable, force this thread's id, then stamp the
|
|
659
|
+
// authenticated caller so the graph can authorize off `configurable.langgraph_auth_user`.
|
|
660
|
+
configurable: withAuthUser(
|
|
661
|
+
{ ...sanitizeConfigurable(kwargs.config?.configurable), thread_id: threadId },
|
|
662
|
+
kwargs.auth_user,
|
|
663
|
+
kwargs.auth_scopes
|
|
664
|
+
),
|
|
611
665
|
streamMode: normalizeModes(kwargs.stream_mode),
|
|
612
666
|
signal
|
|
613
667
|
};
|
|
@@ -688,7 +742,12 @@ function describeInterrupts(snapshot) {
|
|
|
688
742
|
// src/runs/run-engine.ts
|
|
689
743
|
async function resolveGraph(deps, graphId, kwargs) {
|
|
690
744
|
const resolved = await deps.graphs.load(graphId);
|
|
691
|
-
const graph = typeof resolved === "function" ?
|
|
745
|
+
const graph = typeof resolved === "function" ? (
|
|
746
|
+
// Expose the authenticated caller to a graph *factory* too, so a factory that branches on the
|
|
747
|
+
// principal sees the same `langgraph_auth_user` a node reads — sanitized identically, so a
|
|
748
|
+
// client can't spoof it via its own configurable.
|
|
749
|
+
await resolved({ configurable: toFactoryConfigurable(kwargs) })
|
|
750
|
+
) : resolved;
|
|
692
751
|
graph.checkpointer = deps.checkpointer;
|
|
693
752
|
graph.store = new SkeinBaseStore(deps.store.store);
|
|
694
753
|
return graph;
|
|
@@ -775,9 +834,9 @@ async function executeRun(deps, exec) {
|
|
|
775
834
|
}
|
|
776
835
|
if (control.signal.aborted) {
|
|
777
836
|
const timedOut = control.reason.current === "timeout";
|
|
778
|
-
const finalStatus2 = await finalizeRun(deps, runId, timedOut ? "timeout" : "
|
|
837
|
+
const finalStatus2 = await finalizeRun(deps, runId, timedOut ? "timeout" : "cancelled");
|
|
779
838
|
if (finalStatus2 === "timeout") await mirrorThreadError(deps, threadId, "Run timed out.");
|
|
780
|
-
else if (finalStatus2 === "
|
|
839
|
+
else if (finalStatus2 === "cancelled") await mirrorThreadStatus(deps, threadId, "idle");
|
|
781
840
|
logFinished(finalStatus2);
|
|
782
841
|
return { status: finalStatus2, values: {} };
|
|
783
842
|
}
|
|
@@ -806,8 +865,8 @@ async function executeRun(deps, exec) {
|
|
|
806
865
|
return { status: finalStatus2, values: {} };
|
|
807
866
|
}
|
|
808
867
|
if (reason === "cancel" || control.signal.aborted) {
|
|
809
|
-
const finalStatus2 = await finalizeRun(deps, runId, "
|
|
810
|
-
if (finalStatus2 === "
|
|
868
|
+
const finalStatus2 = await finalizeRun(deps, runId, "cancelled");
|
|
869
|
+
if (finalStatus2 === "cancelled") await mirrorThreadStatus(deps, threadId, "idle");
|
|
811
870
|
logFinished(finalStatus2);
|
|
812
871
|
return { status: finalStatus2, values: {} };
|
|
813
872
|
}
|
|
@@ -826,7 +885,7 @@ async function executeRun(deps, exec) {
|
|
|
826
885
|
}
|
|
827
886
|
|
|
828
887
|
// src/runs/run-service.ts
|
|
829
|
-
function toKwargs(input) {
|
|
888
|
+
function toKwargs(input, authUser, authScopes) {
|
|
830
889
|
const kwargs = {};
|
|
831
890
|
if (input.input !== void 0) kwargs.input = input.input;
|
|
832
891
|
if (input.command !== void 0) kwargs.command = input.command;
|
|
@@ -835,24 +894,44 @@ function toKwargs(input) {
|
|
|
835
894
|
if (input.stream_mode !== void 0) kwargs.stream_mode = input.stream_mode;
|
|
836
895
|
if (input.interrupt_before !== void 0) kwargs.interrupt_before = input.interrupt_before;
|
|
837
896
|
if (input.interrupt_after !== void 0) kwargs.interrupt_after = input.interrupt_after;
|
|
897
|
+
if (authUser !== void 0) {
|
|
898
|
+
kwargs.auth_user = authUser;
|
|
899
|
+
if (authScopes !== void 0) kwargs.auth_scopes = authScopes;
|
|
900
|
+
}
|
|
838
901
|
return kwargs;
|
|
839
902
|
}
|
|
840
903
|
function createRunService(ctx) {
|
|
841
|
-
const { deps, control, locks } = ctx;
|
|
904
|
+
const { deps, control, locks, authUser, authScopes } = ctx;
|
|
842
905
|
const requireThread = async (threadId) => {
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
906
|
+
const thread = await deps.store.threads.get(threadId);
|
|
907
|
+
if (!thread) throw SkeinHttpError5.notFound(`Thread "${threadId}" not found.`);
|
|
908
|
+
return thread;
|
|
846
909
|
};
|
|
847
910
|
const requireAssistant = async (assistantId) => {
|
|
848
911
|
const assistant = await deps.store.assistants.get(assistantId);
|
|
849
912
|
if (!assistant) throw SkeinHttpError5.notFound(`Assistant "${assistantId}" not found.`);
|
|
850
|
-
return assistant
|
|
913
|
+
return assistant;
|
|
914
|
+
};
|
|
915
|
+
const stampGraphOnThread = async (thread, assistant) => {
|
|
916
|
+
if (thread.metadata?.["graph_id"] === assistant.graph_id && thread.metadata?.["assistant_id"] === assistant.assistant_id) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
try {
|
|
920
|
+
await deps.store.threads.update(thread.thread_id, {
|
|
921
|
+
metadata: {
|
|
922
|
+
...thread.metadata,
|
|
923
|
+
graph_id: assistant.graph_id,
|
|
924
|
+
assistant_id: assistant.assistant_id
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
} catch (error) {
|
|
928
|
+
deps.logger.warn("failed to stamp graph_id/assistant_id onto thread", error);
|
|
929
|
+
}
|
|
851
930
|
};
|
|
852
931
|
const ensureThread = async (threadId) => {
|
|
853
|
-
if (threadId === void 0) return
|
|
932
|
+
if (threadId === void 0) return deps.store.threads.create();
|
|
854
933
|
const existing = await deps.store.threads.get(threadId);
|
|
855
|
-
return
|
|
934
|
+
return existing ?? await deps.store.threads.create({ thread_id: threadId });
|
|
856
935
|
};
|
|
857
936
|
const createPendingRun = async (input, threadId, assistantId, kwargs) => {
|
|
858
937
|
return locks.run(threadId, async () => {
|
|
@@ -884,33 +963,36 @@ function createRunService(ctx) {
|
|
|
884
963
|
};
|
|
885
964
|
return {
|
|
886
965
|
async createWait(input) {
|
|
887
|
-
const
|
|
888
|
-
const
|
|
889
|
-
const kwargs = toKwargs(input);
|
|
890
|
-
const run = await createPendingRun(input,
|
|
966
|
+
const assistant = await requireAssistant(input.assistant_id);
|
|
967
|
+
const thread = await ensureThread(input.thread_id);
|
|
968
|
+
const kwargs = toKwargs(input, authUser, authScopes);
|
|
969
|
+
const run = await createPendingRun(input, thread.thread_id, assistant.assistant_id, kwargs);
|
|
970
|
+
await stampGraphOnThread(thread, assistant);
|
|
891
971
|
const outcome = await runInline(run, kwargs);
|
|
892
972
|
return outcome.values;
|
|
893
973
|
},
|
|
894
974
|
async createStream(input) {
|
|
895
|
-
const
|
|
896
|
-
const
|
|
897
|
-
const kwargs = toKwargs(input);
|
|
898
|
-
const run = await createPendingRun(input,
|
|
975
|
+
const assistant = await requireAssistant(input.assistant_id);
|
|
976
|
+
const thread = await ensureThread(input.thread_id);
|
|
977
|
+
const kwargs = toKwargs(input, authUser, authScopes);
|
|
978
|
+
const run = await createPendingRun(input, thread.thread_id, assistant.assistant_id, kwargs);
|
|
979
|
+
await stampGraphOnThread(thread, assistant);
|
|
899
980
|
void runInline(run, kwargs).catch(
|
|
900
981
|
(error) => deps.logger.error("stream run failed", error)
|
|
901
982
|
);
|
|
902
983
|
return { runId: run.run_id, frames: deps.bus.subscribe(run.run_id, 0) };
|
|
903
984
|
},
|
|
904
985
|
async createBackground(threadId, input) {
|
|
905
|
-
const
|
|
906
|
-
await requireThread(threadId);
|
|
907
|
-
const kwargs = toKwargs(input);
|
|
986
|
+
const assistant = await requireAssistant(input.assistant_id);
|
|
987
|
+
const thread = await requireThread(threadId);
|
|
988
|
+
const kwargs = toKwargs(input, authUser, authScopes);
|
|
908
989
|
const run = await createPendingRun(
|
|
909
990
|
{ ...input, thread_id: threadId },
|
|
910
991
|
threadId,
|
|
911
|
-
|
|
992
|
+
assistant.assistant_id,
|
|
912
993
|
kwargs
|
|
913
994
|
);
|
|
995
|
+
await stampGraphOnThread(thread, assistant);
|
|
914
996
|
await deps.queue.enqueue({ run_id: run.run_id, thread_id: threadId });
|
|
915
997
|
return run;
|
|
916
998
|
},
|
|
@@ -928,12 +1010,12 @@ function createRunService(ctx) {
|
|
|
928
1010
|
if (!run) throw SkeinHttpError5.notFound(`Run "${runId}" not found.`);
|
|
929
1011
|
if (isTerminalRunStatus3(run.status)) return run;
|
|
930
1012
|
if (run.status === "pending") {
|
|
931
|
-
await deps.store.runs.setStatus(runId, "
|
|
1013
|
+
await deps.store.runs.setStatus(runId, "cancelled");
|
|
932
1014
|
await deps.store.threads.update(run.thread_id, { status: "idle" });
|
|
933
1015
|
await deps.bus.close(runId);
|
|
934
1016
|
control.abort(runId, "cancel");
|
|
935
1017
|
} else {
|
|
936
|
-
await deps.store.runs.setStatus(runId, "
|
|
1018
|
+
await deps.store.runs.setStatus(runId, "cancelled");
|
|
937
1019
|
await deps.store.threads.update(run.thread_id, { status: "idle" });
|
|
938
1020
|
control.abort(runId, "cancel");
|
|
939
1021
|
}
|
|
@@ -960,10 +1042,12 @@ function createRunService(ctx) {
|
|
|
960
1042
|
}
|
|
961
1043
|
|
|
962
1044
|
// src/store/store-service.ts
|
|
963
|
-
import {
|
|
1045
|
+
import {
|
|
1046
|
+
SkeinHttpError as SkeinHttpError6
|
|
1047
|
+
} from "@skein-js/core";
|
|
964
1048
|
function createStoreService(deps) {
|
|
965
1049
|
return {
|
|
966
|
-
put: (namespace, key, value) => deps.store.store.put(namespace, key, value),
|
|
1050
|
+
put: (namespace, key, value, options) => deps.store.store.put(namespace, key, value, options),
|
|
967
1051
|
async get(namespace, key) {
|
|
968
1052
|
const item = await deps.store.store.get(namespace, key);
|
|
969
1053
|
if (!item) {
|
|
@@ -980,10 +1064,50 @@ function createStoreService(deps) {
|
|
|
980
1064
|
}
|
|
981
1065
|
|
|
982
1066
|
// src/threads/thread-service.ts
|
|
1067
|
+
import {
|
|
1068
|
+
copyCheckpoint
|
|
1069
|
+
} from "@langchain/langgraph";
|
|
983
1070
|
import {
|
|
984
1071
|
isTerminalRunStatus as isTerminalRunStatus4,
|
|
985
1072
|
SkeinHttpError as SkeinHttpError7
|
|
986
1073
|
} from "@skein-js/core";
|
|
1074
|
+
async function copyCheckpointHistory(checkpointer, sourceId, targetId) {
|
|
1075
|
+
const tuples = [];
|
|
1076
|
+
for await (const tuple of checkpointer.list({ configurable: { thread_id: sourceId } })) {
|
|
1077
|
+
tuples.push(tuple);
|
|
1078
|
+
}
|
|
1079
|
+
for (const tuple of tuples.reverse()) {
|
|
1080
|
+
const ns = tuple.config.configurable?.checkpoint_ns ?? "";
|
|
1081
|
+
const parentId = tuple.parentConfig?.configurable?.checkpoint_id;
|
|
1082
|
+
const putConfig = {
|
|
1083
|
+
configurable: { thread_id: targetId, checkpoint_ns: ns, checkpoint_id: parentId }
|
|
1084
|
+
};
|
|
1085
|
+
await checkpointer.put(
|
|
1086
|
+
putConfig,
|
|
1087
|
+
copyCheckpoint(tuple.checkpoint),
|
|
1088
|
+
tuple.metadata ?? {},
|
|
1089
|
+
tuple.checkpoint.channel_versions
|
|
1090
|
+
);
|
|
1091
|
+
if (tuple.pendingWrites && tuple.pendingWrites.length > 0) {
|
|
1092
|
+
const writeConfig = {
|
|
1093
|
+
configurable: {
|
|
1094
|
+
thread_id: targetId,
|
|
1095
|
+
checkpoint_ns: ns,
|
|
1096
|
+
checkpoint_id: tuple.checkpoint.id
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
const byTask = /* @__PURE__ */ new Map();
|
|
1100
|
+
for (const [taskId, channel, value] of tuple.pendingWrites) {
|
|
1101
|
+
const writes = byTask.get(taskId) ?? [];
|
|
1102
|
+
writes.push([channel, value]);
|
|
1103
|
+
byTask.set(taskId, writes);
|
|
1104
|
+
}
|
|
1105
|
+
for (const [taskId, writes] of byTask) {
|
|
1106
|
+
await checkpointer.putWrites(writeConfig, writes, taskId);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
987
1111
|
function emptyThreadState(threadId) {
|
|
988
1112
|
return {
|
|
989
1113
|
values: {},
|
|
@@ -1037,10 +1161,33 @@ function createThreadService(ctx) {
|
|
|
1037
1161
|
create: (input) => deps.store.threads.create(input),
|
|
1038
1162
|
get: requireThread,
|
|
1039
1163
|
list: () => deps.store.threads.list(),
|
|
1164
|
+
search: (query) => deps.store.threads.search(query),
|
|
1040
1165
|
async patch(threadId, patch) {
|
|
1041
1166
|
await requireThread(threadId);
|
|
1042
1167
|
return deps.store.threads.update(threadId, { metadata: patch.metadata });
|
|
1043
1168
|
},
|
|
1169
|
+
async copy(threadId) {
|
|
1170
|
+
await requireThread(threadId);
|
|
1171
|
+
const copy = await deps.store.threads.copy(threadId);
|
|
1172
|
+
await copyCheckpointHistory(deps.checkpointer, threadId, copy.thread_id);
|
|
1173
|
+
const sourceRuns = await deps.store.runs.listByThread(threadId);
|
|
1174
|
+
for (const run of [...sourceRuns].sort((a, b) => a.created_at.localeCompare(b.created_at))) {
|
|
1175
|
+
if (!isTerminalRunStatus4(run.status)) continue;
|
|
1176
|
+
const kwargs = await deps.store.runs.getKwargs(run.run_id);
|
|
1177
|
+
await deps.store.runs.create({
|
|
1178
|
+
thread_id: copy.thread_id,
|
|
1179
|
+
assistant_id: run.assistant_id,
|
|
1180
|
+
status: run.status,
|
|
1181
|
+
metadata: run.metadata,
|
|
1182
|
+
multitask_strategy: run.multitask_strategy,
|
|
1183
|
+
...kwargs ? { kwargs } : {}
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
if (copy.status === "busy") {
|
|
1187
|
+
return deps.store.threads.update(copy.thread_id, { status: "idle" });
|
|
1188
|
+
}
|
|
1189
|
+
return copy;
|
|
1190
|
+
},
|
|
1044
1191
|
async delete(threadId) {
|
|
1045
1192
|
await requireThread(threadId);
|
|
1046
1193
|
const runs = await deps.store.runs.listByThread(threadId);
|
|
@@ -1162,6 +1309,14 @@ function createAuthScopedStore(inner, engine, filters, resource) {
|
|
|
1162
1309
|
if (resource === "threads") {
|
|
1163
1310
|
const threads = {
|
|
1164
1311
|
list: async () => (await inner.threads.list()).filter((thread) => matches(thread.metadata)),
|
|
1312
|
+
search: async (query) => {
|
|
1313
|
+
const { limit, offset, ...filters2 } = query;
|
|
1314
|
+
const owned = (await inner.threads.search(filters2)).filter(
|
|
1315
|
+
(thread) => matches(thread.metadata)
|
|
1316
|
+
);
|
|
1317
|
+
const start = offset ?? 0;
|
|
1318
|
+
return owned.slice(start, limit === void 0 ? void 0 : start + limit);
|
|
1319
|
+
},
|
|
1165
1320
|
get: async (threadId) => {
|
|
1166
1321
|
const thread = await inner.threads.get(threadId);
|
|
1167
1322
|
return thread && matches(thread.metadata) ? thread : null;
|
|
@@ -1183,6 +1338,13 @@ function createAuthScopedStore(inner, engine, filters, resource) {
|
|
|
1183
1338
|
const next = patch.metadata !== void 0 ? { ...patch, metadata: stamp(patch.metadata) } : patch;
|
|
1184
1339
|
return inner.threads.update(threadId, next);
|
|
1185
1340
|
},
|
|
1341
|
+
copy: async (threadId) => {
|
|
1342
|
+
const thread = await inner.threads.get(threadId);
|
|
1343
|
+
if (!thread || !matches(thread.metadata)) {
|
|
1344
|
+
throw SkeinHttpError9.notFound(`Thread "${threadId}" not found.`);
|
|
1345
|
+
}
|
|
1346
|
+
return inner.threads.copy(threadId);
|
|
1347
|
+
},
|
|
1186
1348
|
delete: async (threadId) => {
|
|
1187
1349
|
const thread = await inner.threads.get(threadId);
|
|
1188
1350
|
if (!thread || !matches(thread.metadata)) {
|
|
@@ -1227,6 +1389,7 @@ var ROUTE_AUTHZ = {
|
|
|
1227
1389
|
searchAssistants: { resource: "assistants", action: "search" },
|
|
1228
1390
|
// threads
|
|
1229
1391
|
createThread: { resource: "threads", action: "create" },
|
|
1392
|
+
copyThread: { resource: "threads", action: "create" },
|
|
1230
1393
|
getThread: { resource: "threads", action: "read" },
|
|
1231
1394
|
getThreadState: { resource: "threads", action: "read" },
|
|
1232
1395
|
getThreadHistory: { resource: "threads", action: "read" },
|
|
@@ -1281,7 +1444,7 @@ var STUDIO_USER = {
|
|
|
1281
1444
|
};
|
|
1282
1445
|
function createAuthorizingHandlers(context, engine) {
|
|
1283
1446
|
const baseHandlers = createProtocolHandlers(buildProtocolService(context));
|
|
1284
|
-
const names = Object.keys(
|
|
1447
|
+
const names = Object.keys(ROUTE_AUTHZ);
|
|
1285
1448
|
const resolveAuthContext = async (req) => {
|
|
1286
1449
|
if (!engine.studioAuthDisabled && req.headers["x-auth-scheme"] === "langsmith") {
|
|
1287
1450
|
return { user: STUDIO_USER, scopes: [] };
|
|
@@ -1299,13 +1462,17 @@ function createAuthorizingHandlers(context, engine) {
|
|
|
1299
1462
|
value: authValue(req),
|
|
1300
1463
|
context: authContext
|
|
1301
1464
|
});
|
|
1302
|
-
if (!filters) return baseHandlers[name](req);
|
|
1303
|
-
const
|
|
1304
|
-
const scopedContext = {
|
|
1465
|
+
if (!filters && !authContext) return baseHandlers[name](req);
|
|
1466
|
+
const requestContext = {
|
|
1305
1467
|
...context,
|
|
1306
|
-
|
|
1468
|
+
authUser: authContext?.user,
|
|
1469
|
+
authScopes: authContext?.scopes,
|
|
1470
|
+
deps: filters ? {
|
|
1471
|
+
...context.deps,
|
|
1472
|
+
store: createAuthScopedStore(context.deps.store, engine, filters, route.resource)
|
|
1473
|
+
} : context.deps
|
|
1307
1474
|
};
|
|
1308
|
-
return createProtocolHandlers(buildProtocolService(
|
|
1475
|
+
return createProtocolHandlers(buildProtocolService(requestContext))[name](req);
|
|
1309
1476
|
};
|
|
1310
1477
|
}
|
|
1311
1478
|
return wrapped;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skein-js/agent-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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.5.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.5.0"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public"
|